@reforgium/data-grid 1.1.0 → 2.0.0

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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://badge.fury.io/js/%40reforgium%2Fdata-grid.svg)](https://www.npmjs.com/package/@reforgium/data-grid)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- **High-performance data grid for Angular (20+).**
6
+ **High-performance data grid for Angular (18+).**
7
7
 
8
8
  `@reforgium/data-grid` provides a flexible and performant component for displaying large tabular datasets.
9
9
  It focuses on smooth scrolling, predictable layout, and full control over rendering via templates and signals.
@@ -18,14 +18,17 @@ Designed for **real-world datasets**, not demo tables.
18
18
  - Virtual row rendering (smooth, no jumps)
19
19
  - Infinity scroll (loads data when reaching the bottom)
20
20
  - Jitter-free fixed (sticky) columns
21
- - Two-line headers max, with ellipsis + tooltip
21
+ - Two-line headers max, with ellipsis and tooltip
22
+ - Declarative column DSL (`<re-dg-column>`) *[NEW in 2.0.0]*
22
23
  - Column expanders (hidden columns via toggler)
23
24
  - Scrollable overlay scrollbar
24
25
  - Pinned rows (top and bottom)
25
26
  - Custom templates for headers, cells, pinned rows, icons
27
+ - Skeleton loading rows for pagination/infinity *[NEW in 2.0.0]*
26
28
  - Row selection (single / multi)
27
29
  - Signals-based API (`signal()` first)
28
30
  - Paginator component *[NEW in 1.1.0]*
31
+ - Column manager dropdown *[NEW in 2.0.0]*
29
32
 
30
33
  ---
31
34
 
@@ -37,12 +40,16 @@ npm install @reforgium/data-grid
37
40
 
38
41
  ```ts
39
42
  import { DataGrid } from '@reforgium/data-grid';
43
+ import { DataGridPaginator } from '@reforgium/data-grid/paginator';
44
+ import { DataGridColumnManager } from '@reforgium/data-grid/column-manager';
40
45
 
41
- @Component({ imports: [DataGrid]})
42
- export class SomeComponent {}
46
+ @Component({ imports: [DataGrid, DataGridPaginator] })
47
+ export class SomeComponent {
48
+ }
43
49
  ```
44
50
 
45
51
  ```html
52
+
46
53
  <re-data-grid
47
54
  mode="infinity"
48
55
  [data]="users"
@@ -53,172 +60,514 @@ export class SomeComponent {}
53
60
  />
54
61
  ```
55
62
 
63
+ ### Column manager
64
+
65
+ Column manager is a secondary entrypoint that provides a dropdown UI for reordering, show/hide, and pinning columns.
66
+
67
+ ```ts
68
+ import { DataGridColumnManager } from '@reforgium/data-grid/column-manager';
69
+ ```
70
+
71
+ ```html
72
+
73
+ <re-data-grid-column-manager
74
+ triggerLabel="Columns"
75
+ [columns]="managedColumns()"
76
+ (columnsChange)="managedColumns.set($event)"
77
+ />
78
+
79
+ <re-data-grid [columns]="managedColumns()" ... />
80
+ ```
81
+
82
+ Custom trigger template:
83
+
84
+ ```html
85
+
86
+ <ng-template #cmTrigger let-label let-visible="visible" let-total="total">
87
+ <span>{{ label }}</span>
88
+ <strong>{{ visible }}/{{ total }}</strong>
89
+ </ng-template>
90
+
91
+ <re-data-grid-column-manager
92
+ [columns]="managedColumns()"
93
+ [triggerTemplate]="cmTrigger"
94
+ (columnsChange)="managedColumns.set($event)"
95
+ />
96
+ ```
97
+
98
+ Trigger template context:
99
+
100
+ - `$implicit` — trigger label
101
+ - `visible` — visible columns count
102
+ - `total` — total columns count
103
+
104
+ Inputs:
105
+
106
+ - `columns: GridColumn<T>[]`
107
+ - `triggerLabel?: string`
108
+ - `triggerTemplate?: TemplateRef<{ $implicit: string; visible: number; total: number }>`
109
+ - `controlsVisible?: boolean` — hides search and “show all / hide optional” panel
110
+ - `searchable?: boolean`
111
+ - `allowReorder?: boolean`
112
+ - `allowPin?: boolean`
113
+ - `allowVisibility?: boolean`
114
+
115
+ Outputs:
116
+
117
+ - `columnsChange: GridColumn<T>[]`
118
+
56
119
  ---
57
120
 
121
+ ## Migration notes (1.1.0 → 2.0.0)
122
+
123
+ - **Column tooltips are now explicit.** Use `tooltip` on a column (string / function / `TemplateRef`) to show a popover on hover.
124
+ - **New column manager entrypoint.** Import it from `@reforgium/data-grid/column-manager` and wire `columns` + `columnsChange`.
125
+ - **Sticky columns use sides.** Prefer `sticky="left"` / `sticky="right"`; legacy `true` still maps to left.
126
+
58
127
  ## Configuration
59
128
 
129
+ ### Global defaults provider
130
+
131
+ You can override default input values for all grid instances via DI:
132
+
133
+ ```ts
134
+ import { provideDataGridDefaults } from '@reforgium/data-grid';
135
+
136
+ export const appConfig: ApplicationConfig = {
137
+ providers: [
138
+ provideDataGridDefaults({
139
+ mode: 'pagination',
140
+ hasIndexColumn: true,
141
+ pageSize: 50,
142
+ }),
143
+ ],
144
+ };
145
+ ```
146
+
147
+ Supported default fields:
148
+
149
+ - `mode`
150
+ - `hasIndexColumn`
151
+ - `selection`
152
+ - `pageSize`
153
+ - `rowHeight`
154
+ - `headerHeight`
155
+ - `height`
156
+ - `virtualBuffer`
157
+ - `loadingMode`
158
+ - `pageStartFromZero`
159
+ - `translations`
160
+ - `debounce`
161
+
60
162
  ### Inputs
61
163
 
62
- | Parameter | Type | Default | Description | |
63
- |-------------------|-------------------------------------------------|--------------------|-------------------------------------|------------------|
64
- | data | `T[]` | `[]` | Data array to render | |
65
- | columns | `GridColumn<T>[]` | `[]` | Column configuration | |
66
- | pinnedRows | `GridPinnedRow<T>[]` | `[]` | Top / bottom pinned rows | |
67
- | mode | `'none' \| 'pagination' \| 'infinity'` | `'none'` | Grid operation mode | |
68
- | pageSize | `number` | `20` | Page size | |
69
- | pageStartFromZero | `boolean` | `true` | Start page index from 0 | |
70
- | hasIndexColumn | `boolean` | `false` | Show index column | |
71
- | selection | `GridSelection<T>` | `{ mode: 'none' }` | Row selection configuration | |
72
- | rowHeight | `number` | `40` | Row height (px) | *[FIX in 1.1.0]* |
73
- | virtualBuffer | `number` | `6` | Extra rows above/below viewport | |
74
- | height | `number \| 'full' \| 'default'` | `'full'` | Grid height | |
75
- | loading | `boolean` | `false` | Loading state | |
76
- | rowKey | `DataKey<T> \| ((item: T) => string \| number)` | `undefined` | Unique row key resolver | |
164
+ | Parameter | Type | Default | Description | |
165
+ |-------------------|--------------------------------------------------|--------------------|-------------------------------------------------------------------------------------------------|------------------|
166
+ | data | `T[]` | `[]` | Data array to render | |
167
+ | columns | `GridColumn<T>[]` | `[]` | Programmatic column configuration (used when no declarative columns are provided) | |
168
+ | headerGroups | `GridHeaderGroup<T>[]` | `[]` | Optional top header row groups (`from`..`to`) for 2-level headers | *[NEW in 2.0.0]* |
169
+ | pinnedRows | `GridPinnedRow<T>[]` | `[]` | Top / bottom pinned rows | |
170
+ | isRowSticky | `(row: T, index: number) => boolean` | `undefined` | Predicate that marks rows as sticky at the top | |
171
+ | getRowTemplate | `(row: T, index: number) => TemplateRef \| null` | `undefined` | Optional resolver for custom row template | |
172
+ | mode | `'none' \| 'pagination' \| 'infinity'` | `'pagination'` | Grid operation mode | |
173
+ | pageSize | `number` | `20` | Number of items per page (pagination/infinity modes) | |
174
+ | pageStartFromZero | `boolean` | `true` | If true, page indexing starts from 0, otherwise from 1 | |
175
+ | hasIndexColumn | `boolean` | `false` | If true, an index column will be shown | |
176
+ | selection | `GridSelection<T>` | `{ mode: 'none' }` | Row selection configuration | |
177
+ | rowHeight | `number` | `40` | Fixed height of each row in pixels | *[FIX in 1.1.0]* |
178
+ | virtualBuffer | `number` | `8` | Extra rows to render above/below viewport to reduce flickering during scrolling | |
179
+ | height | `number \| 'full' \| 'default'` | `'default'` | Grid height configuration (`number` for px, `'full'` for 100%, `'default'` for CSS variable) | |
180
+ | loading | `boolean` | `false` | Displays loading state template when true | |
181
+ | loadingMode | `'spinner' \| 'skeleton'` | `'spinner'` | Loading rendering mode (`spinner` shows centered spinner, `skeleton` enables row skeleton mode) | *[NEW in 2.0.0]* |
182
+ | rowKey | `DataKey<T> \| ((item: T) => string \| number)` | `undefined` | Property name or function to resolve unique row key | |
77
183
 
78
184
  When selection mode is `'single'` or `'multi'`, provide a `key` (data property) and optionally `defaultSelected`.
79
185
 
186
+ Loading behavior:
187
+
188
+ - `loadingMode="spinner"`: shows centered spinner over grid content.
189
+ - `loadingMode="skeleton"` + `mode="infinity"` + `loading=true`: appends 4 skeleton rows at the end of the current data.
190
+ - `loadingMode="skeleton"` + `mode="pagination"` + `loading=true` + empty data: renders 4 skeleton rows in the table body.
191
+
192
+ Sticky rows:
193
+
194
+ - Provide `isRowSticky` to mark rows that should stick to the top during scroll.
195
+ - You can customize the sticky row rendering with `reDataGridStickyRow` template.
196
+
197
+ ```html
198
+
199
+ <re-data-grid [data]="items" [columns]="columns" [isRowSticky]="isSticky">
200
+ <ng-template reDataGridStickyRow let-row let-index="index">
201
+ <div class="my-sticky-row">{{ index + 1 }}. {{ row.name }}</div>
202
+ </ng-template>
203
+ </re-data-grid>
204
+ ```
205
+
206
+ Row templates:
207
+
208
+ - Provide `getRowTemplate` to select a custom row template per row.
209
+ - Use `reDataGridRow` as a default row template.
210
+
211
+ ```html
212
+
213
+ <re-data-grid [data]="items" [columns]="columns" [getRowTemplate]="rowTpl">
214
+ <ng-template reDataGridRow let-index="index">
215
+ <div class="my-row">{{ index + 1 }}. {{ row.name }}</div>
216
+ </ng-template>
217
+ </re-data-grid>
218
+ ```
219
+
80
220
  ### Outputs
81
221
 
82
- | Event | Type | Description |
83
- |--------------|-------------------------|------------------|
84
- | cellClick | `GridCellClickEvent<T>` | Cell click |
85
- | rowClick | `GridRowClickEvent<T>` | Row click |
86
- | sortChange | `GridSortEvent<T>` | Sort change |
87
- | pageChange | `GridPageChangeEvent` | Page change |
88
- | selectChange | `GridSelectEvent<T>` | Selection change |
222
+ | Event | Type | Description | |
223
+ |-----------------|-------------------------------|--------------------------------------------------------|------------------|
224
+ | cellClick | `GridCellClickEvent<T>` | Emitted when a cell is clicked (includes native event) | *[UPD in 2.0.0]* |
225
+ | cellContext | `GridCellContextEvent<T>` | Emitted on cell context menu | *[NEW in 2.0.0]* |
226
+ | cellDoubleClick | `GridCellDoubleClickEvent<T>` | Emitted when a cell is double-clicked | *[NEW in 2.0.0]* |
227
+ | rowClick | `GridRowClickEvent<T>` | Emitted when a row is clicked (includes native event) | *[UPD in 2.0.0]* |
228
+ | rowContext | `GridRowContextEvent<T>` | Emitted on row context menu | *[NEW in 2.0.0]* |
229
+ | rowDoubleClick | `GridRowDoubleClickEvent<T>` | Emitted when a row is double-clicked | *[NEW in 2.0.0]* |
230
+ | sortChange | `GridSortEvent<T>` | Emitted when sort order changes | |
231
+ | pageChange | `GridPageChangeEvent` | Emitted when requesting data for a new page | |
232
+ | selectChange | `GridSelectEvent<T>` | Emitted when selected rows change | |
89
233
 
90
- ### Templates
234
+ Notes:
235
+
236
+ - A cell click also triggers the row click event (bubbling), so listen to one or stop propagation if needed.
237
+
238
+ ### GridColumn<T> reference
239
+
240
+ `columns` accepts `GridColumn<T>[]`, where `GridColumn<T>` is a union of three column variants:
241
+
242
+ - default column (`type` + optional `typeParams`)
243
+ - value column (`value` + required `track`)
244
+ - template column (`renderTemplate` + required `track`)
245
+
246
+ Common (base) fields:
247
+
248
+ | Field | Type | Description |
249
+ |-------------------------|-----------------------------------------------------------------|---------------------------------------------------------------------------------|
250
+ | `sortKey` | `string` | Alternative key used for sorting when display `key` differs from sortable data. |
251
+ | `sticky` | `'left' \| 'right' \| true` | Keeps the column fixed while horizontally scrolling. `true` pins to the left. |
252
+ | `expandBy` | `DataKey<T>` | Data key that controls expand/collapse behavior for the column. |
253
+ | `flex` | `number` | Flex grow factor for width distribution in flexible layouts. |
254
+ | `minWidth` / `maxWidth` | `number` | Column width limits in pixels. |
255
+ | `cellClass` | `string \| ((row: T) => string)` | Static CSS class or resolver per row. |
256
+ | `tooltip` | `string \| ((row: T) => string) \| TemplateRef<TooltipContext>` | Popover tooltip content shown on cell hover. |
257
+
258
+ Renderer-specific fields:
259
+
260
+ | Variant | Fields | Notes |
261
+ |-------------------|-----------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------|
262
+ | Built-in renderer | `type?: 'plain' \| 'date' \| 'number' \| 'index' \| 'checkbox'`, `typeParams?: any`, `defaultValue?: any` | `defaultValue` is used when source value is `null`/`undefined`; `typeParams` passes formatter config. |
263
+ | Value renderer | `value: (row: T) => string \| number`, `track: (row: T) => string` | `track` is required for stable row identity and efficient updates. |
264
+ | Template renderer | `renderTemplate: TemplateRef<...>`, `track: (row: T) => string` | `track` is required when rendering through Angular templates. |
91
265
 
92
- | Directive | Parameters | Description | |
93
- |------------------------|------------------------------------|----------------------------------------|------------------|
94
- | reDataGridHeader | key: string | Column header template | |
95
- | reDataGridCell | key: string | Cell template for specific column key | *[NEW in 1.1.0]* |
96
- | reDataGridTypeCell | type: string | Cell template for specific column type | |
97
- | reDataGridEmpty | - | Empty state template | |
98
- | reDataGridLoading | - | Loading state template | |
99
- | reDataGridSortIcon | order: GridSortOrder \| undefined | Sort icon template | *[NEW in 1.1.0]* |
100
- | reDataGridExpanderIcon | expanded: boolean | Expander icon template | *[NEW in 1.1.0]* |
266
+ ### Declarative columns *[NEW in 2.0.0]*
101
267
 
268
+ You can define columns directly in markup via `<re-dg-column>`, then the grid will normalize them to `GridColumn<T>` internally.
269
+
270
+ ```html
271
+
272
+ <re-data-grid [data]="items" [rowKey]="'id'">
273
+ <re-dg-column key="name">
274
+ <ng-template reHeader>Name</ng-template>
275
+ <ng-template reCell let-row="row">{{ row.name }}</ng-template>
276
+ </re-dg-column>
277
+
278
+ <re-dg-column key="age" sortKey="age">
279
+ <ng-template reHeader>Age</ng-template>
280
+ <ng-template reCell let-row="row">{{ row.age }}</ng-template>
281
+ </re-dg-column>
282
+
283
+ <re-dg-column key="extra" sticky="left" expandBy="age" disabled>
284
+ <ng-template reHeader>Extra</ng-template>
285
+ <ng-template reCell let-row="row">{{ row.extra }}</ng-template>
286
+ </re-dg-column>
287
+ </re-data-grid>
288
+ ```
289
+
290
+ Notes:
291
+
292
+ - If at least one `<re-dg-column>` is present, declarative columns are used as the source of truth.
293
+ - `reHeader` maps to `headerTemplate`.
294
+ - `reCell` receives value as `$implicit`, and the row is available as `let-row="row"` via `RenderTemplateData`.
295
+ - Most `GridColumn<T>` fields are available as `<re-dg-column>` inputs (`sortKey`, `sticky`, `expandBy`, `disabled`, `width`, `minWidth`, `maxWidth`, `flex`, `align`, `cellClass`, `type`, `typeParams`, `defaultValue`, `value`, `track`, `tooltip`).
296
+ - `sticky` accepts `'left' | 'right'`; legacy `true` maps to `'left'` for backward compatibility.
297
+
298
+ ### Tooltip
299
+
300
+ `tooltip` is opt-in per column and shows a popover on cell hover.
301
+
302
+ ```ts
303
+ columns = [
304
+ { key: 'email', header: 'Email', tooltip: (row) => row.email },
305
+ { key: 'status', header: 'Status', tooltip: 'User status' },
306
+ ];
307
+ ```
308
+
309
+ Template tooltip:
310
+
311
+ ```html
312
+
313
+ <ng-template #tip let-row let-col="col" let-index="index" let-value="value">
314
+ <div><b>{{ col.header }}</b> #{{ index + 1 }}</div>
315
+ <div>{{ value }}</div>
316
+ </ng-template>
317
+
318
+ <re-dg-column key="email" [tooltip]="tip"></re-dg-column>
319
+ ```
320
+
321
+ Tooltip template context (`TooltipContext`):
322
+
323
+ - `$implicit` / `row`
324
+ - `col`
325
+ - `index`
326
+ - `value`
327
+
328
+ ### GridHeaderGroup<T> reference
329
+
330
+ #### NEW in 2.0.0
331
+
332
+ Use `headerGroups` to render an optional top header row (2-level header layout) above regular column headers.
333
+
334
+ | Field | Type | Description |
335
+ |-----------------|-----------------------------------|------------------------------------------------------------------|
336
+ | `key` | `string` | Unique id of the header group. |
337
+ | `from` | `DataKey<T>` | Start column key (inclusive). |
338
+ | `to` | `DataKey<T>` | End column key (inclusive). If omitted, group covers one column. |
339
+ | `title` | `string` | Plain text title for the group. |
340
+ | `titleTemplate` | `TemplateRef<HeaderTemplateData>` | Template-based title for the group. |
341
+ | `align` | `'left' \| 'center' \| 'right'` | Optional group title alignment (plain title variant). |
342
+
343
+ Notes:
344
+
345
+ - Groups are normalized against visible columns only.
346
+ - Overlapping or invalid ranges are ignored during normalization.
347
+ - Columns not covered by groups are automatically placed into technical spacer groups to keep width alignment.
348
+
349
+ ### Templates
350
+
351
+ | Directive | Parameters | Description | |
352
+ |------------------------|-----------------------------------|---------------------------------------------------|------------------|
353
+ | reHeader | - | Declarative header template inside `re-dg-column` | *[NEW in 2.0.0]* |
354
+ | reCell | let-row | Declarative cell template inside `re-dg-column` | *[NEW in 2.0.0]* |
355
+ | reDataGridHeader | key: string | Template for specific column header by key | |
356
+ | reDataGridCell | key: string | Template for specific column cell by key | *[NEW in 1.1.0]* |
357
+ | reDataGridTypeCell | type: string | Template for cells of a specific column type | |
358
+ | reDataGridEmpty | - | Template for the empty state (no data) | |
359
+ | reDataGridLoading | - | Template for the loading state | |
360
+ | reDataGridSortIcon | order: GridSortOrder \| undefined | Template for custom sorting icon | *[NEW in 1.1.0]* |
361
+ | reDataGridExpanderIcon | expanded: boolean | Template for custom expander icon | *[NEW in 1.1.0]* |
102
362
 
103
363
  ### CSS Variables
104
364
 
105
365
  Layout / Appearance:
106
- - `--re-data-grid-min-height` — component min height (`200px`). *[NEW in 1.1.0]*
107
- - `--re-data-grid-height` component height (`400px`). *[UPDATED in 1.1.0]*
108
- - `--re-data-grid-rounded` border radius (`var(--radius-md, 6px)`).
109
- - `--re-data-grid-surface` table background (`#fff`).
110
- - `--re-data-grid-active` active color (`#2a90f4`).
366
+
367
+ - `--re-data-grid-min-height` - component min height (`200px`)
368
+ - `--re-data-grid-height` - component height (`400px`)
369
+ - `--re-data-grid-rounded` - border radius (`var(--radius-md, 6px)`)
370
+ - `--re-data-grid-separator-color` - outer border color (`var(--border-color)`)
371
+ - `--re-data-grid-separator` - outer border (`1px solid var(--re-data-grid-separator-color)`)
372
+ - `--re-data-grid-surface` - table background (`#fff`)
373
+ - `--re-data-grid-active` - active color (`#2a90f4`)
111
374
 
112
375
  Empty / Loading States:
113
- - `--re-data-grid-empty-color` — empty text color (`#777`)
114
- - `--re-data-grid-empty-surface` empty text background (`transparent`) *[NEW in 1.1.0]*
115
- - `--re-data-grid-loading-color` loading indicator color (`#444`)
116
- - `--re-data-grid-loading-surface` loading overlay (`rgba(255,255,255,0.5)`)
376
+
377
+ - `--re-data-grid-empty-color` - empty text color (`#777`)
378
+ - `--re-data-grid-empty-surface` - empty text background (`transparent`)
379
+ - `--re-data-grid-loading-color` - loading indicator color (`#444`)
380
+ - `--re-data-grid-loading-surface` - loading spinner (`rgba(255,255,255,0.5)`)
381
+ - `--re-data-grid-spinner-size` - spinner size (`2rem`)
382
+ - `--re-data-grid-spinner-width` - spinner ring thickness (`0.25rem`)
383
+ - `--re-data-grid-spinner-track` - spinner track color (`rgba(0, 0, 0, 0.12)`)
384
+ - `--re-data-grid-skeleton-width` - skeleton line width (`100%`)
385
+ - `--re-data-grid-skeleton-height` - skeleton line height (`100%`)
386
+ - `--re-data-grid-skeleton-rounded` - skeleton border radius (`var(--re-data-grid-rounded, 0.75rem)`)
387
+ - `--re-data-grid-skeleton-line` - skeleton base color (`#e7ebf0`)
388
+ - `--re-data-grid-skeleton-shine` - skeleton highlight color (`rgba(255, 255, 255, 0.8)`)
389
+
390
+ Note: for `--re-data-grid-skeleton-height` it's usually better to use percentages (for example `60%` / `70%`) so the line scales naturally with row height.
117
391
 
118
392
  Scrollbar:
119
- - `--re-data-grid-scrollbar-size` — track size (`10px`)
120
- - `--re-data-grid-scrollbar-offset` inner offset (`2px`)
121
- - `--re-data-grid-scrollbar-thumb-size` thumb size (`8px`)
122
- - `--re-data-grid-scrollbar-thumb-color` thumb color (`rgba(0,0,0,0.35)`)
123
- - `--re-data-grid-scrollbar-thumb-rounded` thumb radius (`4px`)
393
+
394
+ - `--re-data-grid-scrollbar-size` - track size (`4px`)
395
+ - `--re-data-grid-scrollbar-offset` - inner offset (`2px`)
396
+ - `--re-data-grid-scrollbar-track-rounded` - track radius (`0.25rem`)
397
+ - `--re-data-grid-scrollbar-track-surface` - track surface (`transparent`)
398
+ - `--re-data-grid-scrollbar-thumb-size` - thumb size (`8px`)
399
+ - `--re-data-grid-scrollbar-thumb-color` - thumb color (`rgba(0,0,0,0.25)`)
400
+ - `--re-data-grid-scrollbar-thumb-active-color` - thumb active color (`rgba(0,0,0,0.45)`)
401
+ - `--re-data-grid-scrollbar-thumb-rounded` - thumb radius (`var(--re-data-grid-scrollbar-track-rounded)`)
124
402
 
125
403
  Header:
126
- - `--re-data-grid-header-height` — header row height (`40px`)
127
- - `--re-data-grid-header-separator-color` separator color (`#ccc`)
128
- - `--re-data-grid-header-separator` separator line (`1px solid var(--re-data-grid-header-separator-color)`)
129
- - `--re-data-grid-header-surface` header background (`#fff`)
404
+
405
+ - `--re-data-grid-header-surface` - header area background (`#fff`)
406
+ - `--re-data-grid-header-row-height` - main header row height (`40px`)
407
+ - `--re-data-grid-header-row-separator-color` - main header separator color (`#ccc`)
408
+ - `--re-data-grid-header-row-separator` - main header separator (`1px solid var(--re-data-grid-header-row-separator-color)`)
409
+ - `--re-data-grid-header-group-row-height` - group header row height (`var(--re-data-grid-header-row-height)`)
410
+ - `--re-data-grid-header-group-row-separator-color` - group header separator color (`var(--re-data-grid-header-row-separator-color)`)
411
+ - `--re-data-grid-header-group-row-separator` - group header separator (`1px solid var(--re-data-grid-header-group-row-separator-color)`)
130
412
 
131
413
  Header Cells:
132
414
 
133
- - `--re-data-grid-header-cell-font-weight` font weight (`600`)
134
- - `--re-data-grid-header-cell-font-size` font size (`0.8rem`)
135
- - `--re-data-grid-header-cell-color` text color (`#000`)
136
- - `--re-data-grid-header-cell-surface` cell background (`#fafafa`)
415
+ - `--re-data-grid-header-cell-font-weight` - font weight (`600`)
416
+ - `--re-data-grid-header-cell-font-size` - font size (`0.8rem`)
417
+ - `--re-data-grid-header-cell-color` - text color (`#000`)
418
+ - `--re-data-grid-header-cell-surface` - cell background (`#fafafa`)
419
+ - `--re-data-grid-header-group-cell-font-weight` - group font weight (`var(--re-data-grid-header-cell-font-weight)`)
420
+ - `--re-data-grid-header-group-cell-font-size` - group font size (`var(--re-data-grid-header-cell-font-size)`)
421
+ - `--re-data-grid-header-group-cell-color` - group text color (`var(--re-data-grid-header-cell-color)`)
422
+ - `--re-data-grid-header-group-cell-surface` - group cell background (`var(--re-data-grid-header-cell-surface)`)
137
423
 
138
424
  Footer:
139
425
 
140
- - `--re-data-grid-footer-separator-color` separator color (`#ccc`)
141
- - `--re-data-grid-footer-separator` separator line (`1px solid var(--re-data-grid-footer-separator-color)`)
142
- - `--re-data-grid-footer-surface` footer background (`#fff`)
426
+ - `--re-data-grid-footer-separator-color` - separator color (`#ccc`)
427
+ - `--re-data-grid-footer-separator` - separator line (`1px solid var(--re-data-grid-footer-separator-color)`)
428
+ - `--re-data-grid-footer-surface` - footer background (`#fff`)
143
429
 
144
430
  Rows:
145
431
 
146
- - `--re-data-grid-row-separator-color` row separator color (`#bbb`)
147
- - `--re-data-grid-row-separator` separator line (`1px solid var(--re-data-grid-row-separator-color)`)
148
- - `--re-data-grid-row-odd-surface` odd rows background (`var(--re-data-grid-cell-surface)`)
432
+ - `--re-data-grid-row-separator-color` - row separator color (`#bbb`)
433
+ - `--re-data-grid-row-separator` - separator line (`1px solid var(--re-data-grid-row-separator-color)`)
434
+ - `--re-data-grid-row-odd-surface` - odd rows background (`var(--re-data-grid-cell-surface)`)
149
435
 
150
436
  Columns:
151
- - `--re-data-grid-column-separator-color` — column divider color (`transparent`)
152
- - `--re-data-grid-column-separator` column divider (`1px solid var(--re-data-grid-column-separator-color)`)
153
- - `--re-data-grid-column-odd-surface` odd column background (`var(--re-data-grid-cell-surface)`)
437
+
438
+ - `--re-data-grid-column-separator-color` - column divider color (`transparent`)
439
+ - `--re-data-grid-column-separator` - column divider (`1px solid var(--re-data-grid-column-separator-color)`)
440
+ - `--re-data-grid-column-odd-surface` - odd column background (`var(--re-data-grid-cell-surface)`)
154
441
 
155
442
  Cells:
156
443
 
157
- - `--re-data-grid-cell-paddings` inner paddings (`0.4rem 0.625rem`)
158
- - `--re-data-grid-cell-font-weight` font weight (`400`)
159
- - `--re-data-grid-cell-font-size` font size (`0.75rem`)
160
- - `--re-data-grid-cell-color` text color (`#000`)
161
- - `--re-data-grid-cell-surface` cell background (`#fff`)
444
+ - `--re-data-grid-cell-paddings` - inner paddings (`0.4rem 0.625rem`)
445
+ - `--re-data-grid-cell-font-weight` - font weight (`400`)
446
+ - `--re-data-grid-cell-font-size` - font size (`0.75rem`)
447
+ - `--re-data-grid-cell-color` - text color (`#000`)
448
+ - `--re-data-grid-cell-surface` - cell background (`#fff`)
162
449
 
163
450
  Sticky Cells:
164
451
 
165
- - `--re-data-grid-sticky-cell-surface` sticky cells background (`#fdfdfd`)
166
- - `--re-data-grid-sticky-cell-left-shadow` left shadow (`2px 0 2px rgba(0, 0, 0, 0.03)`)
167
- - `--re-data-grid-sticky-cell-right-shadow` right shadow (`-2px 0 2px rgba(0, 0, 0, 0.03)`)
452
+ - `--re-data-grid-sticky-header-cell-surface` - sticky header cell background (`#fff`)
453
+ - `--re-data-grid-sticky-cell-surface` - sticky body cell background (`#fdfdfd`)
454
+ - `--re-data-grid-sticky-cell-left-shadow` - left shadow (`2px 0 2px rgba(0,0,0,0.03)`)
455
+ - `--re-data-grid-sticky-cell-right-shadow` - right shadow (`-2px 0 2px rgba(0,0,0,0.03)`)
168
456
 
169
457
  Pinned Sections:
170
458
 
171
- - `--re-data-grid-pinned-surface` pinned sections background (`#fcfcfc`)
172
- - `--re-data-grid-pinned-separator-color` separator color (`#eee`)
173
- - `--re-data-grid-pinned-separator` separator line (`1px solid var(--re-data-grid-pinned-separator-color)`)
459
+ - `--re-data-grid-pinned-surface` - pinned sections background (`#fcfcfc`)
460
+ - `--re-data-grid-pinned-separator-color` - separator color (`#eee`)
461
+ - `--re-data-grid-pinned-separator` - separator line (`1px solid var(--re-data-grid-pinned-separator-color)`)
174
462
 
175
463
  Misc:
176
464
 
177
- - `--re-data-grid-expander-color` expander indicator color (`var(--primary-color, currentColor)`)
178
- - `--re-data-grid-expanded-color` expanded column color (`var(--re-data-grid-cell-color, #000)`) *[NEW in 1.1.0]*
179
- - `--re-data-grid-expanded-surface` expanded column background(`var(--re-data-grid-cell-surface, #fff)`) *[NEW in 1.1.0]*
465
+ - `--re-data-grid-expander-color` - expander indicator color (`var(--primary-color, currentColor)`)
466
+ - `--re-data-grid-expanded-color` - expanded column color (`var(--re-data-grid-cell-color, #000)`)
467
+ - `--re-data-grid-expanded-surface` - expanded column background (`var(--re-data-grid-cell-surface, #fff)`)
180
468
 
181
469
  ---
182
470
 
183
471
  ## Paginator *[NEW in 1.1.0]*
472
+
184
473
  The paginator component is used to display a page selector and total count.
185
474
 
186
475
  ### Inputs
187
476
 
188
- | Parameter | Type | Default | Description |
189
- |----------------|------------|-----------|--------------------------|
190
- | current | `number` | 0 | Current page |
191
- | pageSize | `number` | 0 | |
192
- | totalElements | `number` | 0 | Total number of elements |
193
- | maxShowPages | `number` | 7 | |
477
+ | Parameter | Type | Default | Description |
478
+ |-----------------|------------|-------------------|----------------------------------------|
479
+ | current | `number` | 0 | Current page |
480
+ | pageSize | `number` | 0 | Number of items per page |
481
+ | totalElements | `number` | 0 | Total number of elements |
482
+ | maxShowPages | `number` | 7 | Maximum number of page buttons to show |
483
+ | showFirstLast | `boolean` | `false` | Show "First" and "Last" buttons |
484
+ | showPerPage | `boolean` | `false` | Show page-size dropdown |
485
+ | pageSizeOptions | `number[]` | `[10,20,50,100]` | Options for per-page dropdown |
486
+ | perPageLabel | `string` | `Items per page:` | Label near dropdown |
487
+ | firstLabel | `string` | `First` | Fallback label for first-page button |
488
+ | lastLabel | `string` | `Last` | Fallback label for last-page button |
194
489
 
195
490
  ### Outputs
196
491
 
197
- | Event | Type | Description |
198
- |--------------|-----------|-------------|
199
- | pageChange | `number` | |
492
+ | Event | Type | Description |
493
+ |----------------|----------|----------------------------------------------------------------------|
494
+ | pageChange | `number` | Emitted when the page changes. Returns the new page index (0-based). |
495
+ | pageSizeChange | `number` | Emitted when per-page value changes. |
496
+
497
+ ### Paginator templates
498
+
499
+ You can customize first/last controls with `ng-template`:
500
+
501
+ ```html
502
+
503
+ <re-data-grid-paginator
504
+ showFirstLast
505
+ showPerPage
506
+ [current]="page"
507
+ [pageSize]="20"
508
+ [pageSizeOptions]="[10, 20, 50]"
509
+ [totalElements]="total"
510
+ (pageChange)="page = $event"
511
+ (pageSizeChange)="pageSize = $event"
512
+ >
513
+ <ng-template reDataGridPaginatorFirst let-targetPage let-disabled="disabled">
514
+ <span [style.opacity]="disabled ? 0.5 : 1">&lt;&lt; First</span>
515
+ </ng-template>
516
+
517
+ <ng-template reDataGridPaginatorPage let-label="label" let-active="active">
518
+ <span [style.fontWeight]="active ? 700 : 400">{{ label }}</span>
519
+ </ng-template>
520
+
521
+ <ng-template reDataGridPaginatorLast let-targetPage let-disabled="disabled">
522
+ <span [style.opacity]="disabled ? 0.5 : 1">Last &gt;&gt;</span>
523
+ </ng-template>
524
+ </re-data-grid-paginator>
525
+ ```
526
+
527
+ Template contexts:
528
+
529
+ - `reDataGridPaginatorFirst` / `reDataGridPaginatorLast`: `$implicit` (target page), `current`, `total`, `disabled`
530
+ - `reDataGridPaginatorPage`: `$implicit` / `page` (0-based index), `label` (1-based display), `current`, `total`, `active`
200
531
 
201
532
  ### CSS Variables
202
533
 
534
+ First / Last controls:
535
+
536
+ - `--re-data-grid-paginator-edge-min-width` - min width (`2.5rem`)
537
+ - `--re-data-grid-paginator-edge-height` - control height (`var(--re-data-grid-paginator-page-size)`)
538
+ - `--re-data-grid-paginator-edge-paddings` - control paddings (`0 0.625rem`)
539
+ - `--re-data-grid-paginator-edge-border` - control border (`var(--re-data-grid-paginator-page-border)`)
540
+ - `--re-data-grid-paginator-edge-rounded` - control border radius (`var(--re-data-grid-paginator-page-rounded)`)
541
+ - `--re-data-grid-paginator-edge-surface` - control background (`var(--re-data-grid-paginator-page-surface)`)
542
+ - `--re-data-grid-paginator-edge-color` - control text color (`var(--re-data-grid-paginator-page-color)`)
543
+ - `--re-data-grid-paginator-edge-font-size` - control font size (`var(--re-data-grid-paginator-page-font-size)`)
544
+ - `--re-data-grid-paginator-edge-hover-surface` - hover background (`var(--re-data-grid-paginator-page-hover-surface)`)
545
+ - `--re-data-grid-paginator-edge-hover-color` - hover text color (`var(--re-data-grid-paginator-page-hover-color)`)
546
+ - `--re-data-grid-paginator-edge-disabled-opacity` - disabled opacity (`0.5`)
547
+
203
548
  Layout:
204
- - `--re-data-grid-paginator-gap` — gap (`0.5rem`)
549
+
550
+ - `--re-data-grid-paginator-gap` - gap between paginator elements (`0.5rem`)
205
551
 
206
552
  Page:
207
- - `--re-data-grid-paginator-page-size` — (`1.75rem`)
208
- - `--re-data-grid-paginator-page-border` (`1px solid var(--re-data-grid-paginator-separator-color, #e2e8f0)`)
209
- - `--re-data-grid-paginator-page-separator-color` (`var(--re-data-grid-separator-color, --border-color)`)
210
- - `--re-data-grid-paginator-page-rounded` (`var(--re-data-grid-rounded, --radius-md)`)
211
- - `--re-data-grid-paginator-page-surface` (`var(--re-data-grid-surface, white)`)
212
- - `--re-data-grid-paginator-page-color` (`var(--text-primary, #1e293b)`)
213
- - `--re-data-grid-paginator-page-font-size` (`0.875rem`)
553
+
554
+ - `--re-data-grid-paginator-page-size` - page button size (`1.75rem`)
555
+ - `--re-data-grid-paginator-page-border` - page button border (`1px solid var(--re-data-grid-paginator-separator-color, #e2e8f0)`)
556
+ - `--re-data-grid-paginator-page-separator-color` - page button border color (`var(--re-data-grid-separator-color, --border-color)`)
557
+ - `--re-data-grid-paginator-page-rounded` - page button border radius (`var(--re-data-grid-rounded, --radius-md)`)
558
+ - `--re-data-grid-paginator-page-surface` - page button background (`var(--re-data-grid-surface, white)`)
559
+ - `--re-data-grid-paginator-page-color` - page button text color (`var(--text-primary, #1e293b)`)
560
+ - `--re-data-grid-paginator-page-font-size` - page button font size (`0.875rem`)
214
561
 
215
562
  Active Page:
216
- - `--re-data-grid-paginator-page-active-surface` — (`var(--re-data-grid-active, #3b82f6)`)
217
- - `--re-data-grid-paginator-page-active-color` — (`white`)
218
563
 
219
- Hover Page:
220
- - `--re-data-grid-paginator-page-hover-surface` (`var(--re-data-grid-active, #3b82f6)`)
221
- - `--re-data-grid-paginator-page-hover-color` — (`white`)
564
+ - `--re-data-grid-paginator-page-active-surface` - active page button background (`var(--re-data-grid-active, #3b82f6)`)
565
+ - `--re-data-grid-paginator-page-active-color` - active page button text color (`white`)
566
+
567
+ Hover Page:
568
+
569
+ - `--re-data-grid-paginator-page-hover-surface` - page button hover background (`var(--re-data-grid-active, #3b82f6)`)
570
+ - `--re-data-grid-paginator-page-hover-color` - page button hover text color (`white`)
222
571
 
223
572
  ---
224
573
 
@@ -229,6 +578,7 @@ Variables can be overridden globally (`:root`) or per grid container.
229
578
  ### Quick Theming Examples
230
579
 
231
580
  Dark Theme Example:
581
+
232
582
  ```css
233
583
  .dark .data-grid {
234
584
  --re-data-grid-surface: #121416;
@@ -240,9 +590,10 @@ Dark Theme Example:
240
590
  ```
241
591
 
242
592
  Compact Mode Example:
593
+
243
594
  ```css
244
595
  .compact-grid {
245
- --re-data-grid-header-height: 32px;
596
+ --re-data-grid-header-row-height: 32px;
246
597
  --re-data-grid-cell-font-size: 12px;
247
598
  --re-data-grid-cell-paddings: 2px 8px;
248
599
  }
@@ -251,22 +602,28 @@ Compact Mode Example:
251
602
  ### Custom Template Examples
252
603
 
253
604
  Cell Template by Type
605
+
254
606
  ```html
255
- <ng-template reDataGridTypeCell="link" let-value let-row="row">
607
+
608
+ <ng-template reDataGridTypeCell="link" let-row="row">
256
609
  <a [href]="value" target="_blank">{{ value }}</a>
257
610
  </ng-template>
258
611
  ```
259
612
 
260
613
  Cell Template *[NEW in 1.1.0]*
614
+
261
615
  ```html
262
- <ng-template reDataGridCell="fullName" let-value let-row="row">
616
+
617
+ <ng-template reDataGridCell="fullName" let-row="row">
263
618
  <span><b>{{ row.firstName }}</b> {{ row.lastName }}</span>
264
619
  </ng-template>
265
620
  ```
266
621
 
267
622
  Custom Header Template:
623
+
268
624
  ```html
269
- <ng-template reDataGridHeader key="name" let-value>
625
+
626
+ <ng-template reDataGridHeader="name" let-value="value">
270
627
  <span class="header-with-icon">
271
628
  <icon name="user" size="20" />
272
629
  {{ value }}
@@ -276,11 +633,11 @@ Custom Header Template:
276
633
 
277
634
  ## Compatibility
278
635
 
279
- - Angular: 20+
636
+ - Angular: 18+
280
637
  - Rendering: ESM
281
638
  - Signals: required
282
639
  - Zone.js: optional
283
-
284
640
 
285
641
  ## License
642
+
286
643
  MIT