@nexterajs/ui 0.0.1 → 0.0.2

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.
Files changed (44) hide show
  1. package/fesm2022/nexterajs-ui-core.mjs +189 -5
  2. package/fesm2022/nexterajs-ui-core.mjs.map +1 -1
  3. package/fesm2022/nexterajs-ui-dropdowns.mjs +314 -50
  4. package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -1
  5. package/fesm2022/nexterajs-ui-file-uploader.mjs +6 -3
  6. package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -1
  7. package/fesm2022/nexterajs-ui-forms.mjs +729 -370
  8. package/fesm2022/nexterajs-ui-forms.mjs.map +1 -1
  9. package/fesm2022/nexterajs-ui-grid.mjs +1188 -208
  10. package/fesm2022/nexterajs-ui-grid.mjs.map +1 -1
  11. package/fesm2022/nexterajs-ui-intl.mjs +15 -5
  12. package/fesm2022/nexterajs-ui-intl.mjs.map +1 -1
  13. package/fesm2022/nexterajs-ui-notify.mjs +351 -0
  14. package/fesm2022/nexterajs-ui-notify.mjs.map +1 -0
  15. package/fesm2022/nexterajs-ui-pivot.mjs +146 -116
  16. package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -1
  17. package/fesm2022/nexterajs-ui-styling.mjs +568 -3
  18. package/fesm2022/nexterajs-ui-styling.mjs.map +1 -1
  19. package/fesm2022/nexterajs-ui-tree-view.mjs +147 -18
  20. package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -1
  21. package/fesm2022/nexterajs-ui-validation.mjs +247 -92
  22. package/fesm2022/nexterajs-ui-validation.mjs.map +1 -1
  23. package/fesm2022/nexterajs-ui.mjs +1 -0
  24. package/fesm2022/nexterajs-ui.mjs.map +1 -1
  25. package/package.json +5 -1
  26. package/styles/_dropdown.scss +149 -0
  27. package/styles/_editor.scss +23 -4
  28. package/styles/_icons.scss +92 -0
  29. package/styles/_mixins.scss +104 -1
  30. package/styles/_scrollbar.scss +134 -0
  31. package/styles/_toast.scss +243 -0
  32. package/styles/_tokens.scss +48 -0
  33. package/styles/index.scss +4 -0
  34. package/types/nexterajs-ui-core.d.ts +142 -22
  35. package/types/nexterajs-ui-dropdowns.d.ts +145 -27
  36. package/types/nexterajs-ui-file-uploader.d.ts +4 -1
  37. package/types/nexterajs-ui-forms.d.ts +103 -19
  38. package/types/nexterajs-ui-grid.d.ts +445 -57
  39. package/types/nexterajs-ui-notify.d.ts +147 -0
  40. package/types/nexterajs-ui-pivot.d.ts +35 -22
  41. package/types/nexterajs-ui-styling.d.ts +163 -3
  42. package/types/nexterajs-ui-tree-view.d.ts +50 -1
  43. package/types/nexterajs-ui-validation.d.ts +94 -15
  44. package/types/nexterajs-ui.d.ts +1 -0
@@ -1,12 +1,12 @@
1
- import { formatNumber, parseNumberFormat, parseDateValue, formatDisplay } from '@nexterajs/ui/intl';
1
+ import { NXE_EN_US_SEPARATORS, formatNumber, parseNumberFormat, parseDateValue, formatDisplay, NXE_NUMBER_SEPARATORS } from '@nexterajs/ui/intl';
2
2
  import * as i0 from '@angular/core';
3
- import { input, contentChildren, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, signal, output, Directive, model, inject, ElementRef, ViewContainerRef, NgZone, contentChild, EventEmitter, afterRenderEffect, untracked, afterNextRender, linkedSignal, viewChild, Output } from '@angular/core';
3
+ import { input, ViewEncapsulation, ChangeDetectionStrategy, Component, contentChildren, contentChild, computed, signal, output, viewChild, Directive, model, inject, ElementRef, ViewContainerRef, NgZone, DestroyRef, EventEmitter, linkedSignal, afterRenderEffect, untracked, afterNextRender, Output } from '@angular/core';
4
4
  import { NgTemplateOutlet } from '@angular/common';
5
5
  import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
6
6
  import { nxePane } from '@nexterajs/ui/styling';
7
7
  import { Workbook, nxeSaveAs } from '@nexterajs/ui/excel';
8
8
  import { Overlay } from '@angular/cdk/overlay';
9
- import { NXE_DEFAULT_OPTIONS, NxeTemplateDirective } from '@nexterajs/ui/core';
9
+ import { NXE_DEFAULT_OPTIONS, NxeTemplateDirective, nxeResolveExpr, nxeResolveText } from '@nexterajs/ui/core';
10
10
 
11
11
  /* eslint-disable @typescript-eslint/no-explicit-any */
12
12
  // Shared value-formatting helpers for the nxe grid family — the single source of truth for how
@@ -55,21 +55,22 @@ function nxeNumberMask(format) {
55
55
  * `{ formatter }`). No format / an unresolvable one → `String(value)` (the grid's untyped
56
56
  * rendering).
57
57
  */
58
- function nxeFormatNumberValue(value, format) {
58
+ function nxeFormatNumberValue(value, format, separators = NXE_EN_US_SEPARATORS) {
59
59
  if (format && typeof format === 'object') {
60
60
  if (typeof format.formatter === 'function') {
61
61
  return String(format.formatter(value));
62
62
  }
63
63
  if (format.type === 'percent') {
64
64
  const mask = '#,##0' + precisionDecimals(format.precision) + '%';
65
- return formatNumber(value * 100, parseNumberFormat(mask));
65
+ return formatNumber(value * 100, parseNumberFormat(mask), separators);
66
66
  }
67
67
  }
68
68
  const mask = nxeNumberMask(format);
69
- return mask ? formatNumber(value, parseNumberFormat(mask)) : String(value);
69
+ return mask ? formatNumber(value, parseNumberFormat(mask), separators) : String(value);
70
70
  }
71
71
  /**
72
- * Derive the Excel `numFmt` mask for a `format`: string masks pass through, fixedPoint/currency
72
+ * Derive the Excel `numFmt` mask for a `format`: string masks go through
73
+ * {@link nxeExcelNumberMask} (the same conversion the grid export uses), fixedPoint/currency
73
74
  * come from {@link nxeNumberMask}, percent appends a bare `%` (Excel multiplies by 100 itself),
74
75
  * and any other `{ type }` is a date pattern used as-is. `{ formatter }`-only objects →
75
76
  * `undefined` (callers fall back to exporting the formatted text).
@@ -79,7 +80,7 @@ function nxeExcelNumFmt(format) {
79
80
  return undefined;
80
81
  }
81
82
  if (typeof format === 'string') {
82
- return format;
83
+ return nxeExcelNumberMask(format);
83
84
  }
84
85
  if (typeof format.formatter === 'function') {
85
86
  return undefined;
@@ -89,20 +90,120 @@ function nxeExcelNumFmt(format) {
89
90
  }
90
91
  return nxeNumberMask(format) ?? format.type ?? undefined;
91
92
  }
93
+ /** Excel's digit placeholders. */
94
+ const NXE_DIGIT_PLACEHOLDERS = new Set(['0', '#', '?']);
95
+ /**
96
+ * Excel `numFmt` for a dx LDML string mask.
97
+ *
98
+ * The two languages disagree on one character. In Excel an unquoted comma after the last digit
99
+ * placeholder SCALES the value by 1/1000, while dx's LDML engine ignores it (see the golden
100
+ * transcript in `@nexterajs/ui/intl`). The consumer percent mask `#,##0.00,'%'` therefore
101
+ * exported 100 as "0.10%". Dropped here, per `;` section: every unquoted comma after the
102
+ * section's decimal point, and a comma that follows a digit placeholder with no placeholder
103
+ * after it before the section's decimal point or end (`#,##0,'%'`, `#,##0,.00`) — none of them
104
+ * ever meant anything to dx. Grouping commas between integer placeholders stay, and
105
+ * single-quoted literals become Excel's double-quoted ones.
106
+ */
107
+ function nxeExcelNumberMask(mask) {
108
+ const chars = Array.from(mask);
109
+ // Whether each character is part of a single-quoted literal (its quotes included).
110
+ const quoted = [];
111
+ let inQuote = false;
112
+ for (const ch of chars) {
113
+ if (ch === "'") {
114
+ inQuote = !inQuote;
115
+ quoted.push(true);
116
+ }
117
+ else {
118
+ quoted.push(inQuote);
119
+ }
120
+ }
121
+ /** A digit placeholder follows the comma at `i` in its section's integer part: it groups. */
122
+ const groups = (i) => {
123
+ for (let j = i + 1; j < chars.length; j++) {
124
+ if (quoted[j]) {
125
+ continue;
126
+ }
127
+ if (chars[j] === '.' || chars[j] === ';') {
128
+ return false;
129
+ }
130
+ if (NXE_DIGIT_PLACEHOLDERS.has(chars[j])) {
131
+ return true;
132
+ }
133
+ }
134
+ return false;
135
+ };
136
+ let out = '';
137
+ let afterPoint = false;
138
+ // The last unquoted character that was not a comma, within the current section.
139
+ let prev = '';
140
+ for (let i = 0; i < chars.length; i++) {
141
+ const ch = chars[i];
142
+ if (ch === "'") {
143
+ out += '"';
144
+ continue;
145
+ }
146
+ if (!quoted[i]) {
147
+ if (ch === ';') {
148
+ afterPoint = false;
149
+ prev = '';
150
+ out += ch;
151
+ continue;
152
+ }
153
+ if (ch === '.') {
154
+ afterPoint = true;
155
+ }
156
+ if (ch === ',') {
157
+ if (afterPoint || (NXE_DIGIT_PLACEHOLDERS.has(prev) && !groups(i))) {
158
+ continue;
159
+ }
160
+ }
161
+ else {
162
+ prev = ch;
163
+ }
164
+ }
165
+ out += ch;
166
+ }
167
+ return out;
168
+ }
169
+ /**
170
+ * dx `getValueDataType` (m_columns_controller_utils.js:230-236): the type a value contributes
171
+ * to column type inference. `null`, arrays and functions contribute nothing.
172
+ */
173
+ function nxeValueDataType(v) {
174
+ if (v == null || Array.isArray(v)) {
175
+ return undefined;
176
+ }
177
+ if (v instanceof Date) {
178
+ return 'date';
179
+ }
180
+ const t = typeof v;
181
+ return t === 'string' || t === 'number' || t === 'boolean' || t === 'object' ? t : undefined;
182
+ }
183
+ /**
184
+ * dx's number-column deserializer (m_columns_controller_utils.js:263 `updateSerializers`):
185
+ * `parseFloat`, so "20.5" is 20.5 and "1,234" is 1 exactly as dx shows it. `NaN` when the
186
+ * value is not numeric at all, which callers render raw.
187
+ */
188
+ function nxeNumberValue(raw) {
189
+ return typeof raw === 'number' ? raw : parseFloat(String(raw));
190
+ }
92
191
  /**
93
192
  * The data grid's default cell rendering (pure extraction of nxe-data-grid's `defaultFormat`):
94
- * `dataType`/`format` → display text. Number cells honour string masks only — exactly the
95
- * grid's pre-extraction behaviour.
193
+ * `dataType`/`format` → display text.
194
+ *
195
+ * dx's format_helper (format_helper.js:23-43) formats by the VALUE, not by the column type: any
196
+ * JS number meets any valid `format`, whatever the column declared. So a number in an untyped
197
+ * or string column still takes its mask, and a number column honours object formats as well.
96
198
  */
97
- function nxeDefaultFormat(dataType, format, raw) {
199
+ function nxeDefaultFormat(dataType, format, raw, separators = NXE_EN_US_SEPARATORS) {
98
200
  if (raw == null || raw === '') {
99
201
  return '';
100
202
  }
101
203
  switch (dataType) {
102
204
  case 'number': {
103
- const pattern = typeof format === 'string' ? format : undefined;
104
- const num = typeof raw === 'number' ? raw : Number(raw);
105
- return isNaN(num) ? String(raw) : formatNumber(num, parseNumberFormat(pattern));
205
+ const num = nxeNumberValue(raw);
206
+ return isNaN(num) ? String(raw) : nxeFormatNumberValue(num, format, separators);
106
207
  }
107
208
  case 'date': {
108
209
  const dt = parseDateValue(raw);
@@ -111,43 +212,53 @@ function nxeDefaultFormat(dataType, format, raw) {
111
212
  case 'boolean':
112
213
  return raw ? '✓' : '';
113
214
  default:
114
- return String(raw);
215
+ // No format (or an unresolvable one) leaves the number as String(value), as dx does.
216
+ return typeof raw === 'number' ? nxeFormatNumberValue(raw, format, separators) : String(raw);
115
217
  }
116
218
  }
117
219
 
118
220
  /**
119
221
  * DOM lookups shared by the grid component and its drag/resize controllers.
120
222
  *
121
- * Both questions here used to be answered with `querySelector('.dx-header-row')`, which stopped
223
+ * Both questions here used to be answered with `querySelector('.nxe-header-row')`, which stopped
122
224
  * being right once bands could add header rows above the leaves.
123
225
  */
124
226
  /**
125
- * The all-leaves header row of THIS grid.
227
+ * The bottom `.nxe-header-row` of THIS grid.
126
228
  *
127
- * Two things have to be true at once. It must be the LAST `.dx-header-row` in the thead, because
128
- * bands sit above the leaves and only the bottom row has one cell per data column. And it must
129
- * belong to this grid rather than a nested master-detail grid — those live in the tbody, so the
130
- * first `thead` in document order is always the outer grid's own.
229
+ * Used for header-strip geometry — the drop highlight and the drag indicator's vertical extent.
230
+ * It must belong to this grid rather than a nested master-detail grid; those live in the tbody,
231
+ * so the first `thead` in document order is always the outer grid's own.
232
+ *
233
+ * It is NOT one cell per column — see {@link nxeHeaderLeafCells}.
131
234
  */
132
235
  function nxeLeafHeaderRow(host) {
133
- const thead = host.querySelector('.dx-datagrid-content > .dx-datagrid-table > thead');
134
- const rows = thead?.querySelectorAll('.dx-header-row');
236
+ const thead = host.querySelector('.nxe-datagrid-content > .nxe-datagrid-table > thead');
237
+ const rows = thead?.querySelectorAll('.nxe-header-row');
135
238
  return rows && rows.length ? rows[rows.length - 1] : null;
136
239
  }
137
240
  /**
138
- * The data cells of a header row, command columns removed.
241
+ * The header cell of each visible data column, in visible-column order.
242
+ *
243
+ * Deliberately not "the cells of the bottom header row". A top-level column in a banded grid is
244
+ * emitted in the FIRST header row with a rowspan reaching the bottom, so the bottom row holds
245
+ * only the leaves that sit under a band: `Name | Shared(Gross, Comm) | Total` puts Name and Total
246
+ * in row 0 and just Gross and Comm in row 1. Reading the bottom row loses half the columns, and
247
+ * everything that indexes these by visible position — auto-width, resize, reorder, keyboard
248
+ * focus — then addresses the wrong column.
139
249
  *
140
- * Indexing these by visible-column index is deliberate: command cells live in the FIRST header
141
- * row and span downwards, so a banded grid's leaf row has none, and any fixed lead offset would
142
- * be off by however many command columns the grid happens to show.
250
+ * `role="columnheader"` plus `aria-colindex` is the discriminator: every leaf header cell carries
251
+ * both, a band is a column header without a number, and a command cell (group indent, detail
252
+ * expander, select-all, edit) is numbered, as dx numbers it, but is no column header
253
+ * (m_column_headers.js:193). The number already accounts for the lead command columns, so sorting
254
+ * on it recovers visible order across however many rows the header happens to have.
143
255
  */
144
- function nxeHeaderDataCells(row) {
145
- if (!row) {
256
+ function nxeHeaderLeafCells(host) {
257
+ const thead = host.querySelector('.nxe-datagrid-content > .nxe-datagrid-table > thead');
258
+ if (!thead) {
146
259
  return [];
147
260
  }
148
- return Array.from(row.cells).filter((td) => !td.classList.contains('dx-command-expand') &&
149
- !td.classList.contains('dx-command-select') &&
150
- !td.classList.contains('dx-command-edit'));
261
+ return Array.from(thead.querySelectorAll('.nxe-header-row > [role="columnheader"][aria-colindex]')).sort((a, b) => Number(a.getAttribute('aria-colindex')) - Number(b.getAttribute('aria-colindex')));
151
262
  }
152
263
 
153
264
  /**
@@ -156,16 +267,16 @@ function nxeHeaderDataCells(row) {
156
267
  *
157
268
  * Deliberately NOT CDK DragDrop: the drop topologies are "header → group panel", "chip →
158
269
  * anywhere outside = ungroup/remove", "chip ↔ chip = reorder" — not list-to-list — and the
159
- * ghosts must be dx's exact clones (`.dx-datagrid-drag-header` for the grid,
160
- * `.dx-pivotgrid-fields-container.dx-drag` for the pivot panel). Document-level listeners run
270
+ * ghosts must be dx's exact clones (`.nxe-datagrid-drag-header` for the grid,
271
+ * `.nxe-pivotgrid-fields-container.nxe-drag` for the pivot panel). Document-level listeners run
161
272
  * outside the zone; Angular re-enters only on drop. No `setPointerCapture` (throws for
162
273
  * synthetic pointerIds under Karma).
163
274
  *
164
275
  * dx baseline facts encoded here:
165
- * - grid ghost: `div.dx-datagrid-drag-header.dx-datagrid-text-content.dx-widget.dx-datagrid-drag-action`
276
+ * - grid ghost: `div.nxe-datagrid-drag-header.nxe-datagrid-text-content.nxe-widget.nxe-datagrid-drag-action`
166
277
  * appended to <body>, caption text only, positioned near the cursor;
167
- * - insertion indicator: `div.dx-block-separator` placed between panel chips;
168
- * - chip drag highlights the header row with `dx-datagrid-drop-highlight`;
278
+ * - insertion indicator: `div.nxe-block-separator` placed between panel chips;
279
+ * - chip drag highlights the header row with `nxe-datagrid-drop-highlight`;
169
280
  * - the source cell/chip stays in place, dimmed;
170
281
  * - header→panel drop APPENDS as the last group level (indicator after the last chip).
171
282
  */
@@ -357,7 +468,7 @@ class NxeGridDragController {
357
468
  createGhost: () => {
358
469
  const ghost = document.createElement('div');
359
470
  ghost.className =
360
- 'dx-datagrid-drag-header dx-datagrid-text-content dx-widget dx-datagrid-drag-action nxe-grid-drag-ghost';
471
+ 'nxe-datagrid-drag-header nxe-datagrid-text-content nxe-widget nxe-datagrid-drag-action nxe-grid-drag-ghost';
361
472
  const col = this.drag?.col;
362
473
  ghost.textContent = col ? col.caption() || col.dataField() : '';
363
474
  ghost.style.whiteSpace = 'nowrap';
@@ -367,7 +478,7 @@ class NxeGridDragController {
367
478
  dragStarted: () => {
368
479
  if (this.drag?.kind === 'chip') {
369
480
  // dx: while a chip is dragged, the header row is the highlighted return target.
370
- this.headerRow()?.classList.add('dx-datagrid-drop-highlight');
481
+ this.headerRow()?.classList.add('nxe-datagrid-drop-highlight');
371
482
  }
372
483
  },
373
484
  dragMove: (x, y) => {
@@ -381,7 +492,7 @@ class NxeGridDragController {
381
492
  this.removeSeparator(this.drag);
382
493
  this.removeColIndicator(this.drag);
383
494
  }
384
- this.headerRow()?.classList.remove('dx-datagrid-drop-highlight');
495
+ this.headerRow()?.classList.remove('nxe-datagrid-drop-highlight');
385
496
  },
386
497
  reset: () => {
387
498
  this.drag = null;
@@ -430,7 +541,7 @@ class NxeGridDragController {
430
541
  }
431
542
  this.removeColIndicator(a);
432
543
  a.headerSlot = null;
433
- const chips = Array.from(panel.querySelectorAll('.dx-group-panel-item'));
544
+ const chips = Array.from(panel.querySelectorAll('.nxe-group-panel-item'));
434
545
  if (a.kind === 'header') {
435
546
  // dx appends on header→panel drop; the indicator sits after the last chip.
436
547
  a.dropIndex = chips.length;
@@ -518,7 +629,7 @@ class NxeGridDragController {
518
629
  placeSeparator(a, panel, chips, index) {
519
630
  if (!a.separator) {
520
631
  a.separator = document.createElement('div');
521
- a.separator.className = 'dx-block-separator';
632
+ a.separator.className = 'nxe-block-separator';
522
633
  a.separator.textContent = ' '; // dx renders the separator as an &nbsp; block
523
634
  }
524
635
  const reference = chips[index] ?? null;
@@ -531,7 +642,7 @@ class NxeGridDragController {
531
642
  a.separator = null;
532
643
  }
533
644
  panel() {
534
- return this.hooks.host().querySelector('.dx-datagrid-group-panel');
645
+ return this.hooks.host().querySelector('.nxe-datagrid-group-panel');
535
646
  }
536
647
  headerRow() {
537
648
  return nxeLeafHeaderRow(this.hooks.host());
@@ -542,6 +653,52 @@ class NxeGridDragController {
542
653
  // Shared types for the nxe-data-grid family. Row data is `any` to mirror DevExtreme's untyped
543
654
  // grid (the app stores plain JS records and reads arbitrary fields off `key.data`).
544
655
 
656
+ /**
657
+ * `nxe-header-filter` — drop-in replacement for DevExtreme `dxo-header-filter` (grid child option).
658
+ *
659
+ * Adds a funnel icon to each filterable column header; clicking it opens a popup of the column's
660
+ * distinct values (checkbox list + optional search) to filter the grid. The app uses essentially
661
+ * one shape: `[search]="{ enabled: true }" [visible]="true"` (60 files). Legacy `[allowSearch]`
662
+ * (1×) folds into `search.enabled`.
663
+ */
664
+ class NxeHeaderFilterComponent {
665
+ visible = input(true, /* @ts-ignore */
666
+ ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
667
+ search = input(undefined, /* @ts-ignore */
668
+ ...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
669
+ /** Legacy alias for `search.enabled` (one holdout). */
670
+ allowSearch = input(undefined, /* @ts-ignore */
671
+ ...(ngDevMode ? [{ debugName: "allowSearch" }] : /* istanbul ignore next */ []));
672
+ /**
673
+ * dx `headerFilter.dataSource` — a fixed `{ text, value }` list shown instead of the values
674
+ * derived from the rows.
675
+ */
676
+ dataSource = input(undefined, /* @ts-ignore */
677
+ ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
678
+ /** Whether the in-popup search box shows.
679
+ *
680
+ * Defaults to **true**, matching the legacy DevExtreme deployment: a popup listing every
681
+ * distinct value in a column is close to unusable without one. 60 of the app's templates
682
+ * already opt in explicitly, so this only changes the handful that never said either way —
683
+ * and an explicit `[search]="{ enabled: false }"` still turns it off. */
684
+ searchEnabled() {
685
+ return this.search()?.enabled ?? this.allowSearch() ?? true;
686
+ }
687
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeHeaderFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
688
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type: NxeHeaderFilterComponent, isStandalone: true, selector: "nxe-header-filter", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, allowSearch: { classPropertyName: "allowSearch", publicName: "allowSearch", isSignal: true, isRequired: false, transformFunction: null }, dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display: none" }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
689
+ }
690
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeHeaderFilterComponent, decorators: [{
691
+ type: Component,
692
+ args: [{
693
+ selector: 'nxe-header-filter',
694
+ standalone: true,
695
+ template: '',
696
+ host: { style: 'display: none' },
697
+ changeDetection: ChangeDetectionStrategy.OnPush,
698
+ encapsulation: ViewEncapsulation.None,
699
+ }]
700
+ }], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], search: [{ type: i0.Input, args: [{ isSignal: true, alias: "search", required: false }] }], allowSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowSearch", required: false }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }] } });
701
+
545
702
  /* eslint-disable @typescript-eslint/no-explicit-any */
546
703
  /**
547
704
  * `nxe-column` — drop-in replacement for DevExtreme `dxi-column`.
@@ -557,7 +714,11 @@ class NxeColumnComponent {
557
714
  /** Bound row field (supports dotted paths and bound/ternary expressions). */
558
715
  dataField = input('', /* @ts-ignore */
559
716
  ...(ngDevMode ? [{ debugName: "dataField" }] : /* istanbul ignore next */ []));
560
- dataType = input('string', /* @ts-ignore */
717
+ /**
718
+ * Unset = dx: inferred from the loaded data (m_columns_controller.js:914-975), so a numeric
719
+ * column nobody typed still right-aligns and honours its `format`. Declaring it wins.
720
+ */
721
+ dataType = input(undefined, /* @ts-ignore */
561
722
  ...(ngDevMode ? [{ debugName: "dataType" }] : /* istanbul ignore next */ []));
562
723
  /** px width — number (`[width]="150"`) or numeric string (`width="200"`). */
563
724
  width = input(undefined, /* @ts-ignore */
@@ -643,11 +804,17 @@ class NxeColumnComponent {
643
804
  * its children, and flattening them here would collapse the very nesting the band expresses.
644
805
  */
645
806
  childColumns = contentChildren(NxeColumnComponent, { ...(ngDevMode ? { debugName: "childColumns" } : /* istanbul ignore next */ {}), descendants: false });
807
+ /**
808
+ * A `<nxe-header-filter>` declared inside this column, dx's per-column `headerFilter`
809
+ * config. Only its `dataSource` is read here; visibility and search stay grid-level.
810
+ */
811
+ headerFilterConfig = contentChild(NxeHeaderFilterComponent, /* @ts-ignore */
812
+ ...(ngDevMode ? [{ debugName: "headerFilterConfig" }] : /* istanbul ignore next */ []));
646
813
  /** dx `isBand`: a column that groups other columns under a shared header instead of a field. */
647
814
  isBand = computed(() => this.childColumns().length > 0, /* @ts-ignore */
648
815
  ...(ngDevMode ? [{ debugName: "isBand" }] : /* istanbul ignore next */ []));
649
816
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
650
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.5", type: NxeColumnComponent, isStandalone: true, selector: "nxe-column", inputs: { caption: { classPropertyName: "caption", publicName: "caption", isSignal: true, isRequired: false, transformFunction: null }, dataField: { classPropertyName: "dataField", publicName: "dataField", isSignal: true, isRequired: false, transformFunction: null }, dataType: { classPropertyName: "dataType", publicName: "dataType", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, alignment: { classPropertyName: "alignment", publicName: "alignment", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, customizeText: { classPropertyName: "customizeText", publicName: "customizeText", isSignal: true, isRequired: false, transformFunction: null }, cellTemplate: { classPropertyName: "cellTemplate", publicName: "cellTemplate", isSignal: true, isRequired: false, transformFunction: null }, lookup: { classPropertyName: "lookup", publicName: "lookup", isSignal: true, isRequired: false, transformFunction: null }, allowSorting: { classPropertyName: "allowSorting", publicName: "allowSorting", isSignal: true, isRequired: false, transformFunction: null }, allowGrouping: { classPropertyName: "allowGrouping", publicName: "allowGrouping", isSignal: true, isRequired: false, transformFunction: null }, allowResizing: { classPropertyName: "allowResizing", publicName: "allowResizing", isSignal: true, isRequired: false, transformFunction: null }, allowFiltering: { classPropertyName: "allowFiltering", publicName: "allowFiltering", isSignal: true, isRequired: false, transformFunction: null }, allowEditing: { classPropertyName: "allowEditing", publicName: "allowEditing", isSignal: true, isRequired: false, transformFunction: null }, allowExporting: { classPropertyName: "allowExporting", publicName: "allowExporting", isSignal: true, isRequired: false, transformFunction: null }, allowSearch: { classPropertyName: "allowSearch", publicName: "allowSearch", isSignal: true, isRequired: false, transformFunction: null }, allowHeaderFiltering: { classPropertyName: "allowHeaderFiltering", publicName: "allowHeaderFiltering", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, sortOrder: { classPropertyName: "sortOrder", publicName: "sortOrder", isSignal: true, isRequired: false, transformFunction: null }, sortIndex: { classPropertyName: "sortIndex", publicName: "sortIndex", isSignal: true, isRequired: false, transformFunction: null }, groupIndex: { classPropertyName: "groupIndex", publicName: "groupIndex", isSignal: true, isRequired: false, transformFunction: null }, fixed: { classPropertyName: "fixed", publicName: "fixed", isSignal: true, isRequired: false, transformFunction: null }, filterValues: { classPropertyName: "filterValues", publicName: "filterValues", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, cssClass: { classPropertyName: "cssClass", publicName: "cssClass", isSignal: true, isRequired: false, transformFunction: null }, editCellTemplate: { classPropertyName: "editCellTemplate", publicName: "editCellTemplate", isSignal: true, isRequired: false, transformFunction: null }, groupCellTemplate: { classPropertyName: "groupCellTemplate", publicName: "groupCellTemplate", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display: none" }, queries: [{ propertyName: "childColumns", predicate: NxeColumnComponent, isSignal: true }], ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
817
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.5", type: NxeColumnComponent, isStandalone: true, selector: "nxe-column", inputs: { caption: { classPropertyName: "caption", publicName: "caption", isSignal: true, isRequired: false, transformFunction: null }, dataField: { classPropertyName: "dataField", publicName: "dataField", isSignal: true, isRequired: false, transformFunction: null }, dataType: { classPropertyName: "dataType", publicName: "dataType", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, alignment: { classPropertyName: "alignment", publicName: "alignment", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, customizeText: { classPropertyName: "customizeText", publicName: "customizeText", isSignal: true, isRequired: false, transformFunction: null }, cellTemplate: { classPropertyName: "cellTemplate", publicName: "cellTemplate", isSignal: true, isRequired: false, transformFunction: null }, lookup: { classPropertyName: "lookup", publicName: "lookup", isSignal: true, isRequired: false, transformFunction: null }, allowSorting: { classPropertyName: "allowSorting", publicName: "allowSorting", isSignal: true, isRequired: false, transformFunction: null }, allowGrouping: { classPropertyName: "allowGrouping", publicName: "allowGrouping", isSignal: true, isRequired: false, transformFunction: null }, allowResizing: { classPropertyName: "allowResizing", publicName: "allowResizing", isSignal: true, isRequired: false, transformFunction: null }, allowFiltering: { classPropertyName: "allowFiltering", publicName: "allowFiltering", isSignal: true, isRequired: false, transformFunction: null }, allowEditing: { classPropertyName: "allowEditing", publicName: "allowEditing", isSignal: true, isRequired: false, transformFunction: null }, allowExporting: { classPropertyName: "allowExporting", publicName: "allowExporting", isSignal: true, isRequired: false, transformFunction: null }, allowSearch: { classPropertyName: "allowSearch", publicName: "allowSearch", isSignal: true, isRequired: false, transformFunction: null }, allowHeaderFiltering: { classPropertyName: "allowHeaderFiltering", publicName: "allowHeaderFiltering", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, sortOrder: { classPropertyName: "sortOrder", publicName: "sortOrder", isSignal: true, isRequired: false, transformFunction: null }, sortIndex: { classPropertyName: "sortIndex", publicName: "sortIndex", isSignal: true, isRequired: false, transformFunction: null }, groupIndex: { classPropertyName: "groupIndex", publicName: "groupIndex", isSignal: true, isRequired: false, transformFunction: null }, fixed: { classPropertyName: "fixed", publicName: "fixed", isSignal: true, isRequired: false, transformFunction: null }, filterValues: { classPropertyName: "filterValues", publicName: "filterValues", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, cssClass: { classPropertyName: "cssClass", publicName: "cssClass", isSignal: true, isRequired: false, transformFunction: null }, editCellTemplate: { classPropertyName: "editCellTemplate", publicName: "editCellTemplate", isSignal: true, isRequired: false, transformFunction: null }, groupCellTemplate: { classPropertyName: "groupCellTemplate", publicName: "groupCellTemplate", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display: none" }, queries: [{ propertyName: "childColumns", predicate: NxeColumnComponent, isSignal: true }, { propertyName: "headerFilterConfig", first: true, predicate: NxeHeaderFilterComponent, descendants: true, isSignal: true }], ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
651
818
  }
652
819
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeColumnComponent, decorators: [{
653
820
  type: Component,
@@ -659,7 +826,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
659
826
  changeDetection: ChangeDetectionStrategy.OnPush,
660
827
  encapsulation: ViewEncapsulation.None,
661
828
  }]
662
- }], propDecorators: { caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }], dataField: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataField", required: false }] }], dataType: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataType", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], alignment: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignment", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], customizeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "customizeText", required: false }] }], cellTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellTemplate", required: false }] }], lookup: [{ type: i0.Input, args: [{ isSignal: true, alias: "lookup", required: false }] }], allowSorting: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowSorting", required: false }] }], allowGrouping: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowGrouping", required: false }] }], allowResizing: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowResizing", required: false }] }], allowFiltering: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowFiltering", required: false }] }], allowEditing: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowEditing", required: false }] }], allowExporting: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowExporting", required: false }] }], allowSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowSearch", required: false }] }], allowHeaderFiltering: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowHeaderFiltering", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], sortOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortOrder", required: false }] }], sortIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortIndex", required: false }] }], groupIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupIndex", required: false }] }], fixed: [{ type: i0.Input, args: [{ isSignal: true, alias: "fixed", required: false }] }], filterValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterValues", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], cssClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "cssClass", required: false }] }], editCellTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "editCellTemplate", required: false }] }], groupCellTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupCellTemplate", required: false }] }], childColumns: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NxeColumnComponent), { ...{ descendants: false }, isSignal: true }] }] } });
829
+ }], propDecorators: { caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }], dataField: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataField", required: false }] }], dataType: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataType", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], alignment: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignment", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], customizeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "customizeText", required: false }] }], cellTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellTemplate", required: false }] }], lookup: [{ type: i0.Input, args: [{ isSignal: true, alias: "lookup", required: false }] }], allowSorting: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowSorting", required: false }] }], allowGrouping: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowGrouping", required: false }] }], allowResizing: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowResizing", required: false }] }], allowFiltering: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowFiltering", required: false }] }], allowEditing: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowEditing", required: false }] }], allowExporting: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowExporting", required: false }] }], allowSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowSearch", required: false }] }], allowHeaderFiltering: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowHeaderFiltering", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], sortOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortOrder", required: false }] }], sortIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortIndex", required: false }] }], groupIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupIndex", required: false }] }], fixed: [{ type: i0.Input, args: [{ isSignal: true, alias: "fixed", required: false }] }], filterValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterValues", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], cssClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "cssClass", required: false }] }], editCellTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "editCellTemplate", required: false }] }], groupCellTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupCellTemplate", required: false }] }], childColumns: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NxeColumnComponent), { ...{ descendants: false }, isSignal: true }] }], headerFilterConfig: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeHeaderFilterComponent), { isSignal: true }] }] } });
663
830
 
664
831
  /**
665
832
  * `nxe-selection` — drop-in replacement for DevExtreme `dxo-selection` (grid child option).
@@ -697,46 +864,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
697
864
  }]
698
865
  }], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], selectAllMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllMode", required: false }] }], showCheckBoxesMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCheckBoxesMode", required: false }] }], recursive: [{ type: i0.Input, args: [{ isSignal: true, alias: "recursive", required: false }] }], selectByClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectByClick", required: false }] }] } });
699
866
 
700
- /**
701
- * `nxe-header-filter` — drop-in replacement for DevExtreme `dxo-header-filter` (grid child option).
702
- *
703
- * Adds a funnel icon to each filterable column header; clicking it opens a popup of the column's
704
- * distinct values (checkbox list + optional search) to filter the grid. The app uses essentially
705
- * one shape: `[search]="{ enabled: true }" [visible]="true"` (60 files). Legacy `[allowSearch]`
706
- * (1×) folds into `search.enabled`.
707
- */
708
- class NxeHeaderFilterComponent {
709
- visible = input(true, /* @ts-ignore */
710
- ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
711
- search = input(undefined, /* @ts-ignore */
712
- ...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
713
- /** Legacy alias for `search.enabled` (one holdout). */
714
- allowSearch = input(undefined, /* @ts-ignore */
715
- ...(ngDevMode ? [{ debugName: "allowSearch" }] : /* istanbul ignore next */ []));
716
- /** Whether the in-popup search box shows.
717
- *
718
- * Defaults to **true**, matching the legacy DevExtreme deployment: a popup listing every
719
- * distinct value in a column is close to unusable without one. 60 of the app's templates
720
- * already opt in explicitly, so this only changes the handful that never said either way —
721
- * and an explicit `[search]="{ enabled: false }"` still turns it off. */
722
- searchEnabled() {
723
- return this.search()?.enabled ?? this.allowSearch() ?? true;
724
- }
725
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeHeaderFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
726
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type: NxeHeaderFilterComponent, isStandalone: true, selector: "nxe-header-filter", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, allowSearch: { classPropertyName: "allowSearch", publicName: "allowSearch", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display: none" }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
727
- }
728
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeHeaderFilterComponent, decorators: [{
729
- type: Component,
730
- args: [{
731
- selector: 'nxe-header-filter',
732
- standalone: true,
733
- template: '',
734
- host: { style: 'display: none' },
735
- changeDetection: ChangeDetectionStrategy.OnPush,
736
- encapsulation: ViewEncapsulation.None,
737
- }]
738
- }], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], search: [{ type: i0.Input, args: [{ isSignal: true, alias: "search", required: false }] }], allowSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowSearch", required: false }] }] } });
739
-
740
867
  /* eslint-disable @typescript-eslint/no-explicit-any */
741
868
  /**
742
869
  * Header-filter state model — the draft/apply/search machinery extracted from
@@ -749,6 +876,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
749
876
  * {@link NxeHeaderFilterSource} adapter, so it owns only the frozen semantics:
750
877
  * `(Blanks)` first, Yes/No boolean buckets, the date Year > Month > Day tree, dx search
751
878
  * scoping of (Select All), check-on-open, and "everything checked = no filter" on apply.
879
+ * (Select All)'s indeterminate state ({@link NxeHeaderFilterModel.someChecked}) is derived
880
+ * from the same draft and search scope, and changes none of these rules.
752
881
  */
753
882
  /**
754
883
  * Header-filter key for blank-ish values (`null`/`undefined`/`''`) — dx lists them as one
@@ -798,6 +927,10 @@ class NxeHeaderFilterModel {
798
927
  if (!col) {
799
928
  return [];
800
929
  }
930
+ const fixed = this.source.fixedItems?.(col);
931
+ if (fixed) {
932
+ return fixed;
933
+ }
801
934
  const seen = new Map();
802
935
  for (const row of this.source.rows()) {
803
936
  const raw = this.source.rawValue(col, row);
@@ -979,6 +1112,20 @@ class NxeHeaderFilterModel {
979
1112
  return all.length > 0 && all.every((k) => draft.has(k));
980
1113
  }, /* @ts-ignore */
981
1114
  ...(ngDevMode ? [{ debugName: "allChecked" }] : /* istanbul ignore next */ []));
1115
+ /**
1116
+ * (Select All)'s third state: some visible keys checked, but not all. dx shows it as the
1117
+ * indeterminate square (the select-all dxCheckBox's `value: undefined`). It is never true
1118
+ * together with {@link allChecked}, so check-on-open, which checks everything when no filter
1119
+ * is applied, always opens on a fully checked box and never on a half-checked one.
1120
+ */
1121
+ someChecked = computed(() => {
1122
+ if (this.allChecked()) {
1123
+ return false;
1124
+ }
1125
+ const draft = this.draft();
1126
+ return this.visibleKeys().some((k) => draft.has(k));
1127
+ }, /* @ts-ignore */
1128
+ ...(ngDevMode ? [{ debugName: "someChecked" }] : /* istanbul ignore next */ []));
982
1129
  /** Whether every key of the whole column is checked — decides "no filter" on apply. */
983
1130
  fullChecked = computed(() => {
984
1131
  const all = this.allKeys();
@@ -1033,8 +1180,10 @@ class NxeHeaderFilterModel {
1033
1180
  * The header-filter popup panel — the `.nxe-hf-*` chunk extracted from `nxe-data-grid`'s
1034
1181
  * template (pivot parity Phase 3a) so the pivot can open the same popup on pages with no
1035
1182
  * data grid. Same DOM, same FROZEN classes (`.nxe-hf-item`/`.nxe-hf-search`/… — DEFAULTS.md):
1036
- * the host element carries `dx-header-filter-menu nxe-hf-panel`, exactly where the old
1037
- * template's root `<div>` sat inside the overlay pane.
1183
+ * the host element carries `nxe-header-filter-menu nxe-hf-panel`, exactly where the old
1184
+ * template's root `<div>` sat inside the overlay pane. The dx-parity pass only added to that DOM:
1185
+ * the `input.nxe-hf-search` now sits in a `.nxe-hf-searchbox` with its magnifier and clear
1186
+ * button, and rows also carry `.nxe-hf-list-item` / `.nxe-hf-tree-item`.
1038
1187
  *
1039
1188
  * Renders whatever {@link NxeHeaderFilterModel} it is given — checkbox list or the date
1040
1189
  * Year > Month > Day tree — and emits `applied`/`cancelled` for the host to commit/close;
@@ -1051,16 +1200,24 @@ class NxeHeaderFilterPanelComponent {
1051
1200
  applied = output();
1052
1201
  /** Cancel — the host closes without committing. */
1053
1202
  cancelled = output();
1203
+ searchInput = viewChild('searchInput', /* @ts-ignore */
1204
+ ...(ngDevMode ? [{ debugName: "searchInput" }] : /* istanbul ignore next */ []));
1054
1205
  showSearch() {
1055
1206
  return this.searchEnabled()();
1056
1207
  }
1208
+ /** The search box's clear button (dx search mode forces `showClearButton`): empties the
1209
+ search and keeps the caret in the box, as dx's clear does. */
1210
+ clearSearch() {
1211
+ this.model().search.set('');
1212
+ this.searchInput()?.nativeElement.focus();
1213
+ }
1057
1214
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeHeaderFilterPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1058
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeHeaderFilterPanelComponent, isStandalone: true, selector: "nxe-header-filter-panel", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, searchEnabled: { classPropertyName: "searchEnabled", publicName: "searchEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { applied: "applied", cancelled: "cancelled" }, host: { classAttribute: "dx-header-filter-menu nxe-hf-panel" }, ngImport: i0, template: "@if (showSearch()) {\r\n <input\r\n class=\"nxe-hf-search\"\r\n type=\"text\"\r\n placeholder=\"Search\"\r\n [value]=\"model().search()\"\r\n (input)=\"model().search.set($any($event.target).value)\" />\r\n}\r\n<div class=\"nxe-hf-list\" [class.nxe-hf-tree]=\"model().isDate()\">\r\n <label class=\"nxe-hf-item nxe-hf-selectall\">\r\n <input type=\"checkbox\" [checked]=\"model().allChecked()\" (change)=\"model().toggleAll()\" />\r\n <span>Select All</span>\r\n </label>\r\n @if (model().isDate()) {\r\n <!-- dx date header filter: Year > Month > Day checkbox tree -->\r\n <ng-container\r\n [ngTemplateOutlet]=\"hfTreeNodes\"\r\n [ngTemplateOutletContext]=\"{ $implicit: model().dateTreeFiltered() }\" />\r\n } @else {\r\n @for (item of model().distinctFiltered(); track item.key) {\r\n <label class=\"nxe-hf-item\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"model().draft().has(item.key)\"\r\n (change)=\"model().toggleValue(item.key)\" />\r\n <span>{{ item.text }}</span>\r\n </label>\r\n }\r\n }\r\n</div>\r\n<div class=\"nxe-hf-actions\">\r\n <button type=\"button\" class=\"nxe-hf-btn nxe-hf-ok\" (click)=\"applied.emit()\">OK</button>\r\n <button type=\"button\" class=\"nxe-hf-btn\" (click)=\"cancelled.emit()\">Cancel</button>\r\n</div>\r\n\r\n<!-- Recursive date-tree nodes for the header-filter popup -->\r\n<ng-template #hfTreeNodes let-nodes>\r\n @for (n of nodes; track n.key) {\r\n <div class=\"nxe-hf-tree-node\">\r\n @if (n.children.length) {\r\n <span\r\n class=\"nxe-hf-tree-toggle\"\r\n [class.nxe-expanded]=\"model().nodeExpanded(n.key)\"\r\n (click)=\"model().toggleExpand(n.key)\"></span>\r\n } @else {\r\n <span class=\"nxe-hf-tree-toggle-stub\"></span>\r\n }\r\n <label class=\"nxe-hf-item\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"model().nodeChecked(n)\"\r\n [indeterminate]=\"model().nodeIndeterminate(n)\"\r\n (change)=\"model().toggleNode(n)\" />\r\n <span>{{ n.label }}</span>\r\n </label>\r\n @if (n.children.length && model().nodeExpanded(n.key)) {\r\n <div class=\"nxe-hf-tree-children\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"hfTreeNodes\"\r\n [ngTemplateOutletContext]=\"{ $implicit: n.children }\" />\r\n </div>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n", styles: ["@charset \"UTF-8\";.nxe-hf-panel{display:flex;flex-direction:column;min-width:200px;max-width:320px;background:#fff;border:1px solid #ddd;border-radius:4px;box-shadow:0 6px 12px #0000002d;color:#333}.nxe-hf-panel .nxe-hf-search{margin:6px;padding:5px 8px;border:1px solid #ddd;border-radius:3px;font:inherit;outline:none}.nxe-hf-panel .nxe-hf-search:focus{border-color:#30374c}.nxe-hf-panel .nxe-hf-list{max-height:260px;overflow-y:auto;padding:2px 0}.nxe-hf-panel .nxe-hf-item{display:flex;align-items:center;gap:8px;padding:10px;cursor:pointer}.nxe-hf-panel .nxe-hf-item:hover{background:#f4f4f4}.nxe-hf-panel .nxe-hf-item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nxe-hf-panel .nxe-hf-selectall{padding:12px 10px 8px;border-bottom:1px solid #ddd}.nxe-hf-panel .nxe-hf-actions{display:flex;justify-content:flex-end;gap:6px;padding:6px;border-top:1px solid #eee}.nxe-hf-panel .nxe-hf-btn{min-width:85px;padding:8px 12px;border:1px solid #ddd;border-radius:4px;background:transparent;color:inherit;cursor:pointer;font:inherit}.nxe-hf-panel .nxe-hf-btn:hover{background:#0000000a}.nxe-hf-panel input[type=checkbox]{appearance:none;-webkit-appearance:none;flex:0 0 auto;position:relative;box-sizing:border-box;width:22px;height:22px;margin:0;border:1px solid #ddd;border-radius:2px;background:#fff;cursor:pointer}.nxe-hf-panel input[type=checkbox]:checked:after{content:\"\";position:absolute;left:7px;top:3px;width:6px;height:12px;border:solid #337ab7;border-width:0 2px 2px 0;transform:rotate(45deg)}.nxe-hf-panel input[type=checkbox]:indeterminate:after{content:\"\";position:absolute;left:4px;top:9px;width:12px;height:2px;background:#337ab7}.nxe-hf-panel input[type=checkbox]:hover{border-color:#265a87}.nxe-hf-panel input[type=checkbox]:focus-visible{outline:none;border-color:#337ab7}.nxe-hf-tree-node{display:block;white-space:nowrap}.nxe-hf-tree-children{padding-left:18px}.nxe-hf-tree-node .nxe-hf-item{display:inline-flex}.nxe-hf-tree-toggle{display:inline-block;position:relative;width:14px;height:14px;cursor:pointer;vertical-align:middle}.nxe-hf-tree-toggle:before{content:\"\";position:absolute;left:4px;top:4px;width:5px;height:5px;border-right:1.5px solid #555;border-bottom:1.5px solid #555;transform:rotate(-45deg)}.nxe-hf-tree-toggle.nxe-expanded:before{transform:rotate(45deg)}.nxe-hf-tree-toggle-stub{display:inline-block;width:14px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1215
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeHeaderFilterPanelComponent, isStandalone: true, selector: "nxe-header-filter-panel", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, searchEnabled: { classPropertyName: "searchEnabled", publicName: "searchEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { applied: "applied", cancelled: "cancelled" }, host: { classAttribute: "nxe-header-filter-menu nxe-hf-panel" }, viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (showSearch()) {\r\n <!-- dx builds this as a dxTextBox in mode 'search' (the list/tree searchEnabled option), which\r\n forces showClearButton: a magnifier in a 34px slot inside the left border, and a clear\r\n button in a 34px area inside the right border that only exists while there is text to\r\n clear. Both glyphs are drawn here from primitives; the DXIcons font is not used. -->\r\n <div class=\"nxe-hf-searchbox\">\r\n <!-- The magnifier both search boxes share (dx draws them with one rule,\r\n .dx-searchbox .dx-icon-search): a r4.75 ring and a 45deg handle, 1.9px strokes. Keep\r\n it identical to the tree view's (nxe-tree-view.component.html). -->\r\n <svg class=\"nxe-hf-search-icon\" viewBox=\"0 0 34 34\" aria-hidden=\"true\" focusable=\"false\">\r\n <circle cx=\"15\" cy=\"15\" r=\"4.75\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" />\r\n <path d=\"M18.5 18.5 23.5 23.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" />\r\n </svg>\r\n <input\r\n #searchInput\r\n class=\"nxe-hf-search\"\r\n type=\"text\"\r\n placeholder=\"Search\"\r\n aria-label=\"Search\"\r\n [value]=\"model().search()\"\r\n (input)=\"model().search.set($any($event.target).value)\" />\r\n @if (model().search()) {\r\n <button\r\n class=\"nxe-hf-search-clear\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n aria-label=\"Clear\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"clearSearch()\">\r\n <!-- The editors' clear glyph: a 16px disc with a 2px x cut out, two 2px bars\r\n each running corner to corner of the 8px square centred on the disc, drawn as\r\n a plus and turned 45deg. -->\r\n <svg viewBox=\"0 0 18 18\" aria-hidden=\"true\" focusable=\"false\">\r\n <path\r\n fill=\"currentColor\"\r\n fill-rule=\"evenodd\"\r\n transform=\"rotate(45 9 9)\"\r\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\r\n </svg>\r\n </button>\r\n }\r\n </div>\r\n}\r\n<div class=\"nxe-hf-list\" [class.nxe-hf-tree]=\"model().isDate()\">\r\n <label class=\"nxe-hf-item nxe-hf-selectall\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"model().allChecked()\"\r\n [indeterminate]=\"model().someChecked()\"\r\n (change)=\"model().toggleAll()\" />\r\n <span>Select All</span>\r\n </label>\r\n @if (model().isDate()) {\r\n <!-- dx date header filter: Year > Month > Day checkbox tree -->\r\n <ng-container\r\n [ngTemplateOutlet]=\"hfTreeNodes\"\r\n [ngTemplateOutletContext]=\"{ $implicit: model().dateTreeFiltered() }\" />\r\n } @else {\r\n @for (item of model().distinctFiltered(); track item.key) {\r\n <label class=\"nxe-hf-item nxe-hf-list-item\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"model().draft().has(item.key)\"\r\n (change)=\"model().toggleValue(item.key)\" />\r\n <span>{{ item.text }}</span>\r\n </label>\r\n }\r\n }\r\n</div>\r\n<div class=\"nxe-hf-actions\">\r\n <button type=\"button\" class=\"nxe-hf-btn nxe-hf-ok\" (click)=\"applied.emit()\">OK</button>\r\n <button type=\"button\" class=\"nxe-hf-btn\" (click)=\"cancelled.emit()\">Cancel</button>\r\n</div>\r\n\r\n<!-- Recursive date-tree nodes for the header-filter popup. Each node's first grid row is dx's\r\n .dx-treeview-item: the toggle (or its stub) and the label share it, the label spanning the\r\n whole row so its hover paints the full row, as dx's does. -->\r\n<ng-template #hfTreeNodes let-nodes>\r\n @for (n of nodes; track n.key) {\r\n <div class=\"nxe-hf-tree-node\">\r\n @if (n.children.length) {\r\n <span\r\n class=\"nxe-hf-tree-toggle\"\r\n [class.nxe-expanded]=\"model().nodeExpanded(n.key)\"\r\n (click)=\"model().toggleExpand(n.key)\"></span>\r\n } @else {\r\n <span class=\"nxe-hf-tree-toggle-stub\"></span>\r\n }\r\n <label class=\"nxe-hf-item nxe-hf-tree-item\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"model().nodeChecked(n)\"\r\n [indeterminate]=\"model().nodeIndeterminate(n)\"\r\n (change)=\"model().toggleNode(n)\" />\r\n <span>{{ n.label }}</span>\r\n </label>\r\n @if (n.children.length && model().nodeExpanded(n.key)) {\r\n <div class=\"nxe-hf-tree-children\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"hfTreeNodes\"\r\n [ngTemplateOutletContext]=\"{ $implicit: n.children }\" />\r\n </div>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n", styles: ["@charset \"UTF-8\";.nxe-hf-panel{display:flex;flex-direction:column;box-sizing:border-box;width:252px;height:325px;min-width:var(--nxe-ui-header-filter-min-width, 0px);padding:20px 0 0;background:var(--nxe-ui-surface, #fff);border:1px solid var(--nxe-ui-border, #ddd);border-radius:var(--nxe-ui-popup-radius, 6px);box-shadow:var(--nxe-ui-popup-shadow, 0 6px 12px rgba(0, 0, 0, .175));color:var(--nxe-ui-text, #333)}.nxe-hf-panel .nxe-hf-searchbox{position:relative;display:flex;flex:0 0 auto;box-sizing:border-box;margin:0 20px 5px;padding:0;border:1px solid var(--nxe-ui-border, #ddd);border-radius:var(--nxe-ui-radius, 4px);background:var(--nxe-ui-surface, #fff)}.nxe-hf-panel .nxe-hf-searchbox:hover{border-color:#337ab766}.nxe-hf-panel .nxe-hf-searchbox:focus-within{border-color:var(--nxe-ui-accent, #337ab7)}.nxe-hf-panel .nxe-hf-search{-webkit-appearance:none;appearance:none;flex:1 1 auto;min-width:0;width:100%;box-sizing:border-box;min-height:34px;margin:0;padding:7px 9px 8px;padding-inline-start:34px;border:0;border-radius:var(--nxe-ui-radius, 4px);outline:0;background:transparent;color:var(--nxe-ui-text, #333);font:inherit}.nxe-hf-panel .nxe-hf-search::placeholder{color:var(--nxe-ui-text-muted, #999);opacity:1}.nxe-hf-panel .nxe-hf-search-icon{position:absolute;top:50%;left:0;display:block;box-sizing:border-box;width:34px;height:34px;margin-top:-17px;color:var(--nxe-ui-text-muted, #999);pointer-events:none}.nxe-hf-panel .nxe-hf-search-clear{position:relative;display:flex;align-items:center;justify-content:center;flex:0 0 34px;box-sizing:border-box;width:34px;min-width:34px;margin:0;padding:0;border:0;background:transparent;color:var(--nxe-ui-text-muted, #999);cursor:pointer}.nxe-hf-panel .nxe-hf-search-clear svg{display:block;width:18px;height:18px}.nxe-hf-panel .nxe-hf-list{flex:1 1 auto;min-height:0;margin:0 20px 20px;padding:0;overflow-x:hidden;overflow-y:auto}.nxe-hf-panel .nxe-hf-item{box-sizing:border-box;margin:0;cursor:pointer}.nxe-hf-panel .nxe-hf-item>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nxe-hf-panel .nxe-hf-list-item{display:flex;align-items:center;padding:0}.nxe-hf-panel .nxe-hf-list-item>input[type=checkbox]{margin:-1px 0 -3px;margin-inline:10px -1px}.nxe-hf-panel .nxe-hf-list-item>span{flex:1 1 auto;min-width:0;padding:10px}.nxe-hf-panel .nxe-hf-list-item+.nxe-hf-list-item{border-top:1px solid var(--nxe-ui-border, #ddd)}.nxe-hf-panel .nxe-hf-selectall{display:block;padding:9px 0 8px;border-bottom:1px solid var(--nxe-ui-border, #ddd);white-space:nowrap}.nxe-hf-panel .nxe-hf-selectall>input[type=checkbox]{float:inline-start;margin:-1px 0 -3px;margin-inline:10px 0}.nxe-hf-panel .nxe-hf-selectall>span{display:inline-block;box-sizing:border-box;max-width:calc(100% - 32px);margin-top:1px;padding:0 10px;line-height:17px;vertical-align:baseline}.nxe-hf-panel .nxe-hf-list-item:hover>input[type=checkbox]:not(:focus-visible),.nxe-hf-panel .nxe-hf-selectall:hover>input[type=checkbox]:not(:focus-visible){border-color:var(--nxe-ui-checkbox-hover-border, #265a87)}.nxe-hf-panel .nxe-hf-tree>.nxe-hf-selectall{display:flex;align-items:center;padding:9px 40px 11px}.nxe-hf-panel .nxe-hf-tree>.nxe-hf-selectall>input[type=checkbox]{float:none;margin:0}.nxe-hf-panel .nxe-hf-tree>.nxe-hf-selectall>span{display:block;flex:0 1 auto;min-width:0;max-width:none;margin:0;padding:0;padding-inline-start:8px;line-height:inherit}.nxe-hf-panel .nxe-hf-tree-node{display:grid;grid-template-columns:32px minmax(0,1fr);align-items:center;white-space:nowrap}.nxe-hf-panel .nxe-hf-tree-toggle,.nxe-hf-panel .nxe-hf-tree-toggle-stub{grid-area:1/1;justify-self:end;position:relative;display:block;box-sizing:border-box;width:22px;height:22px}.nxe-hf-panel .nxe-hf-tree-toggle{color:var(--nxe-ui-text, #333);cursor:pointer}.nxe-hf-panel .nxe-hf-tree-toggle:before{content:\"\";position:absolute;left:50%;top:50%;box-sizing:border-box;width:0;height:0;margin:-4px 0 0 -2px;border-style:solid;border-width:4px 0 4px 4px;border-color:transparent transparent transparent currentColor;transform:none}.nxe-hf-panel .nxe-hf-tree-toggle.nxe-expanded:before{margin:-2px 0 0 -4px;border-width:4px 4px 0;border-color:currentColor transparent transparent}.nxe-hf-panel .nxe-hf-tree-toggle-stub{pointer-events:none}.nxe-hf-panel .nxe-hf-tree-item{grid-area:1/1/2/3;display:flex;align-items:center;gap:8px;padding:5px 10px;padding-inline-start:40px;pointer-events:none}.nxe-hf-panel .nxe-hf-tree-item>input[type=checkbox],.nxe-hf-panel .nxe-hf-tree-item>span{pointer-events:auto}.nxe-hf-panel .nxe-hf-tree-item>span{flex:0 1 auto;min-width:0}.nxe-hf-panel .nxe-hf-tree-children{grid-area:2/1/3/3;padding-inline-start:15px}.nxe-hf-panel .nxe-hf-tree-node:hover:not(:has(>.nxe-hf-tree-children:hover))>.nxe-hf-tree-item{background-color:var(--nxe-ui-hover-bg, rgba(0, 0, 0, .04));color:var(--nxe-ui-text, #333)}.nxe-hf-panel .nxe-hf-actions{display:flex;flex:0 0 auto;align-items:center;justify-content:flex-end;gap:10px;box-sizing:content-box;height:36px;padding:20px}.nxe-hf-panel .nxe-hf-btn{box-sizing:border-box;min-width:100px;padding:8px 12px;border:1px solid var(--nxe-ui-border, #ddd);border-radius:var(--nxe-ui-radius, 4px);background:transparent;color:inherit;cursor:pointer;font:inherit;line-height:1.2858}.nxe-hf-panel .nxe-hf-btn:hover{background:#0000000a}.nxe-hf-panel input[type=checkbox]{appearance:none;-webkit-appearance:none;flex:0 0 auto;position:relative;box-sizing:border-box;width:22px;height:22px;margin:0;border:1px solid var(--nxe-ui-border, #ddd);border-radius:2px;background:var(--nxe-ui-surface, #fff);color:var(--nxe-ui-accent, #337ab7);cursor:pointer}.nxe-hf-panel input[type=checkbox]:checked:not(:indeterminate):after{content:\"\";position:absolute;left:50%;top:50%;box-sizing:border-box;width:7.5px;height:13.25px;margin:-8.1px 0 0 -3.75px;border:solid currentColor;border-width:0 2px 2px 0;border-radius:0;background:none;transform:rotate(45deg)}.nxe-hf-panel input[type=checkbox]:indeterminate:after{content:\"\";position:absolute;left:50%;top:50%;box-sizing:border-box;width:12px;height:12px;margin:-6px 0 0 -6px;border:0;border-radius:2px;background:currentColor;transform:none}@media(forced-colors:active){.nxe-hf-panel input[type=checkbox]:indeterminate:after{forced-color-adjust:none;background-color:CanvasText}}.nxe-hf-panel input[type=checkbox]:hover{border-color:var(--nxe-ui-checkbox-hover-border, #265a87)}.nxe-hf-panel input[type=checkbox]:focus-visible{outline:none;border-color:var(--nxe-ui-accent, #337ab7)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1059
1216
  }
1060
1217
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeHeaderFilterPanelComponent, decorators: [{
1061
1218
  type: Component,
1062
- args: [{ selector: 'nxe-header-filter-panel', standalone: true, imports: [NgTemplateOutlet], host: { class: 'dx-header-filter-menu nxe-hf-panel' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if (showSearch()) {\r\n <input\r\n class=\"nxe-hf-search\"\r\n type=\"text\"\r\n placeholder=\"Search\"\r\n [value]=\"model().search()\"\r\n (input)=\"model().search.set($any($event.target).value)\" />\r\n}\r\n<div class=\"nxe-hf-list\" [class.nxe-hf-tree]=\"model().isDate()\">\r\n <label class=\"nxe-hf-item nxe-hf-selectall\">\r\n <input type=\"checkbox\" [checked]=\"model().allChecked()\" (change)=\"model().toggleAll()\" />\r\n <span>Select All</span>\r\n </label>\r\n @if (model().isDate()) {\r\n <!-- dx date header filter: Year > Month > Day checkbox tree -->\r\n <ng-container\r\n [ngTemplateOutlet]=\"hfTreeNodes\"\r\n [ngTemplateOutletContext]=\"{ $implicit: model().dateTreeFiltered() }\" />\r\n } @else {\r\n @for (item of model().distinctFiltered(); track item.key) {\r\n <label class=\"nxe-hf-item\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"model().draft().has(item.key)\"\r\n (change)=\"model().toggleValue(item.key)\" />\r\n <span>{{ item.text }}</span>\r\n </label>\r\n }\r\n }\r\n</div>\r\n<div class=\"nxe-hf-actions\">\r\n <button type=\"button\" class=\"nxe-hf-btn nxe-hf-ok\" (click)=\"applied.emit()\">OK</button>\r\n <button type=\"button\" class=\"nxe-hf-btn\" (click)=\"cancelled.emit()\">Cancel</button>\r\n</div>\r\n\r\n<!-- Recursive date-tree nodes for the header-filter popup -->\r\n<ng-template #hfTreeNodes let-nodes>\r\n @for (n of nodes; track n.key) {\r\n <div class=\"nxe-hf-tree-node\">\r\n @if (n.children.length) {\r\n <span\r\n class=\"nxe-hf-tree-toggle\"\r\n [class.nxe-expanded]=\"model().nodeExpanded(n.key)\"\r\n (click)=\"model().toggleExpand(n.key)\"></span>\r\n } @else {\r\n <span class=\"nxe-hf-tree-toggle-stub\"></span>\r\n }\r\n <label class=\"nxe-hf-item\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"model().nodeChecked(n)\"\r\n [indeterminate]=\"model().nodeIndeterminate(n)\"\r\n (change)=\"model().toggleNode(n)\" />\r\n <span>{{ n.label }}</span>\r\n </label>\r\n @if (n.children.length && model().nodeExpanded(n.key)) {\r\n <div class=\"nxe-hf-tree-children\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"hfTreeNodes\"\r\n [ngTemplateOutletContext]=\"{ $implicit: n.children }\" />\r\n </div>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n", styles: ["@charset \"UTF-8\";.nxe-hf-panel{display:flex;flex-direction:column;min-width:200px;max-width:320px;background:#fff;border:1px solid #ddd;border-radius:4px;box-shadow:0 6px 12px #0000002d;color:#333}.nxe-hf-panel .nxe-hf-search{margin:6px;padding:5px 8px;border:1px solid #ddd;border-radius:3px;font:inherit;outline:none}.nxe-hf-panel .nxe-hf-search:focus{border-color:#30374c}.nxe-hf-panel .nxe-hf-list{max-height:260px;overflow-y:auto;padding:2px 0}.nxe-hf-panel .nxe-hf-item{display:flex;align-items:center;gap:8px;padding:10px;cursor:pointer}.nxe-hf-panel .nxe-hf-item:hover{background:#f4f4f4}.nxe-hf-panel .nxe-hf-item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nxe-hf-panel .nxe-hf-selectall{padding:12px 10px 8px;border-bottom:1px solid #ddd}.nxe-hf-panel .nxe-hf-actions{display:flex;justify-content:flex-end;gap:6px;padding:6px;border-top:1px solid #eee}.nxe-hf-panel .nxe-hf-btn{min-width:85px;padding:8px 12px;border:1px solid #ddd;border-radius:4px;background:transparent;color:inherit;cursor:pointer;font:inherit}.nxe-hf-panel .nxe-hf-btn:hover{background:#0000000a}.nxe-hf-panel input[type=checkbox]{appearance:none;-webkit-appearance:none;flex:0 0 auto;position:relative;box-sizing:border-box;width:22px;height:22px;margin:0;border:1px solid #ddd;border-radius:2px;background:#fff;cursor:pointer}.nxe-hf-panel input[type=checkbox]:checked:after{content:\"\";position:absolute;left:7px;top:3px;width:6px;height:12px;border:solid #337ab7;border-width:0 2px 2px 0;transform:rotate(45deg)}.nxe-hf-panel input[type=checkbox]:indeterminate:after{content:\"\";position:absolute;left:4px;top:9px;width:12px;height:2px;background:#337ab7}.nxe-hf-panel input[type=checkbox]:hover{border-color:#265a87}.nxe-hf-panel input[type=checkbox]:focus-visible{outline:none;border-color:#337ab7}.nxe-hf-tree-node{display:block;white-space:nowrap}.nxe-hf-tree-children{padding-left:18px}.nxe-hf-tree-node .nxe-hf-item{display:inline-flex}.nxe-hf-tree-toggle{display:inline-block;position:relative;width:14px;height:14px;cursor:pointer;vertical-align:middle}.nxe-hf-tree-toggle:before{content:\"\";position:absolute;left:4px;top:4px;width:5px;height:5px;border-right:1.5px solid #555;border-bottom:1.5px solid #555;transform:rotate(-45deg)}.nxe-hf-tree-toggle.nxe-expanded:before{transform:rotate(45deg)}.nxe-hf-tree-toggle-stub{display:inline-block;width:14px}\n"] }]
1063
- }], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: true }] }], searchEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchEnabled", required: false }] }], applied: [{ type: i0.Output, args: ["applied"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
1219
+ args: [{ selector: 'nxe-header-filter-panel', standalone: true, imports: [NgTemplateOutlet], host: { class: 'nxe-header-filter-menu nxe-hf-panel' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if (showSearch()) {\r\n <!-- dx builds this as a dxTextBox in mode 'search' (the list/tree searchEnabled option), which\r\n forces showClearButton: a magnifier in a 34px slot inside the left border, and a clear\r\n button in a 34px area inside the right border that only exists while there is text to\r\n clear. Both glyphs are drawn here from primitives; the DXIcons font is not used. -->\r\n <div class=\"nxe-hf-searchbox\">\r\n <!-- The magnifier both search boxes share (dx draws them with one rule,\r\n .dx-searchbox .dx-icon-search): a r4.75 ring and a 45deg handle, 1.9px strokes. Keep\r\n it identical to the tree view's (nxe-tree-view.component.html). -->\r\n <svg class=\"nxe-hf-search-icon\" viewBox=\"0 0 34 34\" aria-hidden=\"true\" focusable=\"false\">\r\n <circle cx=\"15\" cy=\"15\" r=\"4.75\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" />\r\n <path d=\"M18.5 18.5 23.5 23.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" />\r\n </svg>\r\n <input\r\n #searchInput\r\n class=\"nxe-hf-search\"\r\n type=\"text\"\r\n placeholder=\"Search\"\r\n aria-label=\"Search\"\r\n [value]=\"model().search()\"\r\n (input)=\"model().search.set($any($event.target).value)\" />\r\n @if (model().search()) {\r\n <button\r\n class=\"nxe-hf-search-clear\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n aria-label=\"Clear\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"clearSearch()\">\r\n <!-- The editors' clear glyph: a 16px disc with a 2px x cut out, two 2px bars\r\n each running corner to corner of the 8px square centred on the disc, drawn as\r\n a plus and turned 45deg. -->\r\n <svg viewBox=\"0 0 18 18\" aria-hidden=\"true\" focusable=\"false\">\r\n <path\r\n fill=\"currentColor\"\r\n fill-rule=\"evenodd\"\r\n transform=\"rotate(45 9 9)\"\r\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\r\n </svg>\r\n </button>\r\n }\r\n </div>\r\n}\r\n<div class=\"nxe-hf-list\" [class.nxe-hf-tree]=\"model().isDate()\">\r\n <label class=\"nxe-hf-item nxe-hf-selectall\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"model().allChecked()\"\r\n [indeterminate]=\"model().someChecked()\"\r\n (change)=\"model().toggleAll()\" />\r\n <span>Select All</span>\r\n </label>\r\n @if (model().isDate()) {\r\n <!-- dx date header filter: Year > Month > Day checkbox tree -->\r\n <ng-container\r\n [ngTemplateOutlet]=\"hfTreeNodes\"\r\n [ngTemplateOutletContext]=\"{ $implicit: model().dateTreeFiltered() }\" />\r\n } @else {\r\n @for (item of model().distinctFiltered(); track item.key) {\r\n <label class=\"nxe-hf-item nxe-hf-list-item\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"model().draft().has(item.key)\"\r\n (change)=\"model().toggleValue(item.key)\" />\r\n <span>{{ item.text }}</span>\r\n </label>\r\n }\r\n }\r\n</div>\r\n<div class=\"nxe-hf-actions\">\r\n <button type=\"button\" class=\"nxe-hf-btn nxe-hf-ok\" (click)=\"applied.emit()\">OK</button>\r\n <button type=\"button\" class=\"nxe-hf-btn\" (click)=\"cancelled.emit()\">Cancel</button>\r\n</div>\r\n\r\n<!-- Recursive date-tree nodes for the header-filter popup. Each node's first grid row is dx's\r\n .dx-treeview-item: the toggle (or its stub) and the label share it, the label spanning the\r\n whole row so its hover paints the full row, as dx's does. -->\r\n<ng-template #hfTreeNodes let-nodes>\r\n @for (n of nodes; track n.key) {\r\n <div class=\"nxe-hf-tree-node\">\r\n @if (n.children.length) {\r\n <span\r\n class=\"nxe-hf-tree-toggle\"\r\n [class.nxe-expanded]=\"model().nodeExpanded(n.key)\"\r\n (click)=\"model().toggleExpand(n.key)\"></span>\r\n } @else {\r\n <span class=\"nxe-hf-tree-toggle-stub\"></span>\r\n }\r\n <label class=\"nxe-hf-item nxe-hf-tree-item\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"model().nodeChecked(n)\"\r\n [indeterminate]=\"model().nodeIndeterminate(n)\"\r\n (change)=\"model().toggleNode(n)\" />\r\n <span>{{ n.label }}</span>\r\n </label>\r\n @if (n.children.length && model().nodeExpanded(n.key)) {\r\n <div class=\"nxe-hf-tree-children\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"hfTreeNodes\"\r\n [ngTemplateOutletContext]=\"{ $implicit: n.children }\" />\r\n </div>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n", styles: ["@charset \"UTF-8\";.nxe-hf-panel{display:flex;flex-direction:column;box-sizing:border-box;width:252px;height:325px;min-width:var(--nxe-ui-header-filter-min-width, 0px);padding:20px 0 0;background:var(--nxe-ui-surface, #fff);border:1px solid var(--nxe-ui-border, #ddd);border-radius:var(--nxe-ui-popup-radius, 6px);box-shadow:var(--nxe-ui-popup-shadow, 0 6px 12px rgba(0, 0, 0, .175));color:var(--nxe-ui-text, #333)}.nxe-hf-panel .nxe-hf-searchbox{position:relative;display:flex;flex:0 0 auto;box-sizing:border-box;margin:0 20px 5px;padding:0;border:1px solid var(--nxe-ui-border, #ddd);border-radius:var(--nxe-ui-radius, 4px);background:var(--nxe-ui-surface, #fff)}.nxe-hf-panel .nxe-hf-searchbox:hover{border-color:#337ab766}.nxe-hf-panel .nxe-hf-searchbox:focus-within{border-color:var(--nxe-ui-accent, #337ab7)}.nxe-hf-panel .nxe-hf-search{-webkit-appearance:none;appearance:none;flex:1 1 auto;min-width:0;width:100%;box-sizing:border-box;min-height:34px;margin:0;padding:7px 9px 8px;padding-inline-start:34px;border:0;border-radius:var(--nxe-ui-radius, 4px);outline:0;background:transparent;color:var(--nxe-ui-text, #333);font:inherit}.nxe-hf-panel .nxe-hf-search::placeholder{color:var(--nxe-ui-text-muted, #999);opacity:1}.nxe-hf-panel .nxe-hf-search-icon{position:absolute;top:50%;left:0;display:block;box-sizing:border-box;width:34px;height:34px;margin-top:-17px;color:var(--nxe-ui-text-muted, #999);pointer-events:none}.nxe-hf-panel .nxe-hf-search-clear{position:relative;display:flex;align-items:center;justify-content:center;flex:0 0 34px;box-sizing:border-box;width:34px;min-width:34px;margin:0;padding:0;border:0;background:transparent;color:var(--nxe-ui-text-muted, #999);cursor:pointer}.nxe-hf-panel .nxe-hf-search-clear svg{display:block;width:18px;height:18px}.nxe-hf-panel .nxe-hf-list{flex:1 1 auto;min-height:0;margin:0 20px 20px;padding:0;overflow-x:hidden;overflow-y:auto}.nxe-hf-panel .nxe-hf-item{box-sizing:border-box;margin:0;cursor:pointer}.nxe-hf-panel .nxe-hf-item>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nxe-hf-panel .nxe-hf-list-item{display:flex;align-items:center;padding:0}.nxe-hf-panel .nxe-hf-list-item>input[type=checkbox]{margin:-1px 0 -3px;margin-inline:10px -1px}.nxe-hf-panel .nxe-hf-list-item>span{flex:1 1 auto;min-width:0;padding:10px}.nxe-hf-panel .nxe-hf-list-item+.nxe-hf-list-item{border-top:1px solid var(--nxe-ui-border, #ddd)}.nxe-hf-panel .nxe-hf-selectall{display:block;padding:9px 0 8px;border-bottom:1px solid var(--nxe-ui-border, #ddd);white-space:nowrap}.nxe-hf-panel .nxe-hf-selectall>input[type=checkbox]{float:inline-start;margin:-1px 0 -3px;margin-inline:10px 0}.nxe-hf-panel .nxe-hf-selectall>span{display:inline-block;box-sizing:border-box;max-width:calc(100% - 32px);margin-top:1px;padding:0 10px;line-height:17px;vertical-align:baseline}.nxe-hf-panel .nxe-hf-list-item:hover>input[type=checkbox]:not(:focus-visible),.nxe-hf-panel .nxe-hf-selectall:hover>input[type=checkbox]:not(:focus-visible){border-color:var(--nxe-ui-checkbox-hover-border, #265a87)}.nxe-hf-panel .nxe-hf-tree>.nxe-hf-selectall{display:flex;align-items:center;padding:9px 40px 11px}.nxe-hf-panel .nxe-hf-tree>.nxe-hf-selectall>input[type=checkbox]{float:none;margin:0}.nxe-hf-panel .nxe-hf-tree>.nxe-hf-selectall>span{display:block;flex:0 1 auto;min-width:0;max-width:none;margin:0;padding:0;padding-inline-start:8px;line-height:inherit}.nxe-hf-panel .nxe-hf-tree-node{display:grid;grid-template-columns:32px minmax(0,1fr);align-items:center;white-space:nowrap}.nxe-hf-panel .nxe-hf-tree-toggle,.nxe-hf-panel .nxe-hf-tree-toggle-stub{grid-area:1/1;justify-self:end;position:relative;display:block;box-sizing:border-box;width:22px;height:22px}.nxe-hf-panel .nxe-hf-tree-toggle{color:var(--nxe-ui-text, #333);cursor:pointer}.nxe-hf-panel .nxe-hf-tree-toggle:before{content:\"\";position:absolute;left:50%;top:50%;box-sizing:border-box;width:0;height:0;margin:-4px 0 0 -2px;border-style:solid;border-width:4px 0 4px 4px;border-color:transparent transparent transparent currentColor;transform:none}.nxe-hf-panel .nxe-hf-tree-toggle.nxe-expanded:before{margin:-2px 0 0 -4px;border-width:4px 4px 0;border-color:currentColor transparent transparent}.nxe-hf-panel .nxe-hf-tree-toggle-stub{pointer-events:none}.nxe-hf-panel .nxe-hf-tree-item{grid-area:1/1/2/3;display:flex;align-items:center;gap:8px;padding:5px 10px;padding-inline-start:40px;pointer-events:none}.nxe-hf-panel .nxe-hf-tree-item>input[type=checkbox],.nxe-hf-panel .nxe-hf-tree-item>span{pointer-events:auto}.nxe-hf-panel .nxe-hf-tree-item>span{flex:0 1 auto;min-width:0}.nxe-hf-panel .nxe-hf-tree-children{grid-area:2/1/3/3;padding-inline-start:15px}.nxe-hf-panel .nxe-hf-tree-node:hover:not(:has(>.nxe-hf-tree-children:hover))>.nxe-hf-tree-item{background-color:var(--nxe-ui-hover-bg, rgba(0, 0, 0, .04));color:var(--nxe-ui-text, #333)}.nxe-hf-panel .nxe-hf-actions{display:flex;flex:0 0 auto;align-items:center;justify-content:flex-end;gap:10px;box-sizing:content-box;height:36px;padding:20px}.nxe-hf-panel .nxe-hf-btn{box-sizing:border-box;min-width:100px;padding:8px 12px;border:1px solid var(--nxe-ui-border, #ddd);border-radius:var(--nxe-ui-radius, 4px);background:transparent;color:inherit;cursor:pointer;font:inherit;line-height:1.2858}.nxe-hf-panel .nxe-hf-btn:hover{background:#0000000a}.nxe-hf-panel input[type=checkbox]{appearance:none;-webkit-appearance:none;flex:0 0 auto;position:relative;box-sizing:border-box;width:22px;height:22px;margin:0;border:1px solid var(--nxe-ui-border, #ddd);border-radius:2px;background:var(--nxe-ui-surface, #fff);color:var(--nxe-ui-accent, #337ab7);cursor:pointer}.nxe-hf-panel input[type=checkbox]:checked:not(:indeterminate):after{content:\"\";position:absolute;left:50%;top:50%;box-sizing:border-box;width:7.5px;height:13.25px;margin:-8.1px 0 0 -3.75px;border:solid currentColor;border-width:0 2px 2px 0;border-radius:0;background:none;transform:rotate(45deg)}.nxe-hf-panel input[type=checkbox]:indeterminate:after{content:\"\";position:absolute;left:50%;top:50%;box-sizing:border-box;width:12px;height:12px;margin:-6px 0 0 -6px;border:0;border-radius:2px;background:currentColor;transform:none}@media(forced-colors:active){.nxe-hf-panel input[type=checkbox]:indeterminate:after{forced-color-adjust:none;background-color:CanvasText}}.nxe-hf-panel input[type=checkbox]:hover{border-color:var(--nxe-ui-checkbox-hover-border, #265a87)}.nxe-hf-panel input[type=checkbox]:focus-visible{outline:none;border-color:var(--nxe-ui-accent, #337ab7)}\n"] }]
1220
+ }], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: true }] }], searchEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchEnabled", required: false }] }], applied: [{ type: i0.Output, args: ["applied"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }], searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
1064
1221
 
1065
1222
  /** Create + attach the header-filter popup; the caller keeps the ref to detach/dispose. */
1066
1223
  function openNxeHeaderFilterOverlay(options) {
@@ -1227,12 +1384,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
1227
1384
  class NxePagingComponent {
1228
1385
  pageSize = input(20, /* @ts-ignore */
1229
1386
  ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
1230
- pageIndex = input(0, /* @ts-ignore */
1387
+ /** Two-way: dx supports `[(pageIndex)]`, and a consumer drives the pager from outside with it. */
1388
+ pageIndex = model(0, /* @ts-ignore */
1231
1389
  ...(ngDevMode ? [{ debugName: "pageIndex" }] : /* istanbul ignore next */ []));
1232
1390
  enabled = input(true, /* @ts-ignore */
1233
1391
  ...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
1234
1392
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxePagingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1235
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type: NxePagingComponent, isStandalone: true, selector: "nxe-paging", inputs: { pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageIndex: { classPropertyName: "pageIndex", publicName: "pageIndex", isSignal: true, isRequired: false, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display: none" }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1393
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type: NxePagingComponent, isStandalone: true, selector: "nxe-paging", inputs: { pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageIndex: { classPropertyName: "pageIndex", publicName: "pageIndex", isSignal: true, isRequired: false, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageIndex: "pageIndexChange" }, host: { styleAttribute: "display: none" }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1236
1394
  }
1237
1395
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxePagingComponent, decorators: [{
1238
1396
  type: Component,
@@ -1244,11 +1402,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
1244
1402
  changeDetection: ChangeDetectionStrategy.OnPush,
1245
1403
  encapsulation: ViewEncapsulation.None,
1246
1404
  }]
1247
- }], propDecorators: { pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], pageIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageIndex", required: false }] }], enabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "enabled", required: false }] }] } });
1405
+ }], propDecorators: { pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], pageIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageIndex", required: false }] }, { type: i0.Output, args: ["pageIndexChange"] }], enabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "enabled", required: false }] }] } });
1248
1406
  /** `dxo-pager` — the pager UI config. */
1249
1407
  class NxePagerComponent {
1408
+ /** `'auto'` shows the pager only when there is more than one page (dx `isVisible`). */
1250
1409
  visible = input('auto', /* @ts-ignore */
1251
1410
  ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
1411
+ /** `'auto'` offers half, the same and double the page size (dx `getPageSizes`). */
1252
1412
  allowedPageSizes = input('auto', /* @ts-ignore */
1253
1413
  ...(ngDevMode ? [{ debugName: "allowedPageSizes" }] : /* istanbul ignore next */ []));
1254
1414
  showPageSizeSelector = input(false, /* @ts-ignore */
@@ -1257,10 +1417,20 @@ class NxePagerComponent {
1257
1417
  ...(ngDevMode ? [{ debugName: "showNavigationButtons" }] : /* istanbul ignore next */ []));
1258
1418
  showInfo = input(false, /* @ts-ignore */
1259
1419
  ...(ngDevMode ? [{ debugName: "showInfo" }] : /* istanbul ignore next */ []));
1420
+ /**
1421
+ * `'full'` and `'adaptive'` list the page sizes as buttons; `'compact'` uses a drop-down and
1422
+ * always shows the navigation buttons, as dx's small mode does.
1423
+ */
1260
1424
  displayMode = input('adaptive', /* @ts-ignore */
1261
1425
  ...(ngDevMode ? [{ debugName: "displayMode" }] : /* istanbul ignore next */ []));
1426
+ /** dx `pager.infoText`: `{0}` the page, `{1}` the page count, `{2}` the item count. */
1427
+ infoText = input(undefined, /* @ts-ignore */
1428
+ ...(ngDevMode ? [{ debugName: "infoText" }] : /* istanbul ignore next */ []));
1429
+ /** dx `pager.label`: the pager's accessible name. */
1430
+ label = input('Page navigation', /* @ts-ignore */
1431
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1262
1432
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxePagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1263
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type: NxePagerComponent, isStandalone: true, selector: "nxe-pager", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, allowedPageSizes: { classPropertyName: "allowedPageSizes", publicName: "allowedPageSizes", isSignal: true, isRequired: false, transformFunction: null }, showPageSizeSelector: { classPropertyName: "showPageSizeSelector", publicName: "showPageSizeSelector", isSignal: true, isRequired: false, transformFunction: null }, showNavigationButtons: { classPropertyName: "showNavigationButtons", publicName: "showNavigationButtons", isSignal: true, isRequired: false, transformFunction: null }, showInfo: { classPropertyName: "showInfo", publicName: "showInfo", isSignal: true, isRequired: false, transformFunction: null }, displayMode: { classPropertyName: "displayMode", publicName: "displayMode", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display: none" }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1433
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type: NxePagerComponent, isStandalone: true, selector: "nxe-pager", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, allowedPageSizes: { classPropertyName: "allowedPageSizes", publicName: "allowedPageSizes", isSignal: true, isRequired: false, transformFunction: null }, showPageSizeSelector: { classPropertyName: "showPageSizeSelector", publicName: "showPageSizeSelector", isSignal: true, isRequired: false, transformFunction: null }, showNavigationButtons: { classPropertyName: "showNavigationButtons", publicName: "showNavigationButtons", isSignal: true, isRequired: false, transformFunction: null }, showInfo: { classPropertyName: "showInfo", publicName: "showInfo", isSignal: true, isRequired: false, transformFunction: null }, displayMode: { classPropertyName: "displayMode", publicName: "displayMode", isSignal: true, isRequired: false, transformFunction: null }, infoText: { classPropertyName: "infoText", publicName: "infoText", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display: none" }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1264
1434
  }
1265
1435
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxePagerComponent, decorators: [{
1266
1436
  type: Component,
@@ -1272,7 +1442,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
1272
1442
  changeDetection: ChangeDetectionStrategy.OnPush,
1273
1443
  encapsulation: ViewEncapsulation.None,
1274
1444
  }]
1275
- }], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], allowedPageSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowedPageSizes", required: false }] }], showPageSizeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageSizeSelector", required: false }] }], showNavigationButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNavigationButtons", required: false }] }], showInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "showInfo", required: false }] }], displayMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayMode", required: false }] }] } });
1445
+ }], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], allowedPageSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowedPageSizes", required: false }] }], showPageSizeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageSizeSelector", required: false }] }], showNavigationButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNavigationButtons", required: false }] }], showInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "showInfo", required: false }] }], displayMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayMode", required: false }] }], infoText: [{ type: i0.Input, args: [{ isSignal: true, alias: "infoText", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
1276
1446
 
1277
1447
  /* eslint-disable @typescript-eslint/no-explicit-any */
1278
1448
  /**
@@ -1341,8 +1511,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
1341
1511
  * `nxe-master-detail` — drop-in for DevExtreme `dxo-master-detail` (orbit addition).
1342
1512
  *
1343
1513
  * A no-render config element read by the parent `nxe-data-grid`. When `enabled`, the grid
1344
- * prepends a `dx-command-expand` column; expanding a row renders a
1345
- * `<tr class="dx-master-detail-row">` hosting the named sibling
1514
+ * prepends a `nxe-command-expand` column; expanding a row renders a
1515
+ * `<tr class="nxe-master-detail-row">` hosting the named sibling
1346
1516
  * `<ng-template nxeTemplate="...">` with `{ data, rowIndex, key }` — instantiated ONLY while
1347
1517
  * expanded, preserving the app's lazy nested-grid loading (`onRowExpanding` fires first).
1348
1518
  */
@@ -1417,12 +1587,14 @@ function cellValue(row, col) {
1417
1587
  }
1418
1588
  switch (col.dataType) {
1419
1589
  case 'number': {
1420
- const num = typeof raw === 'number' ? raw : Number(raw);
1590
+ // The same parseFloat deserializer the cell display uses, so a cell exports the number
1591
+ // it shows. dx exports that raw number too (exceljs/export.js:207-221).
1592
+ const num = nxeNumberValue(raw);
1421
1593
  if (isNaN(num)) {
1422
1594
  return { value: String(raw) };
1423
1595
  }
1424
- // dx number masks ≈ exceljs numFmt; convert single-quoted literals to double-quoted.
1425
- const mask = typeof col.format === 'string' ? col.format.replace(/'/g, '"') : undefined;
1596
+ // dx number masks ≈ exceljs numFmt, minus the scaling commas Excel reads differently.
1597
+ const mask = typeof col.format === 'string' ? nxeExcelNumberMask(col.format) : undefined;
1426
1598
  return { value: num, numFmt: mask };
1427
1599
  }
1428
1600
  case 'date': {
@@ -1533,15 +1705,14 @@ class NxeGridResizeController {
1533
1705
  event.stopPropagation(); // the header td above would otherwise start a group drag
1534
1706
  event.preventDefault();
1535
1707
  const host = this.hooks.host();
1536
- const table = host.querySelector('.dx-datagrid-content > .dx-datagrid-table');
1537
- const headerRow = nxeLeafHeaderRow(host);
1538
- if (!table || !headerRow) {
1708
+ const table = host.querySelector('.nxe-datagrid-content > .nxe-datagrid-table');
1709
+ if (!table) {
1539
1710
  return;
1540
1711
  }
1541
1712
  const cols = Array.from(table.querySelectorAll(':scope > colgroup > col'));
1542
- // The colgroup still counts command columns, so it keeps the lead offset; the header row
1543
- // does not, because with bands the command cells span down from the row above.
1544
- const headerCells = nxeHeaderDataCells(headerRow);
1713
+ // The colgroup still counts command columns, so it keeps the lead offset; the header
1714
+ // cells do not, because they are gathered by column rather than by cell position.
1715
+ const headerCells = nxeHeaderLeafCells(host);
1545
1716
  const colEl = cols[lead + visibleIndex];
1546
1717
  const cell = headerCells[visibleIndex];
1547
1718
  if (!colEl || !cell) {
@@ -1653,26 +1824,117 @@ class NxeGridResizeController {
1653
1824
  }
1654
1825
  }
1655
1826
 
1827
+ /**
1828
+ * The pager's page list and info text, as DevExtreme 24.2.3's pagination builds them.
1829
+ *
1830
+ * Pure functions, so the grid holds the only state (the sliding window, carried between renders
1831
+ * by a linkedSignal) and the rules can be specced without rendering a grid.
1832
+ */
1833
+ /** dx `maxPagesCount` (grid_core/pager/m_pager.js): up to this many pages are all listed. */
1834
+ const NXE_PAGER_MAX_PAGES = 10;
1835
+ /** dx `PAGES_LIMITER` (pagination/pages/large.js): the pages kept around the current one. */
1836
+ const WINDOW = 4;
1837
+ /** dx's `dxPagination-infoText` message. */
1838
+ const NXE_PAGER_INFO_TEXT = 'Page {0} of {1} ({2} items)';
1839
+ const pageItem = (page) => ({ key: String(page), page, separator: false });
1840
+ const separatorItem = (key) => ({ key, page: -1, separator: true });
1841
+ /**
1842
+ * The page list for `pageIndex` of `pageCount`, given the previous render's window.
1843
+ *
1844
+ * dx `PagesLarge` (pagination/pages/large.js). Ten pages or fewer are all listed. Beyond that the
1845
+ * list is the first page, a window of four, the last page, and a `. . .` separator wherever pages
1846
+ * are skipped. The window stays put while the new page is one of its inner pages (so clicking
1847
+ * through it does not scroll the numbers under the pointer), and otherwise moves so the page
1848
+ * lands one in from its edge.
1849
+ */
1850
+ function nxePagerWindow(pageCount, pageIndex, prev) {
1851
+ const slide = prev?.window ?? [];
1852
+ const reuse = prev?.reuse ?? [];
1853
+ if (pageCount <= NXE_PAGER_MAX_PAGES) {
1854
+ // dx leaves its window state untouched here, so it resumes if the count grows again.
1855
+ return { window: slide, reuse, items: Array.from({ length: pageCount }, (_, i) => pageItem(i)) };
1856
+ }
1857
+ const last = pageCount - 1;
1858
+ const reusable = reuse.length > 0 && reuse[reuse.length - 1] < last && reuse.includes(pageIndex);
1859
+ let next = slide;
1860
+ let nextReuse = reuse;
1861
+ if (!reusable) {
1862
+ let start;
1863
+ if (pageIndex === slide[0]) {
1864
+ start = pageIndex - 1;
1865
+ }
1866
+ else if (pageIndex === slide[slide.length - 1]) {
1867
+ start = pageIndex + 2 - WINDOW;
1868
+ }
1869
+ else if (pageIndex < WINDOW) {
1870
+ start = 1;
1871
+ }
1872
+ else if (pageIndex >= pageCount - WINDOW) {
1873
+ start = pageCount - WINDOW - 1;
1874
+ }
1875
+ else {
1876
+ start = pageIndex - 1;
1877
+ }
1878
+ // Only a window left over from a different page count can land outside 1..last-4; dx
1879
+ // would list pages that do not exist, so keep it inside the list.
1880
+ start = Math.min(Math.max(start, 1), pageCount - WINDOW - 1);
1881
+ next = Array.from({ length: WINDOW }, (_, i) => start + i);
1882
+ }
1883
+ const start = next[0];
1884
+ const kind = start === 1 ? 'high' : start + WINDOW === last ? 'low' : 'both';
1885
+ if (!reusable) {
1886
+ nextReuse = kind === 'both' ? next.slice(1, -1) : kind === 'high' ? next.slice(0, -1) : next.slice(1);
1887
+ }
1888
+ const items = [pageItem(0)];
1889
+ if (kind !== 'high') {
1890
+ items.push(separatorItem('low'));
1891
+ }
1892
+ items.push(...next.map(pageItem));
1893
+ if (kind !== 'low') {
1894
+ items.push(separatorItem('high'));
1895
+ }
1896
+ items.push(pageItem(last));
1897
+ return { window: next, reuse: nextReuse, items };
1898
+ }
1899
+ /**
1900
+ * dx's info text: `{0}` is the 1-based page, `{1}` the page count and `{2}` the item count
1901
+ * (pagination/info.js, formatted by core/utils/string `format`).
1902
+ */
1903
+ function nxePagerInfoText(template, pageIndex, pageCount, itemCount) {
1904
+ const values = [String(pageIndex + 1), String(pageCount), String(itemCount)];
1905
+ return template.replace(/\{(\d+)\}/g, (match, i) => values[Number(i)] ?? match);
1906
+ }
1907
+ /**
1908
+ * dx `getPageSizes` (grid_core/pager/m_pager.js): an explicit list as given; `'auto'` offers half,
1909
+ * the same and double the configured page size, and nothing when that size is 1 or less.
1910
+ */
1911
+ function nxePagerPageSizes(allowed, pageSize) {
1912
+ if (Array.isArray(allowed)) {
1913
+ return [...allowed];
1914
+ }
1915
+ return allowed === 'auto' && pageSize > 1 ? [Math.floor(pageSize / 2), pageSize, 2 * pageSize] : [];
1916
+ }
1917
+
1656
1918
  /* eslint-disable @typescript-eslint/no-explicit-any */
1657
1919
  /** Classify a rendered `<tr>` for dx's `rowType`, or null for rows dx does not report. */
1658
1920
  function nxeRowTypeOf(tr) {
1659
1921
  const c = tr.classList;
1660
- if (c.contains('dx-header-row')) {
1922
+ if (c.contains('nxe-header-row')) {
1661
1923
  return 'header';
1662
1924
  }
1663
- if (c.contains('dx-datagrid-filter-row')) {
1925
+ if (c.contains('nxe-datagrid-filter-row')) {
1664
1926
  return 'filter';
1665
1927
  }
1666
- if (c.contains('dx-data-row')) {
1928
+ if (c.contains('nxe-data-row')) {
1667
1929
  return 'data';
1668
1930
  }
1669
- if (c.contains('dx-group-footer-row')) {
1931
+ if (c.contains('nxe-group-footer-row')) {
1670
1932
  return 'groupFooter';
1671
1933
  }
1672
- if (c.contains('dx-group-row')) {
1934
+ if (c.contains('nxe-group-row')) {
1673
1935
  return 'group';
1674
1936
  }
1675
- if (tr.parentElement?.classList.contains('dx-datagrid-total-footer')) {
1937
+ if (tr.parentElement?.classList.contains('nxe-datagrid-total-footer')) {
1676
1938
  return 'totalFooter';
1677
1939
  }
1678
1940
  return null;
@@ -1690,13 +1952,30 @@ function flattenLeafColumns(cols) {
1690
1952
  }
1691
1953
  return out;
1692
1954
  }
1955
+ /**
1956
+ * A row's side of a collapsed border: the widest top or bottom border across the `<tr>` and its
1957
+ * cells, as computed (a `none` border computes to 0). Null, or anything but a row, has none.
1958
+ */
1959
+ function nxeRowEdge(row, side) {
1960
+ if (!(row instanceof HTMLTableRowElement)) {
1961
+ return 0;
1962
+ }
1963
+ const prop = `border-${side}-width`;
1964
+ let width = parseFloat(getComputedStyle(row).getPropertyValue(prop)) || 0;
1965
+ for (const cell of Array.from(row.cells)) {
1966
+ width = Math.max(width, parseFloat(getComputedStyle(cell).getPropertyValue(prop)) || 0);
1967
+ }
1968
+ return width;
1969
+ }
1693
1970
  /**
1694
1971
  * `nxe-data-grid` — drop-in replacement for DevExtreme `dx-data-grid` (read-only core, slice 9a).
1695
1972
  *
1696
- * Renders a single `<table>` emitting the dx DOM-class contract (`dx-widget dx-datagrid`,
1697
- * `dx-row`/`dx-header-row`/`dx-data-row`/`dx-datagrid-headers`/`dx-datagrid-rowsview`, `td`s) so
1698
- * the globally-loaded `dx.light.css` + repo `_grid.scss` overrides style it pixel-for-pixel
1699
- * (4px cell padding, #f8fafc header, #525252 header text, row/hover/alt colours). Columns are
1973
+ * Renders a single `<table>` under the library's own class namespace (`nxe-widget nxe-datagrid`,
1974
+ * `nxe-row`/`nxe-header-row`/`nxe-data-row`/`nxe-datagrid-headers`/`nxe-datagrid-rowsview`, `td`s).
1975
+ * The structure still mirrors dx's, name for name, so the visual contract carries over
1976
+ * (4px cell padding, #f8fafc header, #525252 header text, row/hover/alt colours) — but the
1977
+ * `dx-` prefix is gone; `provideNxeDxCompat()` re-emits it for stylesheets not yet migrated.
1978
+ * Columns are
1700
1979
  * collected from `nxe-column` content children; named `cellTemplate`s resolve to sibling
1701
1980
  * `<ng-template nxeTemplate="name">` blocks rendered with `{ data, rowIndex, columnIndex, ... }`
1702
1981
  * (the live row object, so in-cell `nxe-*` editors can write through `key.data.<field>`).
@@ -1710,7 +1989,10 @@ class NxeDataGridComponent {
1710
1989
  overlay = inject(Overlay);
1711
1990
  viewContainerRef = inject(ViewContainerRef);
1712
1991
  ngZone = inject(NgZone);
1992
+ destroyRef = inject(DestroyRef);
1713
1993
  defaults = inject(NXE_DEFAULT_OPTIONS);
1994
+ /** Locale separators for every number this grid renders — cells, summaries and group text. */
1995
+ separators = inject(NXE_NUMBER_SEPARATORS);
1714
1996
  /** Pointer drag engine: drag-to-group, chip drag-out (ungroup), chip/column reorder. */
1715
1997
  drag = new NxeGridDragController({
1716
1998
  zone: this.ngZone,
@@ -1720,7 +2002,7 @@ class NxeDataGridComponent {
1720
2002
  onChipMove: (from, to) => this.moveGroup(from, to),
1721
2003
  reorderEnabled: () => this.allowColumnReordering(),
1722
2004
  onColumnMove: (from, slot) => this.moveColumnByVisible(from, slot),
1723
- headerDataCells: () => nxeHeaderDataCells(nxeLeafHeaderRow(this.elementRef.nativeElement)),
2005
+ headerDataCells: () => nxeHeaderLeafCells(this.elementRef.nativeElement),
1724
2006
  });
1725
2007
  /** Pointer resize engine (dx allowColumnResizing). */
1726
2008
  resize = new NxeGridResizeController({
@@ -1796,13 +2078,19 @@ class NxeDataGridComponent {
1796
2078
  ...(ngDevMode ? [{ debugName: "widgetDefaults" }] : /* istanbul ignore next */ []));
1797
2079
  hoverStateEnabled = computed(() => this.hoverStateEnabledInput() ?? this.widgetDefaults().hoverStateEnabled ?? false, /* @ts-ignore */
1798
2080
  ...(ngDevMode ? [{ debugName: "hoverStateEnabled" }] : /* istanbul ignore next */ []));
1799
- showColumnLines = computed(() => this.showColumnLinesInput() ?? this.widgetDefaults().showColumnLines ?? false, /* @ts-ignore */
2081
+ // The literals are DevExtreme 24.2.3's generic defaults: showColumnLines true, showRowLines
2082
+ // false and wordWrapEnabled false (m_rows_view.js:1180-1182), showBorders false
2083
+ // (m_grid_view.js:743). dxTreeList shares them and changes them only for the Material themes
2084
+ // (tree_list/m_widget_base.js:39-52), so both hosts use the same literals. The library shipped
2085
+ // column lines off and row lines and wrapping on, the reverse of dx, so every grid that bound
2086
+ // neither option drew the wrong lines.
2087
+ showColumnLines = computed(() => this.showColumnLinesInput() ?? this.widgetDefaults().showColumnLines ?? true, /* @ts-ignore */
1800
2088
  ...(ngDevMode ? [{ debugName: "showColumnLines" }] : /* istanbul ignore next */ []));
1801
- showRowLines = computed(() => this.showRowLinesInput() ?? this.widgetDefaults().showRowLines ?? true, /* @ts-ignore */
2089
+ showRowLines = computed(() => this.showRowLinesInput() ?? this.widgetDefaults().showRowLines ?? false, /* @ts-ignore */
1802
2090
  ...(ngDevMode ? [{ debugName: "showRowLines" }] : /* istanbul ignore next */ []));
1803
2091
  showBorders = computed(() => this.showBordersInput() ?? this.widgetDefaults().showBorders ?? false, /* @ts-ignore */
1804
2092
  ...(ngDevMode ? [{ debugName: "showBorders" }] : /* istanbul ignore next */ []));
1805
- wordWrapEnabled = computed(() => this.wordWrapEnabledInput() ?? this.widgetDefaults().wordWrapEnabled ?? true, /* @ts-ignore */
2093
+ wordWrapEnabled = computed(() => this.wordWrapEnabledInput() ?? this.widgetDefaults().wordWrapEnabled ?? false, /* @ts-ignore */
1806
2094
  ...(ngDevMode ? [{ debugName: "wordWrapEnabled" }] : /* istanbul ignore next */ []));
1807
2095
  columnAutoWidth = computed(() => this.columnAutoWidthInput() ?? this.widgetDefaults().columnAutoWidth ?? false, /* @ts-ignore */
1808
2096
  ...(ngDevMode ? [{ debugName: "columnAutoWidth" }] : /* istanbul ignore next */ []));
@@ -1821,6 +2109,17 @@ class NxeDataGridComponent {
1821
2109
  ...(ngDevMode ? [{ debugName: "syncLookupFilterValues" }] : /* istanbul ignore next */ []));
1822
2110
  columnMinWidth = input(undefined, /* @ts-ignore */
1823
2111
  ...(ngDevMode ? [{ debugName: "columnMinWidth" }] : /* istanbul ignore next */ []));
2112
+ /** dx `height` — a bare number means px, anything else passes through. */
2113
+ height = input(undefined, /* @ts-ignore */
2114
+ ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
2115
+ heightStyle = computed(() => {
2116
+ const h = this.height();
2117
+ if (h === undefined || h === null || h === '') {
2118
+ return null;
2119
+ }
2120
+ return /^-?\d+(\.\d+)?$/.test(String(h)) ? `${h}px` : String(h);
2121
+ }, /* @ts-ignore */
2122
+ ...(ngDevMode ? [{ debugName: "heightStyle" }] : /* istanbul ignore next */ []));
1824
2123
  noDataTextInput = input(undefined, { ...(ngDevMode ? { debugName: "noDataTextInput" } : /* istanbul ignore next */ {}), alias: 'noDataText' });
1825
2124
  noDataText = computed(() => this.noDataTextInput() ?? this.widgetDefaults().noDataText ?? 'No data', /* @ts-ignore */
1826
2125
  ...(ngDevMode ? [{ debugName: "noDataText" }] : /* istanbul ignore next */ []));
@@ -1836,6 +2135,10 @@ class NxeDataGridComponent {
1836
2135
  groupPanelOptions = input(undefined, { ...(ngDevMode ? { debugName: "groupPanelOptions" } : /* istanbul ignore next */ {}), alias: 'groupPanel' });
1837
2136
  scrolling = input(undefined, /* @ts-ignore */
1838
2137
  ...(ngDevMode ? [{ debugName: "scrolling" }] : /* istanbul ignore next */ []));
2138
+ /**
2139
+ * dx `sorting`. Unbound means dx's default mode 'single'. `false` and `null` are accepted as dx
2140
+ * reads them - no mode, so headers keep their sort slot but a click never sorts.
2141
+ */
1839
2142
  sorting = input(undefined, /* @ts-ignore */
1840
2143
  ...(ngDevMode ? [{ debugName: "sorting" }] : /* istanbul ignore next */ []));
1841
2144
  allowColumnResizing = input(false, /* @ts-ignore */
@@ -1934,9 +2237,17 @@ class NxeDataGridComponent {
1934
2237
  */
1935
2238
  hf = new NxeHeaderFilterModel({
1936
2239
  rows: () => this.hfBaseRows(),
2240
+ fixedItems: (col) => {
2241
+ const items = col.headerFilterConfig()?.dataSource();
2242
+ // dx uses a supplied list verbatim: declared order, no (Blanks), and values that do
2243
+ // not occur in the data still offered - which is the reason to supply one.
2244
+ return items
2245
+ ? items.map((i) => ({ key: i.value, text: String(i.text ?? i.value ?? '') }))
2246
+ : null;
2247
+ },
1937
2248
  rawValue: (col, row) => this.getRaw(row, col.dataField()),
1938
2249
  displayText: (col, row) => this.cellText(col, row),
1939
- isDate: (col) => col.dataType() === 'date',
2250
+ isDate: (col) => this.colType(col) === 'date',
1940
2251
  sortByDisplayText: (col) => !!col.lookup(),
1941
2252
  boolLabel: (col, raw) => this.hfBoolLabel(col, raw),
1942
2253
  compare: (a, b) => this.compare(a, b),
@@ -1944,8 +2255,15 @@ class NxeDataGridComponent {
1944
2255
  hfColumn = this.hf.column;
1945
2256
  hfDraft = this.hf.draft;
1946
2257
  hfOverlayRef;
1947
- // Internal paging (dxo-paging present) working state
1948
- pageIndex = signal(0, /* @ts-ignore */
2258
+ /**
2259
+ * The current page.
2260
+ *
2261
+ * A `linkedSignal` over `nxe-paging`'s own `pageIndex` so the binding is genuinely two-way:
2262
+ * a host that sets `[(pageIndex)]` moves the grid, and `goToPage` writes back. Declaring the
2263
+ * input without this would satisfy an audit while doing nothing — the option would resolve
2264
+ * and be ignored, which is worse than not accepting it.
2265
+ */
2266
+ pageIndex = linkedSignal(() => this.pagingCmp()?.pageIndex() ?? 0, /* @ts-ignore */
1949
2267
  ...(ngDevMode ? [{ debugName: "pageIndex" }] : /* istanbul ignore next */ []));
1950
2268
  userPageSize = signal(null, /* @ts-ignore */
1951
2269
  ...(ngDevMode ? [{ debugName: "userPageSize" }] : /* istanbul ignore next */ []));
@@ -1975,12 +2293,16 @@ class NxeDataGridComponent {
1975
2293
  this.setColumnVisible(field, value);
1976
2294
  }
1977
2295
  },
1978
- getVisibleColumns: () => this.visibleColumns().map((c) => ({
1979
- caption: c.caption(),
1980
- dataField: c.dataField(),
1981
- dataType: c.dataType(),
1982
- format: c.format(),
1983
- })),
2296
+ getVisibleColumns: () => this.visibleColumns().map((c) => {
2297
+ const type = this.colType(c);
2298
+ return {
2299
+ caption: c.caption(),
2300
+ dataField: c.dataField(),
2301
+ // An inferred 'object' column exports as text, as an untyped one always did.
2302
+ dataType: type === 'object' ? 'string' : type,
2303
+ format: c.format(),
2304
+ };
2305
+ }),
1984
2306
  getVisibleRows: () => this.displayRows(),
1985
2307
  totalCount: () => this.displayRows().length,
1986
2308
  getRootNode: () => ({
@@ -2055,6 +2377,14 @@ class NxeDataGridComponent {
2055
2377
  this.visibleColumns();
2056
2378
  untracked(() => this.emitPreparedEvents());
2057
2379
  });
2380
+ // A cellTemplate that draws a check box centres its column, header and cells, the way a
2381
+ // boolean column is centred. Only the rendered cells can say what a template draws, so
2382
+ // this reads them after render; the DOM walk is untracked, like emitPreparedEvents.
2383
+ afterRenderEffect(() => {
2384
+ this.renderRows();
2385
+ this.visibleColumns();
2386
+ untracked(() => this.detectTemplateCheckboxColumns());
2387
+ });
2058
2388
  // dx onContentReady parity: fire after the rendered row set reaches the DOM.
2059
2389
  afterRenderEffect(() => {
2060
2390
  this.renderRows();
@@ -2072,7 +2402,7 @@ class NxeDataGridComponent {
2072
2402
  untracked(() => this.scheduleAutoWidthMeasure());
2073
2403
  });
2074
2404
  afterNextRender(() => {
2075
- const rv = this.elementRef.nativeElement.querySelector('.dx-datagrid-rowsview');
2405
+ const rv = this.elementRef.nativeElement.querySelector('.nxe-datagrid-rowsview');
2076
2406
  if (rv && typeof ResizeObserver !== 'undefined') {
2077
2407
  this.resizeObserver = new ResizeObserver((entries) => {
2078
2408
  const w = Math.round(entries[0]?.contentRect?.width ?? 0);
@@ -2083,6 +2413,22 @@ class NxeDataGridComponent {
2083
2413
  this.resizeObserver.observe(rv);
2084
2414
  }
2085
2415
  });
2416
+ // The free-space row: re-read whenever the rows view (the grid's height) or its table (the
2417
+ // rows) changes size. The paged case needs no reading beyond the row height, so a page
2418
+ // change that keeps the table's height still resolves through `freeSpace` alone.
2419
+ afterNextRender(() => {
2420
+ const host = this.elementRef.nativeElement;
2421
+ // This grid's own elements: a nested master-detail grid's come later in the document.
2422
+ const rv = host.querySelector('.nxe-datagrid-rowsview');
2423
+ const table = rv?.querySelector(':scope > .nxe-datagrid-content > .nxe-datagrid-table');
2424
+ if (!rv || !table || typeof ResizeObserver === 'undefined') {
2425
+ return;
2426
+ }
2427
+ const observer = new ResizeObserver(() => this.measureFreeSpace(rv, table));
2428
+ observer.observe(rv);
2429
+ observer.observe(table);
2430
+ this.destroyRef.onDestroy(() => observer.disconnect());
2431
+ });
2086
2432
  }
2087
2433
  // --- Column auto-width engine (dx columnAutoWidth parity) ----------------------------------
2088
2434
  autoWidths = signal(null, /* @ts-ignore */
@@ -2113,17 +2459,16 @@ class NxeDataGridComponent {
2113
2459
  }
2114
2460
  measureAutoWidths() {
2115
2461
  const host = this.elementRef.nativeElement;
2116
- const table = host.querySelector('.dx-datagrid-content > .dx-datagrid-table');
2117
- const rowsview = host.querySelector('.dx-datagrid-rowsview');
2118
- const headerRow = nxeLeafHeaderRow(host);
2119
- if (!table || !rowsview || !headerRow) {
2462
+ const table = host.querySelector('.nxe-datagrid-content > .nxe-datagrid-table');
2463
+ const rowsview = host.querySelector('.nxe-datagrid-rowsview');
2464
+ if (!table || !rowsview) {
2120
2465
  return;
2121
2466
  }
2122
2467
  const cols = this.visibleColumns();
2123
2468
  const lead = this.groupLevelCount() + (this.masterDetailEnabled() ? 1 : 0) + (this.showCheckboxes() ? 1 : 0);
2124
- // Indexed among data cells, not by a lead offset: command cells live in the FIRST header
2125
- // row and span down, so a banded grid's leaf row has none of them.
2126
- const cells = nxeHeaderDataCells(headerRow);
2469
+ // One cell per visible column, gathered across the header rows: with bands a top-level
2470
+ // column sits in the first row and spans down, so no single row holds them all.
2471
+ const cells = nxeHeaderLeafCells(host);
2127
2472
  const container = rowsview.clientWidth;
2128
2473
  if (cells.length !== cols.length || container <= 0) {
2129
2474
  return;
@@ -2134,10 +2479,18 @@ class NxeDataGridComponent {
2134
2479
  const prevLayout = table.style.tableLayout;
2135
2480
  const prevWidth = table.style.width;
2136
2481
  const prevColWidths = colEls.map((c) => c.style.width);
2482
+ // dx's best-fit pass moves the header tbody into the rows table as `dx-header
2483
+ // dx-datagrid-best-fit` (m_grid_view.js:187-206). Its rules unfloat the indicator block and
2484
+ // show every `dx-sort-none` placeholder at 14px, so each sortable column measures 17px wider
2485
+ // (34px centred, with the hidden mirror) whether sorted or not - and sorting never changes a
2486
+ // width. The class goes on our thead, which is that tbody's counterpart.
2487
+ const thead = table.querySelector(':scope > thead');
2488
+ thead?.classList.add('nxe-datagrid-best-fit');
2137
2489
  table.style.tableLayout = 'auto';
2138
2490
  table.style.width = 'max-content';
2139
2491
  colEls.forEach((c) => (c.style.width = ''));
2140
2492
  const measured = cells.map((c) => c.getBoundingClientRect().width);
2493
+ thead?.classList.remove('nxe-datagrid-best-fit');
2141
2494
  table.style.tableLayout = prevLayout;
2142
2495
  table.style.width = prevWidth;
2143
2496
  colEls.forEach((c, i) => (c.style.width = prevColWidths[i]));
@@ -2385,6 +2738,13 @@ class NxeDataGridComponent {
2385
2738
  ...(ngDevMode ? [{ debugName: "effectivePageSize" }] : /* istanbul ignore next */ []));
2386
2739
  pageCount = computed(() => this.pagingEnabled() ? Math.max(1, Math.ceil(this.displayRows().length / this.effectivePageSize())) : 1, /* @ts-ignore */
2387
2740
  ...(ngDevMode ? [{ debugName: "pageCount" }] : /* istanbul ignore next */ []));
2741
+ /**
2742
+ * The page shown: `pageIndex` held inside the page count. A filter can leave the bound index
2743
+ * past the last page; dx moves it back (m_data_source_adapter.js:454), and the rows, the
2744
+ * pager's selected button, its info text and prev/next all read this one value.
2745
+ */
2746
+ currentPage = computed(() => Math.max(0, Math.min(this.pageIndex(), this.pageCount() - 1)), /* @ts-ignore */
2747
+ ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
2388
2748
  /** The filtered/sorted rows visible on the current page (all rows when paging is off). */
2389
2749
  pagedRows = computed(() => {
2390
2750
  const rows = this.displayRows();
@@ -2392,25 +2752,45 @@ class NxeDataGridComponent {
2392
2752
  return rows;
2393
2753
  }
2394
2754
  const ps = this.effectivePageSize();
2395
- const pi = Math.min(this.pageIndex(), this.pageCount() - 1);
2755
+ const pi = this.currentPage();
2396
2756
  return rows.slice(pi * ps, pi * ps + ps);
2397
2757
  }, /* @ts-ignore */
2398
2758
  ...(ngDevMode ? [{ debugName: "pagedRows" }] : /* istanbul ignore next */ []));
2759
+ /**
2760
+ * dx `PagerView.isVisible` (m_pager.js): `true` always, `'auto'` only while there is more than
2761
+ * one page to move between, and never under virtual or infinite scrolling.
2762
+ */
2399
2763
  pagerVisible = computed(() => {
2400
2764
  const p = this.pagerCmp();
2401
2765
  if (!p) {
2402
2766
  return false;
2403
2767
  }
2404
- return p.visible() === true || (p.visible() === 'auto' && this.pagingEnabled());
2768
+ const mode = this.scrolling()?.mode;
2769
+ return (p.visible() === true ||
2770
+ (p.visible() === 'auto' &&
2771
+ this.pagingEnabled() &&
2772
+ this.pageCount() > 1 &&
2773
+ mode !== 'virtual' &&
2774
+ mode !== 'infinite'));
2405
2775
  }, /* @ts-ignore */
2406
2776
  ...(ngDevMode ? [{ debugName: "pagerVisible" }] : /* istanbul ignore next */ []));
2407
- pageSizes = computed(() => {
2408
- const a = this.pagerCmp()?.allowedPageSizes();
2409
- return Array.isArray(a) ? a : [this.effectivePageSize()];
2410
- }, /* @ts-ignore */
2777
+ pageSizes = computed(() => nxePagerPageSizes(this.pagerCmp()?.allowedPageSizes(), this.pagingCmp()?.pageSize() ?? 20), /* @ts-ignore */
2411
2778
  ...(ngDevMode ? [{ debugName: "pageSizes" }] : /* istanbul ignore next */ []));
2412
- pageNumbers = computed(() => Array.from({ length: this.pageCount() }, (_, i) => i), /* @ts-ignore */
2413
- ...(ngDevMode ? [{ debugName: "pageNumbers" }] : /* istanbul ignore next */ []));
2779
+ /** dx's small mode: a page-size drop-down, and the navigation buttons whatever the option says. */
2780
+ pagerCompact = computed(() => this.pagerCmp()?.displayMode() === 'compact', /* @ts-ignore */
2781
+ ...(ngDevMode ? [{ debugName: "pagerCompact" }] : /* istanbul ignore next */ []));
2782
+ /** dx `getRenderPrevButton` (pages/page_index_selector.js): `!isLargeDisplayMode || showNavigationButtons`. */
2783
+ pagerNavVisible = computed(() => this.pagerCompact() || !!this.pagerCmp()?.showNavigationButtons(), /* @ts-ignore */
2784
+ ...(ngDevMode ? [{ debugName: "pagerNavVisible" }] : /* istanbul ignore next */ []));
2785
+ /**
2786
+ * The page buttons and `. . .` separators. A linkedSignal because dx's list keeps a sliding
2787
+ * window between renders (pages/large.js): the previous window decides whether the next one
2788
+ * moves.
2789
+ */
2790
+ pagerWindow = linkedSignal({ ...(ngDevMode ? { debugName: "pagerWindow" } : /* istanbul ignore next */ {}), source: () => ({ count: this.pageCount(), index: this.currentPage() }),
2791
+ computation: (src, prev) => nxePagerWindow(src.count, src.index, prev?.value) });
2792
+ pageItems = computed(() => this.pagerWindow().items, /* @ts-ignore */
2793
+ ...(ngDevMode ? [{ debugName: "pageItems" }] : /* istanbul ignore next */ []));
2414
2794
  // --- Tree-list hierarchy (dx-tree-list) -----------------------------------------------------
2415
2795
  /** True when the host element is `<nxe-tree-list>` (vs `<nxe-data-grid>`). */
2416
2796
  treeMode = this.elementRef.nativeElement.tagName.toLowerCase() === 'nxe-tree-list';
@@ -2535,10 +2915,10 @@ class NxeDataGridComponent {
2535
2915
  for (const item of items) {
2536
2916
  const text = this.summaryText(item, bucket);
2537
2917
  if (item.showInGroupFooter()) {
2538
- footerSums.set(item.column(), text);
2918
+ footerSums.set(this.summaryTarget(item), text);
2539
2919
  }
2540
2920
  else if (item.alignByColumn()) {
2541
- alignedSums.set(item.showInColumn() ?? item.column(), text);
2921
+ alignedSums.set(this.summaryTarget(item), text);
2542
2922
  }
2543
2923
  else {
2544
2924
  captionParts.push(text);
@@ -2575,6 +2955,18 @@ class NxeDataGridComponent {
2575
2955
  return out;
2576
2956
  }, /* @ts-ignore */
2577
2957
  ...(ngDevMode ? [{ debugName: "renderRows" }] : /* istanbul ignore next */ []));
2958
+ /**
2959
+ * The dataField a group summary renders under: dx `_calculateSummaryCells` (m_summary.js:443)
2960
+ * takes `showInColumn` when it names a column, else the item's own `column`.
2961
+ *
2962
+ * Keying footers on `column()` alone put an item that names only `showInColumn` (broker's
2963
+ * `name="totalSumInsured" showInColumn="totalSumInsured" summaryType="custom"`) under its
2964
+ * empty `column` - i.e. in whichever column happens to have no dataField.
2965
+ */
2966
+ summaryTarget(item) {
2967
+ const target = item.showInColumn();
2968
+ return target && this.columnByField().has(target) ? target : item.column();
2969
+ }
2578
2970
  /** Computes + formats a summary item over a set of rows. */
2579
2971
  summaryText(item, rows) {
2580
2972
  const field = item.column();
@@ -2605,7 +2997,9 @@ class NxeDataGridComponent {
2605
2997
  }
2606
2998
  }
2607
2999
  const fmt = item.valueFormat();
2608
- const valueText = typeof fmt === 'string' ? formatNumber(value, parseNumberFormat(fmt)) : String(value);
3000
+ const valueText = typeof fmt === 'string'
3001
+ ? formatNumber(value, parseNumberFormat(fmt), this.separators)
3002
+ : String(value);
2609
3003
  const ct = item.customizeText();
2610
3004
  const text = ct ? ct({ value, valueText }) : valueText;
2611
3005
  return item.displayFormat().replace('{0}', text);
@@ -2640,14 +3034,16 @@ class NxeDataGridComponent {
2640
3034
  const next = Math.max(0, Math.min(index, this.pageCount() - 1));
2641
3035
  if (next !== this.pageIndex()) {
2642
3036
  this.pageIndex.set(next);
3037
+ // Write back so `[(pageIndex)]` reflects a page the grid moved to itself.
3038
+ this.pagingCmp()?.pageIndex.set(next);
2643
3039
  this.onOptionChanged.emit({ name: 'paging', fullName: 'paging.pageIndex', value: next });
2644
3040
  }
2645
3041
  }
2646
3042
  prevPage() {
2647
- this.goToPage(this.pageIndex() - 1);
3043
+ this.goToPage(this.currentPage() - 1);
2648
3044
  }
2649
3045
  nextPage() {
2650
- this.goToPage(this.pageIndex() + 1);
3046
+ this.goToPage(this.currentPage() + 1);
2651
3047
  }
2652
3048
  changePageSize(size) {
2653
3049
  this.userPageSize.set(size);
@@ -2656,13 +3052,8 @@ class NxeDataGridComponent {
2656
3052
  onPageSizeSelect(event) {
2657
3053
  this.changePageSize(Number(event.target.value));
2658
3054
  }
2659
- pageInfo = computed(() => {
2660
- const total = this.displayRows().length;
2661
- const ps = this.effectivePageSize();
2662
- const start = total === 0 ? 0 : this.pageIndex() * ps + 1;
2663
- const end = Math.min((this.pageIndex() + 1) * ps, total);
2664
- return `${start}-${end} of ${total}`;
2665
- }, /* @ts-ignore */
3055
+ /** dx's info text, `Page {0} of {1} ({2} items)` unless `infoText` says otherwise (pagination/info.js). */
3056
+ pageInfo = computed(() => nxePagerInfoText(this.pagerCmp()?.infoText() ?? NXE_PAGER_INFO_TEXT, this.currentPage(), this.pageCount(), this.displayRows().length), /* @ts-ignore */
2666
3057
  ...(ngDevMode ? [{ debugName: "pageInfo" }] : /* istanbul ignore next */ []));
2667
3058
  // --- Runtime group state (drag-to-group backbone) --------------------------------------------
2668
3059
  static fieldArrayEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
@@ -2948,25 +3339,40 @@ class NxeDataGridComponent {
2948
3339
  const next = this.columnResizingMode() === 'nextColumn' ? this.visibleColumns()[ci + 1] ?? null : null;
2949
3340
  this.resize.start(col.dataField(), next?.dataField() ?? null, ci, this.leadCellCount(), event);
2950
3341
  }
3342
+ /** A visible column's position, for cells rendered outside the main `@for` index. */
3343
+ visibleIndexOf(col) {
3344
+ return this.visibleColumns().indexOf(col);
3345
+ }
2951
3346
  /** Command columns preceding the data columns — the offset dx's 1-based aria-colindex counts from. */
2952
3347
  leadCellCount() {
2953
3348
  return (this.groupLevelCount() +
2954
3349
  (this.masterDetailEnabled() ? 1 : 0) +
2955
3350
  (this.showCheckboxes() ? 1 : 0));
2956
3351
  }
3352
+ // dx numbers every cell of a rows-view row, command cells included (`_setCellAriaAttributes`,
3353
+ // m_columns_view.js:681-693): the group indents are 1..n, then the master-detail expander, the
3354
+ // selection column, the data columns and the edit column. Apps address cells by it - bright's
3355
+ // merge helper lifts `td[aria-colindex="1"]` out of a merged row, which in a grouped grid is
3356
+ // the indent cell - so the command cells carry it too.
3357
+ /** The master-detail expander's aria-colindex: straight after the group indents. */
3358
+ expandColIndex = computed(() => this.groupLevelCount() + 1, /* @ts-ignore */
3359
+ ...(ngDevMode ? [{ debugName: "expandColIndex" }] : /* istanbul ignore next */ []));
3360
+ /** The selection column's aria-colindex: after the indents and the expander. */
3361
+ selectColIndex = computed(() => this.groupLevelCount() + (this.masterDetailEnabled() ? 1 : 0) + 1, /* @ts-ignore */
3362
+ ...(ngDevMode ? [{ debugName: "selectColIndex" }] : /* istanbul ignore next */ []));
3363
+ /** The row-edit command column's aria-colindex: after the last data column. */
3364
+ editColIndex = computed(() => this.leadCellCount() + this.visibleColumns().length + 1, /* @ts-ignore */
3365
+ ...(ngDevMode ? [{ debugName: "editColIndex" }] : /* istanbul ignore next */ []));
2957
3366
  /** Resize commit: freeze all rendered widths (dx post-resize behavior) + emit width events. */
2958
3367
  commitColumnResize(changes) {
2959
3368
  const map = new Map(this.widthOverride());
2960
- const headerRow = nxeLeafHeaderRow(this.elementRef.nativeElement);
2961
- if (headerRow) {
2962
- const cells = nxeHeaderDataCells(headerRow);
2963
- this.visibleColumns().forEach((c, i) => {
2964
- const cell = cells[i];
2965
- if (cell) {
2966
- map.set(c.dataField(), Math.round(cell.getBoundingClientRect().width * 100) / 100);
2967
- }
2968
- });
2969
- }
3369
+ const cells = nxeHeaderLeafCells(this.elementRef.nativeElement);
3370
+ this.visibleColumns().forEach((c, i) => {
3371
+ const cell = cells[i];
3372
+ if (cell) {
3373
+ map.set(c.dataField(), Math.round(cell.getBoundingClientRect().width * 100) / 100);
3374
+ }
3375
+ });
2970
3376
  for (const ch of changes) {
2971
3377
  map.set(ch.field, Math.round(ch.to * 100) / 100);
2972
3378
  }
@@ -3093,8 +3499,51 @@ class NxeDataGridComponent {
3093
3499
  return;
3094
3500
  }
3095
3501
  event.stopPropagation();
3502
+ // A boolean cell already shows its editor (dx showEditorAlways); only its checkbox
3503
+ // toggles, so a click beside the box does not swap anything in.
3504
+ if (this.booleanInPlace(col)) {
3505
+ return;
3506
+ }
3096
3507
  this.editingCell.set({ row, field: col.dataField() });
3097
3508
  }
3509
+ /**
3510
+ * dx `readOnly: !options.setValue` (m_editing.js:210): a grid checkbox is editable only where
3511
+ * dx gives the cell a setValue - cell mode on an editable column (m_editing.js:1832-1847). Row
3512
+ * mode's resting rows stay read-only; the row being edited renders the editor instead.
3513
+ */
3514
+ booleanReadOnly(col) {
3515
+ return !(this.cellEditingEnabled() && this.editableCell(col));
3516
+ }
3517
+ /**
3518
+ * A boolean cell that is its own editor: cell mode, editable, and no editCellTemplate asking
3519
+ * to take over. The resting checkbox toggles in place, as dx's always-shown dxCheckBox does.
3520
+ */
3521
+ booleanInPlace(col) {
3522
+ return this.isBooleanCell(col) && !this.booleanReadOnly(col) && !col.editCellTemplate();
3523
+ }
3524
+ /** A resting boolean checkbox was clicked: toggle it when it is editable, else let it be. */
3525
+ onBooleanCellClick(row, col, event) {
3526
+ if (!this.booleanInPlace(col)) {
3527
+ return;
3528
+ }
3529
+ event.stopPropagation();
3530
+ this.commitCellEdit(row, col, this.getRaw(row, col.dataField()) !== true);
3531
+ }
3532
+ /**
3533
+ * The checkbox editor's state: the buffered value in the row being edited, else the row's.
3534
+ * Read as the native editor it replaces read it - any value but '' and 'false' is checked.
3535
+ */
3536
+ editorChecked(row, col) {
3537
+ const text = this.editorInputValue(row, col);
3538
+ return !!text && text !== 'false';
3539
+ }
3540
+ /** A click or Space on the checkbox editor. Space must not scroll the page or reach the grid. */
3541
+ toggleEditorCheck(row, col, mode, event) {
3542
+ event.preventDefault();
3543
+ event.stopPropagation();
3544
+ const next = !this.editorChecked(row, col);
3545
+ mode === 'cell' ? this.commitCellEdit(row, col, next) : this.bufferRowEdit(col, next);
3546
+ }
3098
3547
  /** dx cell-mode semantics: commit to the row on blur/Enter (client-side array write-through). */
3099
3548
  commitCellEdit(row, col, rawInput) {
3100
3549
  this.writeCell(row, col, rawInput);
@@ -3136,7 +3585,7 @@ class NxeDataGridComponent {
3136
3585
  writeCell(row, col, rawInput) {
3137
3586
  const field = col.dataField();
3138
3587
  let value = rawInput;
3139
- if (col.dataType() === 'number' && typeof rawInput === 'string') {
3588
+ if (this.colType(col) === 'number' && typeof rawInput === 'string') {
3140
3589
  const n = Number(rawInput.replace(/,/g, ''));
3141
3590
  value = rawInput.trim() === '' || isNaN(n) ? null : n;
3142
3591
  }
@@ -3472,6 +3921,231 @@ class NxeDataGridComponent {
3472
3921
  ...(ngDevMode ? [{ debugName: "displayRows" }] : /* istanbul ignore next */ []));
3473
3922
  loading = computed(() => this.dataSource() == null && this.loadPanel()?.enabled !== false, /* @ts-ignore */
3474
3923
  ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
3924
+ /**
3925
+ * dx `isEmpty()` = `!items().length` (m_data_controller.js:1044-1046), toggled as `dx-empty`
3926
+ * on the rows view in `_renderCore` (m_rows_view.js:710) whether loading or not.
3927
+ *
3928
+ * Keyed on `renderRows` - what the `<tbody>` iterates, dx's `items()` - not `displayRows`. The
3929
+ * two agree for flat and grouped grids (`pagedRows` clamps the page), but a tree list builds
3930
+ * `renderRows` from the unfiltered source while `displayRows` applies the filters, so a tree
3931
+ * whose filter matches nothing still renders rows and must not also show an empty body.
3932
+ */
3933
+ isEmpty = computed(() => this.renderRows().length === 0, /* @ts-ignore */
3934
+ ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
3935
+ // --- Free-space row (dx m_rows_view.js updateFreeSpaceRowHeight, :816-861) --------------------
3936
+ /**
3937
+ * What the last reading of this grid's own layout found.
3938
+ *
3939
+ * - `bounded` is dx's `hasHeight`: the rows view is sized by the grid, not by its rows.
3940
+ * - `room` is dx's `elementHeightWithoutScrollbar - contentHeight` (m_rows_view.js:840-842): the
3941
+ * rows view's inner height less the table as it stands with the free-space row empty and the
3942
+ * last row keeping its line. Neither the row's height nor the last-row-border state changes
3943
+ * it, so what it decides cannot change the next reading.
3944
+ * - `fill` is dx's `resultHeight` (:844-848): the height that leaves the table 1px short of
3945
+ * the rows view (`_getHeightCorrection`, 1 in Chrome).
3946
+ * - `rowHeight` is dx's `_rowHeight`, the average rendered row. It sizes the gap that a short
3947
+ * last page leaves.
3948
+ */
3949
+ freeSpaceMeasure = signal({ bounded: false, room: 0, fill: 0, rowHeight: 0 }, { ...(ngDevMode ? { debugName: "freeSpaceMeasure" } : /* istanbul ignore next */ {}), equal: (a, b) => a.bounded === b.bounded &&
3950
+ Math.abs(a.room - b.room) < 0.01 &&
3951
+ Math.abs(a.fill - b.fill) < 0.01 &&
3952
+ Math.abs(a.rowHeight - b.rowHeight) < 0.01 });
3953
+ /**
3954
+ * dx's three cases, in its order:
3955
+ * - no rows: the row shows at 0px, so the last row keeps no border to lose (:857-860);
3956
+ * - a bounded grid whose rows leave room: the row fills it, less the 1px dx subtracts in
3957
+ * Chrome (`_getHeightCorrection`), and column lines run to the bottom (:837-855);
3958
+ * - an unbounded grid on a short page of several: the row holds the missing rows' height, so
3959
+ * the grid keeps its height from page to page (:823-835).
3960
+ *
3961
+ * `visible` drives the last row's border in a bordered grid (`_updateLastRowBorder`, :931-937).
3962
+ */
3963
+ freeSpace = computed(() => {
3964
+ if (this.isEmpty()) {
3965
+ return { height: 0, visible: true };
3966
+ }
3967
+ const m = this.freeSpaceMeasure();
3968
+ if (m.bounded) {
3969
+ return m.room > 0
3970
+ ? { height: Math.max(0, m.fill), visible: true }
3971
+ : { height: 0, visible: false };
3972
+ }
3973
+ const missing = this.effectivePageSize() - this.pagedRows().length;
3974
+ const mode = this.scrolling()?.mode;
3975
+ if (!this.treeMode &&
3976
+ this.pagingEnabled() &&
3977
+ missing > 0 &&
3978
+ this.pageCount() > 1 &&
3979
+ mode !== 'virtual' &&
3980
+ mode !== 'infinite') {
3981
+ return { height: missing * m.rowHeight, visible: true };
3982
+ }
3983
+ return { height: 0, visible: false };
3984
+ }, /* @ts-ignore */
3985
+ ...(ngDevMode ? [{ debugName: "freeSpace" }] : /* istanbul ignore next */ []));
3986
+ freeSpaceHeight = computed(() => this.freeSpace().height, /* @ts-ignore */
3987
+ ...(ngDevMode ? [{ debugName: "freeSpaceHeight" }] : /* istanbul ignore next */ []));
3988
+ freeSpaceVisible = computed(() => this.freeSpace().visible, /* @ts-ignore */
3989
+ ...(ngDevMode ? [{ debugName: "freeSpaceVisible" }] : /* istanbul ignore next */ []));
3990
+ /**
3991
+ * One reading of the rows view, from the ResizeObserver that watches it and its table.
3992
+ *
3993
+ * dx hides the free-space row and then measures (m_rows_view.js:837-848). This reading cannot
3994
+ * hide it - that would shrink the document and could clamp its scroll position - so it takes
3995
+ * the row's part of the table back out arithmetically, from quantities that do not move with
3996
+ * what the reading decides:
3997
+ *
3998
+ * - The row's box comes out whole: the height it was given, or with none, its halves of the
3999
+ * collapsed borders above and below it (0.5px under a last row with a 1px frame).
4000
+ * - With no free space shown, a bordered grid's last row gives its line to the box
4001
+ * (`nxe-last-row-border`, `border-bottom-width:0`), which makes the table a line shorter.
4002
+ * That line is put back, at the grid's line width, so the answer is the same either way.
4003
+ *
4004
+ * Read naively off the table, either one feeds back: the half-line reads as room, which shows
4005
+ * the row, which gives the line back, which removes the room - a flip every frame - and a
4006
+ * filled row in a rows view that hugs its table reads as its own room, 1px less each time.
4007
+ *
4008
+ * The second case gets its own rule. A rows view that hugs its table while the row adds height
4009
+ * of its own is sized by that row, so it has no room, whatever `height` says: the row lets go
4010
+ * at once. (A bounded grid resized to exactly its table height less the 1px lands here too, and
4011
+ * fills again on the next reading.)
4012
+ *
4013
+ * Whether the grid is bounded (dx `hasHeight`): an explicit `height` other than 'auto' always
4014
+ * is (m_grid_view.js:597-604, `isHeightSpecified`), even a percentage that resolves to auto,
4015
+ * and then has no room while its rows view hugs. Otherwise a rows view that does not hug is
4016
+ * bounded by CSS, and one that hugs while the row holds free space is not, so a grid that
4017
+ * loses its height lets go of the space. When they coincide with no free space held (an exact
4018
+ * fit), the previous answer stands.
4019
+ */
4020
+ measureFreeSpace(rowsView, table) {
4021
+ const tbody = table.tBodies[0];
4022
+ const fsRow = tbody?.lastElementChild;
4023
+ if (!(fsRow instanceof HTMLTableRowElement) ||
4024
+ !fsRow.classList.contains('nxe-freespace-row') ||
4025
+ this.isEmpty()) {
4026
+ return;
4027
+ }
4028
+ const prev = this.freeSpaceMeasure();
4029
+ const last = fsRow.previousElementSibling;
4030
+ // The collapsed borders above and below the row: the last row's line, and the total
4031
+ // footer's top line when there is one (the table's own edge otherwise, which has none).
4032
+ const above = Math.max(nxeRowEdge(last, 'bottom'), nxeRowEdge(fsRow, 'top'));
4033
+ const below = Math.max(nxeRowEdge(fsRow, 'bottom'), nxeRowEdge(table.tFoot?.rows[0], 'top'));
4034
+ // The last row's line as it is with free space shown. The last-row-border rule strips it
4035
+ // to 0 only on a data row; it is drawn at the grid's line width.
4036
+ let line = above;
4037
+ if (rowsView.classList.contains('nxe-last-row-border') && last?.classList.contains('nxe-data-row')) {
4038
+ const token = parseFloat(getComputedStyle(rowsView).getPropertyValue('--nxe-ui-grid-line-width'));
4039
+ line = Number.isFinite(token) ? token : 1;
4040
+ }
4041
+ const fsHeight = fsRow.getBoundingClientRect().height;
4042
+ // The row adds height of its own, beyond its halves of the borders around it.
4043
+ const holding = fsHeight > (above + below) / 2 + 0.01;
4044
+ const tableHeight = table.getBoundingClientRect().height;
4045
+ // Everything but the free-space row, the last row keeping its line: dx's `rowsHeight`.
4046
+ const rowsHeight = tableHeight - fsHeight - above / 2 + line / 2;
4047
+ // And with the row empty: dx's `contentHeight`.
4048
+ const contentHeight = rowsHeight + (line + below) / 2;
4049
+ // Less borders and a horizontal scrollbar: dx's `elementHeightWithoutScrollbar`.
4050
+ const inner = rowsView.getBoundingClientRect().height - (rowsView.offsetHeight - rowsView.clientHeight);
4051
+ const h = this.heightStyle();
4052
+ const hugging = Math.abs(inner - tableHeight) < 0.5;
4053
+ const bounded = (h !== null && h !== 'auto') || (hugging ? prev.bounded && !holding : true);
4054
+ const rows = tbody.rows.length - 1;
4055
+ this.freeSpaceMeasure.set({
4056
+ bounded,
4057
+ room: hugging && holding ? 0 : inner - contentHeight,
4058
+ fill: inner - rowsHeight - 1,
4059
+ rowHeight: rows > 0 ? (tbody.getBoundingClientRect().height - fsHeight) / rows : prev.rowHeight,
4060
+ });
4061
+ }
4062
+ /**
4063
+ * dx `updateColumnDataTypes` (m_columns_controller.js:914-977): a column with no declared
4064
+ * `dataType` takes the type of its values, and mixed values make it 'string'.
4065
+ *
4066
+ * A lookup column is typed by its DISPLAY values - dx's `lookup.dataType`, which is what dx
4067
+ * aligns, formats and edits a lookup by (:901) - and a value with no lookup match contributes
4068
+ * nothing: dx's `lookup.calculateCellValue` returns `valueMap[value]`, undefined for a missing
4069
+ * key, so `getValueDataType` skips it (:946). A not-yet-loaded list must not type the column.
4070
+ *
4071
+ * Once inferred, a type sticks. dx infers only while `dataType` is unset (:935) and stops once
4072
+ * every column is typed (:289), so a later load of mixed values cannot flip a column; the
4073
+ * previous value of this linkedSignal carries that. The refresh tick is in the source because
4074
+ * rows mutated in place (inline editing, `refresh()`) can type a column that was all blanks.
4075
+ *
4076
+ * dx samples the loaded page (or the first group's leaves); this scans the client array,
4077
+ * which only differs when the types are mixed beyond that sample.
4078
+ */
4079
+ inferredTypes = linkedSignal({ ...(ngDevMode ? { debugName: "inferredTypes" } : /* istanbul ignore next */ {}), source: () => {
4080
+ this.refreshTick();
4081
+ return { rows: this.dataSource() ?? [], cols: this.columns() };
4082
+ },
4083
+ computation: ({ rows, cols }, previous) => {
4084
+ const out = new Map();
4085
+ for (const col of cols) {
4086
+ const kept = previous?.value.get(col);
4087
+ // A rebound dataField is a different column as far as its values are concerned.
4088
+ if (kept && kept.field === col.dataField()) {
4089
+ out.set(col, kept);
4090
+ }
4091
+ }
4092
+ if (!rows.length) {
4093
+ return out;
4094
+ }
4095
+ for (const col of cols) {
4096
+ const field = col.dataField();
4097
+ if (out.has(col) || col.dataType() || !field) {
4098
+ continue;
4099
+ }
4100
+ const display = this.lookupDisplayMap(col);
4101
+ let type;
4102
+ for (const row of rows) {
4103
+ let value = this.getRaw(row, field);
4104
+ if (display) {
4105
+ if (!display.has(value)) {
4106
+ continue;
4107
+ }
4108
+ value = display.get(value);
4109
+ }
4110
+ const valueType = nxeValueDataType(value);
4111
+ type = type ?? valueType;
4112
+ if (type && valueType && type !== valueType) {
4113
+ type = 'string';
4114
+ break;
4115
+ }
4116
+ }
4117
+ if (type) {
4118
+ out.set(col, { field, type });
4119
+ }
4120
+ }
4121
+ return out;
4122
+ } });
4123
+ /** A lookup's value → raw display value, first match winning as in `lookupText`; null without one. */
4124
+ lookupDisplayMap(col) {
4125
+ const lookup = col.lookup();
4126
+ if (!lookup) {
4127
+ return null;
4128
+ }
4129
+ const valueExpr = lookup.valueExpr ?? 'lovCode';
4130
+ const displayExpr = lookup.displayExpr ?? 'lovName';
4131
+ const map = new Map();
4132
+ for (const item of lookup.dataSource ?? []) {
4133
+ const key = nxeResolveExpr(valueExpr, item);
4134
+ if (!map.has(key)) {
4135
+ map.set(key, nxeResolveExpr(displayExpr, item));
4136
+ }
4137
+ }
4138
+ return map;
4139
+ }
4140
+ /**
4141
+ * The column's effective type - its declared `dataType`, else the inferred one - which is what
4142
+ * dx's `column.dataType` holds after inference. Every type-driven decision reads this, never
4143
+ * `col.dataType()` directly, or an undeclared column would behave as text in one place and
4144
+ * as a number in the next.
4145
+ */
4146
+ colType(col) {
4147
+ return col.dataType() ?? this.inferredTypes().get(col)?.type;
4148
+ }
3475
4149
  /** The active sort: an explicit user click, else the first column declaring `sortOrder`. */
3476
4150
  effectiveSort() {
3477
4151
  if (this.sortInit()) {
@@ -3481,12 +4155,75 @@ class NxeDataGridComponent {
3481
4155
  return sorted ? { field: sorted.dataField(), dir: sorted.sortOrder() } : null;
3482
4156
  }
3483
4157
  // --- Rendering helpers -----------------------------------------------------------------------
4158
+ /**
4159
+ * dx `getAlignmentByDataType` (m_columns_controller_utils.js) over the effective type:
4160
+ * number right, boolean centre, everything else left. A lookup aligns by its display text
4161
+ * (dx `lookup.dataType`, :901), which is text - so left, even over numeric codes.
4162
+ */
4163
+ /**
4164
+ * Columns whose cellTemplate draws a check box (nxe-check-box, mat-checkbox, a native
4165
+ * checkbox input or anything with role=checkbox). align() centres them like a boolean column,
4166
+ * header and cells, unless the column asks for center or right. dx centres only by dataType,
4167
+ * so a template check box over non-boolean data (or no dataField) would sit at the left there;
4168
+ * a check box reads as a boolean whatever feeds it, so here it is centred too.
4169
+ *
4170
+ * Sticky, as an inferred type is: a column that drew a check box keeps its alignment when a
4171
+ * filter or an empty page leaves no row to look at. A column that goes away drops out.
4172
+ */
4173
+ templateCheckboxColumns = signal(new Set(), /* @ts-ignore */
4174
+ ...(ngDevMode ? [{ debugName: "templateCheckboxColumns" }] : /* istanbul ignore next */ []));
4175
+ detectTemplateCheckboxColumns() {
4176
+ const cols = this.visibleColumns();
4177
+ const current = this.templateCheckboxColumns();
4178
+ let next = null;
4179
+ for (const col of current) {
4180
+ if (!cols.includes(col)) {
4181
+ (next ??= new Set(current)).delete(col);
4182
+ }
4183
+ }
4184
+ const candidates = cols.filter((c) => !!c.cellTemplate() && !current.has(c));
4185
+ if (candidates.length) {
4186
+ const host = this.elementRef.nativeElement;
4187
+ // This grid's own table and rows: a nested master-detail grid's come later, and its
4188
+ // rows live in a master-detail row, never in one of these data rows' cells' place.
4189
+ const table = host.querySelector('.nxe-datagrid-content > .nxe-datagrid-table');
4190
+ // A few rows are enough to know what a template draws, and keep this cheap on long pages.
4191
+ const rows = table
4192
+ ? Array.from(table.querySelectorAll(':scope > tbody > tr.nxe-data-row')).slice(0, 20)
4193
+ : [];
4194
+ const lead = this.leadCellCount();
4195
+ for (const col of candidates) {
4196
+ const index = lead + cols.indexOf(col) + 1;
4197
+ const drawsCheckbox = rows.some((tr) => tr.querySelector(`:scope > td[aria-colindex="${index}"] :is(nxe-check-box, mat-checkbox, input[type="checkbox"], [role="checkbox"])`));
4198
+ if (drawsCheckbox) {
4199
+ (next ??= new Set(current)).add(col);
4200
+ }
4201
+ }
4202
+ }
4203
+ if (next) {
4204
+ this.templateCheckboxColumns.set(next);
4205
+ }
4206
+ }
4207
+ /** Boolean data (declared or inferred) or a cellTemplate seen drawing a check box. */
4208
+ isCheckboxColumn(col) {
4209
+ return (this.colType(col) === 'boolean' && !col.lookup()) || this.templateCheckboxColumns().has(col);
4210
+ }
3484
4211
  align(col) {
3485
4212
  const a = col.alignment();
4213
+ // A check box column - boolean data, or a cellTemplate that draws a check box - is centred,
4214
+ // header and cells, unless it asks for center or right. 'left' counts as the default here:
4215
+ // apps bind alignment="left" onto every column that has none of its own (Bright's generic
4216
+ // list does), and that must not pin a check box to the left.
4217
+ if (a !== 'center' && a !== 'right' && this.isCheckboxColumn(col)) {
4218
+ return 'center';
4219
+ }
3486
4220
  if (a) {
3487
4221
  return a;
3488
4222
  }
3489
- switch (col.dataType()) {
4223
+ if (col.lookup()) {
4224
+ return 'left';
4225
+ }
4226
+ switch (this.colType(col)) {
3490
4227
  case 'number':
3491
4228
  return 'right';
3492
4229
  case 'boolean':
@@ -3495,6 +4232,14 @@ class NxeDataGridComponent {
3495
4232
  return 'left';
3496
4233
  }
3497
4234
  }
4235
+ /**
4236
+ * The filter cell's text-align: dx sets the filter editor's input to the column alignment
4237
+ * unless it is centre (m_filter_row.js:390-395).
4238
+ */
4239
+ filterCellAlign(col) {
4240
+ const a = this.align(col);
4241
+ return a === 'center' ? null : a;
4242
+ }
3498
4243
  colWidth(col) {
3499
4244
  const manual = this.widthOverride().get(col.dataField());
3500
4245
  if (manual != null) {
@@ -3515,15 +4260,16 @@ class NxeDataGridComponent {
3515
4260
  }
3516
4261
  return row[dataField];
3517
4262
  }
3518
- /** Resolve the displayed cell text: lookup > customizeText > dataType/format > raw string. */
3519
- /** dx parity: boolean values render as read-only checkboxes (dx dataType inference did this). */
3520
- isBooleanCell(col, row) {
3521
- if (col.dataType() === 'boolean') {
3522
- return true;
3523
- }
3524
- const v = this.getRaw(row, col.dataField());
3525
- return v === true || v === false;
4263
+ /**
4264
+ * dx `showEditorAlways` (m_columns_controller.js:912): a boolean-typed column - declared or
4265
+ * inferred - renders a checkbox, unless it is a lookup or has its own cellTemplate. Decided
4266
+ * per column, not per value: a column whose values are mixed infers 'string' and shows text,
4267
+ * as in dx.
4268
+ */
4269
+ isBooleanCell(col) {
4270
+ return this.colType(col) === 'boolean' && !col.lookup() && !col.cellTemplate();
3526
4271
  }
4272
+ /** Resolve the displayed cell text: lookup > customizeText > dataType/format > raw string. */
3527
4273
  cellText(col, row) {
3528
4274
  const raw = this.getRaw(row, col.dataField());
3529
4275
  const valueText = this.lookupText(col, raw) ?? this.defaultFormat(col, raw);
@@ -3538,11 +4284,11 @@ class NxeDataGridComponent {
3538
4284
  }
3539
4285
  const valueExpr = lookup.valueExpr ?? 'lovCode';
3540
4286
  const displayExpr = lookup.displayExpr ?? 'lovName';
3541
- const match = (lookup.dataSource ?? []).find((item) => item?.[valueExpr] === raw);
3542
- return match ? String(match[displayExpr] ?? raw) : String(raw);
4287
+ const match = (lookup.dataSource ?? []).find((item) => nxeResolveExpr(valueExpr, item) === raw);
4288
+ return match ? nxeResolveText(displayExpr, match) || String(raw) : String(raw);
3543
4289
  }
3544
4290
  defaultFormat(col, raw) {
3545
- return nxeDefaultFormat(col.dataType(), col.format(), raw);
4291
+ return nxeDefaultFormat(this.colType(col), col.format(), raw, this.separators);
3546
4292
  }
3547
4293
  templateFor(name) {
3548
4294
  if (!name) {
@@ -3582,7 +4328,7 @@ class NxeDataGridComponent {
3582
4328
  column: {
3583
4329
  dataField: col.dataField(),
3584
4330
  caption: col.caption(),
3585
- dataType: col.dataType(),
4331
+ dataType: this.colType(col),
3586
4332
  format: col.format(),
3587
4333
  },
3588
4334
  // dx's setValue changes the cell value; it does not end the edit. The editor
@@ -3631,7 +4377,7 @@ class NxeDataGridComponent {
3631
4377
  return;
3632
4378
  }
3633
4379
  const host = this.elementRef.nativeElement;
3634
- const table = host.querySelector('.dx-datagrid-content > .dx-datagrid-table');
4380
+ const table = host.querySelector('.nxe-datagrid-content > .nxe-datagrid-table');
3635
4381
  if (!table) {
3636
4382
  return;
3637
4383
  }
@@ -3689,18 +4435,30 @@ class NxeDataGridComponent {
3689
4435
  if (!this.onCellPrepared.observed) {
3690
4436
  continue;
3691
4437
  }
3692
- let colSeen = 0;
4438
+ const lead = this.leadCellCount();
3693
4439
  for (const td of Array.from(tr.cells)) {
3694
- const isCommand = td.classList.contains('dx-command-expand') ||
3695
- td.classList.contains('dx-command-select') ||
3696
- td.classList.contains('dx-command-edit') ||
3697
- td.classList.contains('dx-group-cell') ||
3698
- td.classList.contains('nxe-band-cell');
3699
- const col = isCommand ? undefined : cols[colSeen];
3700
- const columnIndex = isCommand ? -1 : colSeen;
3701
- if (!isCommand) {
3702
- colSeen++;
3703
- }
4440
+ // Read the column off `aria-colindex` rather than counting cells.
4441
+ //
4442
+ // Counting was wrong in four separate ways, all silent, and every one of them
4443
+ // the shape this event exists to serve. A data row's grouping indent cells
4444
+ // carry only `nxe-datagrid-group-space`, which no class test here listed, so
4445
+ // in a grouped grid every cell reported the column one place to its left per
4446
+ // group level and the last column was never reported at all. `mergeColumns`
4447
+ // deletes the covered `<td>`s, so a continuation row is short and shifts the
4448
+ // other way. A band adds a header row whose cells are not leaves. And the
4449
+ // counter restarted per `<tr>`, so a banded header row numbered from zero.
4450
+ //
4451
+ // `aria-colindex` numbers every data cell, leaf header cell, filter cell and
4452
+ // summary cell, and already includes the lead command columns, so it survives
4453
+ // all four. Command cells carry it too, as dx's do, but their numbers fall
4454
+ // outside the data columns' range (at or below the lead count, or past the last
4455
+ // column). Those, the cells without one (a band) and the group caption - dx
4456
+ // numbers it as the cell after its toggle, which can be a data column's number
4457
+ // - are not columns and are reported with `column: null` and index -1.
4458
+ const colAttr = td.classList.contains('nxe-group-cell') ? null : td.getAttribute('aria-colindex');
4459
+ const at = colAttr ? Number(colAttr) - 1 - lead : -1;
4460
+ const col = at >= 0 ? cols[at] : undefined;
4461
+ const columnIndex = col ? at : -1;
3704
4462
  const value = col && rowType === 'data' && info.data
3705
4463
  ? this.getRaw(info.data, col.dataField())
3706
4464
  : undefined;
@@ -3716,7 +4474,7 @@ class NxeDataGridComponent {
3716
4474
  ? {
3717
4475
  dataField: col.dataField(),
3718
4476
  caption: col.caption(),
3719
- dataType: col.dataType(),
4477
+ dataType: this.colType(col),
3720
4478
  index: columnIndex,
3721
4479
  }
3722
4480
  : null,
@@ -3733,6 +4491,176 @@ class NxeDataGridComponent {
3733
4491
  this.preparedDepth--;
3734
4492
  }
3735
4493
  }
4494
+ // --- Keyboard navigation (WAI-ARIA grid pattern) ---------------------------------------------
4495
+ /**
4496
+ * The single cell holding the grid's tab stop.
4497
+ *
4498
+ * A grid is one stop in the tab order, not one per cell — a 200-row table would otherwise be
4499
+ * 1,400 tab presses to get past. Row `-1` is the header, so ArrowUp from the first data row
4500
+ * reaches the column headers rather than stopping dead.
4501
+ */
4502
+ focusedCell = signal({ row: -1, col: 0 }, /* @ts-ignore */
4503
+ ...(ngDevMode ? [{ debugName: "focusedCell" }] : /* istanbul ignore next */ []));
4504
+ /**
4505
+ * The rows the keyboard and `aria-rowindex` address: the data rows actually RENDERED.
4506
+ *
4507
+ * Emphatically not `pagedRows()`. That is the page's data BEFORE grouping and tree collapse
4508
+ * take rows out of the DOM, while `renderRows` renumbers densely over what it emits - so with
4509
+ * one group collapsed `pagedRows()` counts three rows where two have a cell to focus. Every
4510
+ * consumer of the count is then wrong in a different way: `aria-rowcount` claims a row a
4511
+ * reader cannot reach, Ctrl+End targets an index with no DOM behind it and movement silently
4512
+ * does nothing, and Enter and Space read `pagedRows()[row]` - which is a DIFFERENT row than
4513
+ * the one under the cursor, so Space selects something the user is not looking at.
4514
+ */
4515
+ navigableRows = computed(() => this.renderRows()
4516
+ .filter((r) => r.kind === 'data')
4517
+ .map((r) => r.row), /* @ts-ignore */
4518
+ ...(ngDevMode ? [{ debugName: "navigableRows" }] : /* istanbul ignore next */ []));
4519
+ isCellTabStop(row, col) {
4520
+ const cell = this.focusedCell();
4521
+ return cell.row === row && cell.col === col;
4522
+ }
4523
+ focusCell(row, col) {
4524
+ this.focusedCell.set({ row, col });
4525
+ }
4526
+ /** dx marks the sorted column for assistive tech as well as with the arrow glyph. */
4527
+ ariaSort(col) {
4528
+ if (!col.allowSorting()) {
4529
+ return null;
4530
+ }
4531
+ const dir = this.sortDir(col);
4532
+ return dir === 'asc' ? 'ascending' : dir === 'desc' ? 'descending' : null;
4533
+ }
4534
+ /**
4535
+ * The DOM cell at a logical position, addressed by `aria-colindex` rather than by offset.
4536
+ *
4537
+ * Offsets break twice over. A band cell carries `role="columnheader"` too, so counting
4538
+ * header cells finds bands as well as leaves; and `mergeColumns` deletes the covered `<td>`s,
4539
+ * so a data row can be short. `aria-colindex` is emitted on every cell and already includes
4540
+ * the lead command columns, so it names the position exactly rather than by counting.
4541
+ *
4542
+ * Both lookups stay inside this grid's own table: a master-detail grid renders a whole second
4543
+ * grid into a `<td>`, and its rows would otherwise answer to the outer grid's arrow keys.
4544
+ */
4545
+ cellAt(row, col) {
4546
+ const host = this.elementRef.nativeElement;
4547
+ const colIndex = this.leadCellCount() + col + 1;
4548
+ if (row < 0) {
4549
+ return nxeHeaderLeafCells(host)[col] ?? null;
4550
+ }
4551
+ const tbody = host.querySelector('.nxe-datagrid-content > .nxe-datagrid-table > tbody');
4552
+ return (tbody?.querySelector(`:scope > .nxe-data-row[aria-rowindex="${row + 1}"] > [aria-colindex="${colIndex}"]`) ?? null);
4553
+ }
4554
+ /** Move the tab stop and put real DOM focus on the cell that now holds it. */
4555
+ moveFocus(row, col) {
4556
+ const maxRow = this.navigableRows().length - 1;
4557
+ const maxCol = this.visibleColumns().length - 1;
4558
+ const targetRow = Math.max(-1, Math.min(row, maxRow));
4559
+ const nextCol = Math.max(0, Math.min(col, maxCol));
4560
+ // A merged cell's rowspan removes the `<td>`s beneath it, so a position can have no cell
4561
+ // of its own. Keep going the way we were headed until one does — landing on the covering
4562
+ // cell instead would make ArrowDown into a merged block a dead end.
4563
+ const step = targetRow > this.focusedCell().row ? 1 : -1;
4564
+ let probe = targetRow;
4565
+ let cell = this.cellAt(probe, nextCol);
4566
+ while (!cell && probe + step >= -1 && probe + step <= maxRow) {
4567
+ probe += step;
4568
+ cell = this.cellAt(probe, nextCol);
4569
+ }
4570
+ if (!cell) {
4571
+ return;
4572
+ }
4573
+ this.focusedCell.set({ row: probe, col: nextCol });
4574
+ cell.focus();
4575
+ }
4576
+ /**
4577
+ * Arrow-key movement over cells.
4578
+ *
4579
+ * Deliberately not bound to the rowsview: a projected editor inside a cell needs its own
4580
+ * arrow keys, so anything originating inside an editor is left alone.
4581
+ */
4582
+ onGridKeydown(event) {
4583
+ const target = event.target;
4584
+ if (target &&
4585
+ target.closest('input, textarea, select, [contenteditable="true"], .nxe-grid-cell-editor-check')) {
4586
+ return;
4587
+ }
4588
+ const { row, col } = this.focusedCell();
4589
+ const rows = this.navigableRows();
4590
+ const lastRow = rows.length - 1;
4591
+ const lastCol = this.visibleColumns().length - 1;
4592
+ switch (event.key) {
4593
+ case 'ArrowRight':
4594
+ event.preventDefault();
4595
+ this.moveFocus(row, col + 1);
4596
+ break;
4597
+ case 'ArrowLeft':
4598
+ event.preventDefault();
4599
+ this.moveFocus(row, col - 1);
4600
+ break;
4601
+ case 'ArrowDown':
4602
+ event.preventDefault();
4603
+ this.moveFocus(row + 1, col);
4604
+ break;
4605
+ case 'ArrowUp':
4606
+ event.preventDefault();
4607
+ this.moveFocus(row - 1, col);
4608
+ break;
4609
+ case 'Home':
4610
+ event.preventDefault();
4611
+ this.moveFocus(event.ctrlKey ? -1 : row, 0);
4612
+ break;
4613
+ case 'End':
4614
+ event.preventDefault();
4615
+ this.moveFocus(event.ctrlKey ? lastRow : row, lastCol);
4616
+ break;
4617
+ case 'PageDown':
4618
+ event.preventDefault();
4619
+ this.pagingEnabled() ? this.nextPage() : this.moveFocus(lastRow, col);
4620
+ break;
4621
+ case 'PageUp':
4622
+ event.preventDefault();
4623
+ this.pagingEnabled() ? this.prevPage() : this.moveFocus(-1, col);
4624
+ break;
4625
+ case 'Enter':
4626
+ case 'F2': {
4627
+ if (row < 0) {
4628
+ // On a header, Enter sorts — the same thing a click does.
4629
+ event.preventDefault();
4630
+ const col0 = this.visibleColumns()[col];
4631
+ if (col0) {
4632
+ this.onHeaderClick(col0);
4633
+ }
4634
+ break;
4635
+ }
4636
+ const rowData = rows[row];
4637
+ const column = this.visibleColumns()[col];
4638
+ if (rowData && column && this.cellEditingEnabled() && this.editableCell(column)) {
4639
+ event.preventDefault();
4640
+ if (this.booleanInPlace(column)) {
4641
+ // The checkbox is already the editor, and it takes no focus of its own:
4642
+ // Enter does what a click on it does.
4643
+ this.commitCellEdit(rowData, column, this.getRaw(rowData, column.dataField()) !== true);
4644
+ break;
4645
+ }
4646
+ this.editingCell.set({ row: rowData, field: column.dataField() });
4647
+ }
4648
+ break;
4649
+ }
4650
+ case ' ': {
4651
+ if (row < 0 || this.selectionMode() === 'none') {
4652
+ break;
4653
+ }
4654
+ event.preventDefault();
4655
+ const rowData = rows[row];
4656
+ if (rowData) {
4657
+ this.toggleRow(rowData);
4658
+ }
4659
+ break;
4660
+ }
4661
+ default:
4662
+ }
4663
+ }
3736
4664
  // --- Interaction -----------------------------------------------------------------------------
3737
4665
  /** True when this header can start a drag gesture (to the group panel and/or a reorder). */
3738
4666
  headerDraggable(col) {
@@ -3753,9 +4681,12 @@ class NxeDataGridComponent {
3753
4681
  onChipPointerDown(col, level, event) {
3754
4682
  this.drag.startChipDrag(col, level, event);
3755
4683
  }
3756
- /** Chip click (dx): toggle the grouped column's sort — unless this click ends a drag. */
4684
+ /**
4685
+ * Chip click (dx): toggle the grouped column's sort — unless this click ends a drag. dx sends
4686
+ * it through the same changeSortOrder as a header click (m_sorting.js:134), so the same gate.
4687
+ */
3757
4688
  onChipClick(col) {
3758
- if (this.drag.consumeClick()) {
4689
+ if (this.drag.consumeClick() || !this.clickSorts(col)) {
3759
4690
  return;
3760
4691
  }
3761
4692
  this.sortGroupColumn(col);
@@ -3764,7 +4695,7 @@ class NxeDataGridComponent {
3764
4695
  if (this.drag.consumeClick() || this.resize.consumeClick()) {
3765
4696
  return; // this click is the tail of a drag/resize gesture, not a sort request
3766
4697
  }
3767
- if (!col.allowSorting() || this.sorting()?.mode === 'none') {
4698
+ if (!this.clickSorts(col)) {
3768
4699
  return;
3769
4700
  }
3770
4701
  const field = col.dataField();
@@ -3787,6 +4718,44 @@ class NxeDataGridComponent {
3787
4718
  const s = this.effectiveSort();
3788
4719
  return s && s.field === col.dataField() ? s.dir : null;
3789
4720
  }
4721
+ /**
4722
+ * `sorting.mode` as dx's sorting code reads the option: unbound is dx's default 'single', an
4723
+ * object gives its `mode` (default 'single'), and anything else - `[sorting]="false"` or
4724
+ * `null` - has no mode at all.
4725
+ */
4726
+ sortingMode() {
4727
+ const s = this.sorting();
4728
+ if (s === undefined) {
4729
+ return 'single';
4730
+ }
4731
+ return s ? (s.mode ?? 'single') : undefined;
4732
+ }
4733
+ /**
4734
+ * dx changeSortOrder (m_columns_controller.js:758-766) acts only in mode 'single' or
4735
+ * 'multiple', and only on a column that allows sorting. `[sorting]="false"` still renders the
4736
+ * sort slot, but a header or chip click never sorts.
4737
+ */
4738
+ clickSorts(col) {
4739
+ const mode = this.sortingMode();
4740
+ return col.allowSorting() && (mode === 'single' || mode === 'multiple');
4741
+ }
4742
+ /**
4743
+ * dx `isSortingAllowed` (m_sorting_mixin.js:37): a sortable column, unless the mode is
4744
+ * literally 'none'. A column with no dataField is never sortable (m_columns_controller.js:
4745
+ * 1383-1388). Gates the `nxe-datagrid-action` class, and the sort slot of an unsorted column.
4746
+ */
4747
+ sortAllowed(col) {
4748
+ const s = this.sorting();
4749
+ return col.allowSorting() && !!col.dataField() && (s ? s.mode : undefined) !== 'none';
4750
+ }
4751
+ /**
4752
+ * Whether the header renders dx's sort span: always for the sorted column, and as an empty
4753
+ * `nxe-sort-none` placeholder for any other sortable one (m_sorting_mixin.js:38-46). The
4754
+ * placeholder is display:none in the fixed table and 14px wide in the best-fit pass.
4755
+ */
4756
+ sortSlot(col) {
4757
+ return this.sortDir(col) !== null || this.sortAllowed(col);
4758
+ }
3790
4759
  onRowEnter(index) {
3791
4760
  if (this.hoverStateEnabled()) {
3792
4761
  this.hoveredIndex.set(index);
@@ -3925,7 +4894,7 @@ class NxeDataGridComponent {
3925
4894
  if (col.lookup()) {
3926
4895
  return 'lookup';
3927
4896
  }
3928
- const type = col.dataType();
4897
+ const type = this.colType(col);
3929
4898
  return type === 'boolean' || type === 'date' || type === 'number' ? type : 'text';
3930
4899
  }
3931
4900
  /** dx shows the operation-chooser magnifier only for typed (non-select) editors. */
@@ -3945,8 +4914,14 @@ class NxeDataGridComponent {
3945
4914
  frText2(col) {
3946
4915
  return this.filterRowState().get(col.dataField())?.text2 ?? '';
3947
4916
  }
3948
- frOpIconClass(col) {
3949
- return `dx-icon-filter-operation-${NxeDataGridComponent.OP_ICON_SLUGS[this.frOp(col)]}`;
4917
+ /**
4918
+ * The chooser glyph: dx's magnifier ('default') until an operation is picked from the menu,
4919
+ * then that operation's own. Only the slug varies - the template emits `nxe-icon-filter-operation-*`
4920
+ * and nothing prefixed `dx-`, which the compat mirror would strip for want of an nxe twin.
4921
+ */
4922
+ frOpIconSlug(col) {
4923
+ const entry = this.filterRowState().get(col.dataField());
4924
+ return entry?.chosen ? NxeDataGridComponent.OP_ICON_SLUGS[entry.op] : 'default';
3950
4925
  }
3951
4926
  opIconSlug(op) {
3952
4927
  return NxeDataGridComponent.OP_ICON_SLUGS[op];
@@ -3997,7 +4972,12 @@ class NxeDataGridComponent {
3997
4972
  const field = col.dataField();
3998
4973
  const next = new Map(this.filterRowState());
3999
4974
  const prev = next.get(field) ?? { op: this.frDefaultOp(col), text: '' };
4000
- next.set(field, { ...prev, op, ...(op !== 'between' ? { text2: undefined } : {}) });
4975
+ next.set(field, {
4976
+ ...prev,
4977
+ op,
4978
+ chosen: true,
4979
+ ...(op !== 'between' ? { text2: undefined } : {}),
4980
+ });
4001
4981
  this.filterRowState.set(next);
4002
4982
  }
4003
4983
  this.closeFilterOpMenu();
@@ -4033,7 +5013,7 @@ class NxeDataGridComponent {
4033
5013
  * date columns → a `yyyy-MM-dd` day key (the tree's leaf unit), else the raw value.
4034
5014
  */
4035
5015
  hfKey(col, raw) {
4036
- return nxeHfKey(raw, col.dataType() === 'date');
5016
+ return nxeHfKey(raw, this.colType(col) === 'date');
4037
5017
  }
4038
5018
  /** dx does NOT cascade header-filter lists for client arrays (bright audit V6). */
4039
5019
  hfBaseRows() {
@@ -4046,7 +5026,7 @@ class NxeDataGridComponent {
4046
5026
  if (raw === true) {
4047
5027
  return 'Yes';
4048
5028
  }
4049
- if (raw === false || col.dataType() === 'boolean') {
5029
+ if (raw === false || this.colType(col) === 'boolean') {
4050
5030
  return 'No';
4051
5031
  }
4052
5032
  return null;
@@ -4124,12 +5104,12 @@ class NxeDataGridComponent {
4124
5104
  this.resizeObserver?.disconnect();
4125
5105
  }
4126
5106
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDataGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4127
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeDataGridComponent, isStandalone: true, selector: "nxe-data-grid, nxe-tree-list", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, keyExpr: { classPropertyName: "keyExpr", publicName: "keyExpr", isSignal: true, isRequired: false, transformFunction: null }, hoverStateEnabledInput: { classPropertyName: "hoverStateEnabledInput", publicName: "hoverStateEnabled", isSignal: true, isRequired: false, transformFunction: null }, showColumnLinesInput: { classPropertyName: "showColumnLinesInput", publicName: "showColumnLines", isSignal: true, isRequired: false, transformFunction: null }, showRowLinesInput: { classPropertyName: "showRowLinesInput", publicName: "showRowLines", isSignal: true, isRequired: false, transformFunction: null }, showBordersInput: { classPropertyName: "showBordersInput", publicName: "showBorders", isSignal: true, isRequired: false, transformFunction: null }, wordWrapEnabledInput: { classPropertyName: "wordWrapEnabledInput", publicName: "wordWrapEnabled", isSignal: true, isRequired: false, transformFunction: null }, columnAutoWidthInput: { classPropertyName: "columnAutoWidthInput", publicName: "columnAutoWidth", isSignal: true, isRequired: false, transformFunction: null }, rowAlternationEnabledInput: { classPropertyName: "rowAlternationEnabledInput", publicName: "rowAlternationEnabled", isSignal: true, isRequired: false, transformFunction: null }, remoteOperations: { classPropertyName: "remoteOperations", publicName: "remoteOperations", isSignal: true, isRequired: false, transformFunction: null }, syncLookupFilterValues: { classPropertyName: "syncLookupFilterValues", publicName: "syncLookupFilterValues", isSignal: true, isRequired: false, transformFunction: null }, columnMinWidth: { classPropertyName: "columnMinWidth", publicName: "columnMinWidth", isSignal: true, isRequired: false, transformFunction: null }, noDataTextInput: { classPropertyName: "noDataTextInput", publicName: "noDataText", isSignal: true, isRequired: false, transformFunction: null }, loadPanel: { classPropertyName: "loadPanel", publicName: "loadPanel", isSignal: true, isRequired: false, transformFunction: null }, paging: { classPropertyName: "paging", publicName: "paging", isSignal: true, isRequired: false, transformFunction: null }, filterRow: { classPropertyName: "filterRow", publicName: "filterRow", isSignal: true, isRequired: false, transformFunction: null }, headerFilterOptions: { classPropertyName: "headerFilterOptions", publicName: "headerFilter", isSignal: true, isRequired: false, transformFunction: null }, groupingOptions: { classPropertyName: "groupingOptions", publicName: "grouping", isSignal: true, isRequired: false, transformFunction: null }, groupPanelOptions: { classPropertyName: "groupPanelOptions", publicName: "groupPanel", isSignal: true, isRequired: false, transformFunction: null }, scrolling: { classPropertyName: "scrolling", publicName: "scrolling", isSignal: true, isRequired: false, transformFunction: null }, sorting: { classPropertyName: "sorting", publicName: "sorting", isSignal: true, isRequired: false, transformFunction: null }, allowColumnResizing: { classPropertyName: "allowColumnResizing", publicName: "allowColumnResizing", isSignal: true, isRequired: false, transformFunction: null }, columnResizingMode: { classPropertyName: "columnResizingMode", publicName: "columnResizingMode", isSignal: true, isRequired: false, transformFunction: null }, allowColumnReordering: { classPropertyName: "allowColumnReordering", publicName: "allowColumnReordering", isSignal: true, isRequired: false, transformFunction: null }, repaintChangesOnly: { classPropertyName: "repaintChangesOnly", publicName: "repaintChangesOnly", isSignal: true, isRequired: false, transformFunction: null }, showColumnHeaders: { classPropertyName: "showColumnHeaders", publicName: "showColumnHeaders", isSignal: true, isRequired: false, transformFunction: null }, parentIdExpr: { classPropertyName: "parentIdExpr", publicName: "parentIdExpr", isSignal: true, isRequired: false, transformFunction: null }, dataStructure: { classPropertyName: "dataStructure", publicName: "dataStructure", isSignal: true, isRequired: false, transformFunction: null }, rootValue: { classPropertyName: "rootValue", publicName: "rootValue", isSignal: true, isRequired: false, transformFunction: null }, autoExpandAll: { classPropertyName: "autoExpandAll", publicName: "autoExpandAll", isSignal: true, isRequired: false, transformFunction: null }, expandedRowKeys: { classPropertyName: "expandedRowKeys", publicName: "expandedRowKeys", isSignal: true, isRequired: false, transformFunction: null }, selectedRowKeys: { classPropertyName: "selectedRowKeys", publicName: "selectedRowKeys", isSignal: true, isRequired: false, transformFunction: null }, mergeColumns: { classPropertyName: "mergeColumns", publicName: "mergeColumns", isSignal: true, isRequired: false, transformFunction: null }, rowClass: { classPropertyName: "rowClass", publicName: "rowClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandedRowKeys: "expandedRowKeysChange", selectedRowKeys: "selectedRowKeysChange", onSelectionChanged: "onSelectionChanged", onRowClick: "onRowClick", onContentReady: "onContentReady", onOptionChanged: "onOptionChanged", onCellPrepared: "onCellPrepared", onRowPrepared: "onRowPrepared", onRowExpanding: "onRowExpanding", onRowExpanded: "onRowExpanded" }, host: { classAttribute: "nxe-widget nxe-data-grid" }, queries: [{ propertyName: "declaredColumns", predicate: NxeColumnComponent, isSignal: true }, { propertyName: "templates", predicate: NxeTemplateDirective, isSignal: true }, { propertyName: "selection", first: true, predicate: NxeSelectionComponent, descendants: true, isSignal: true }, { propertyName: "headerFilter", first: true, predicate: NxeHeaderFilterComponent, descendants: true, isSignal: true }, { propertyName: "grouping", first: true, predicate: NxeGroupingComponent, descendants: true, isSignal: true }, { propertyName: "groupPanel", first: true, predicate: NxeGroupPanelComponent, descendants: true, isSignal: true }, { propertyName: "summaryCmp", first: true, predicate: NxeSummaryComponent, descendants: true, isSignal: true }, { propertyName: "pagingCmp", first: true, predicate: NxePagingComponent, descendants: true, isSignal: true }, { propertyName: "pagerCmp", first: true, predicate: NxePagerComponent, descendants: true, isSignal: true }, { propertyName: "editingCmp", first: true, predicate: NxeEditingComponent, descendants: true, isSignal: true }, { propertyName: "filterRowCmp", first: true, predicate: NxeFilterRowComponent, descendants: true, isSignal: true }, { propertyName: "masterDetail", first: true, predicate: NxeMasterDetailComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "frMenuPanel", first: true, predicate: ["frOpMenu"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"dx-widget dx-datagrid\" [class.dx-datagrid-borders]=\"showBorders()\">\r\n <div class=\"dx-gridbase-container\">\r\n @if (groupPanelVisible()) {\r\n <div class=\"dx-datagrid-header-panel\">\r\n <div class=\"dx-datagrid-group-panel\">\r\n @if (groupColumns().length === 0) {\r\n <span class=\"dx-group-panel-message\">Drag a column header here to group by that column</span>\r\n } @else {\r\n @for (col of groupColumns(); track col.dataField(); let li = $index) {\r\n <!-- dx chip DOM: caption + sort indicator + header-filter button (bright audit) -->\r\n <div\r\n class=\"dx-group-panel-item dx-datagrid-action dx-datagrid-drag-action\"\r\n tabindex=\"0\"\r\n (pointerdown)=\"onChipPointerDown(col, li, $event)\"\r\n (click)=\"onChipClick(col)\">{{ col.caption() }}<div class=\"dx-column-indicators\" role=\"presentation\"><span\r\n class=\"dx-sort\"\r\n [class.dx-sort-up]=\"groupDir(col) === 'asc'\"\r\n [class.dx-sort-down]=\"groupDir(col) === 'desc'\"></span>@if (headerFilterVisible(col)) {<span\r\n class=\"nxe-grid-hf-icon\"\r\n [class.nxe-hf-active]=\"hasHeaderFilter(col)\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"openHeaderFilter(col, $event)\"></span>}</div></div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n <div\r\n class=\"dx-datagrid-rowsview dx-datagrid-after-headers\"\r\n [class.dx-datagrid-nowrap]=\"!wordWrapEnabled()\">\r\n <div class=\"dx-datagrid-content\">\r\n <table class=\"dx-datagrid-table dx-datagrid-table-fixed\" [style.width]=\"tableWidthStyle()\">\r\n <colgroup>\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <col style=\"width: 30px\" />\r\n }\r\n @if (masterDetailEnabled()) {\r\n <col style=\"width: 30px\" />\r\n }\r\n @if (showCheckboxes()) {\r\n <col style=\"width: 40px\" />\r\n }\r\n @for (col of visibleColumns(); track $index; let ci = $index) {\r\n <col [style.width]=\"effectiveColWidth(col, ci)\" />\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <col style=\"width: 100px\" />\r\n }\r\n </colgroup>\r\n <thead class=\"dx-datagrid-headers\">\r\n <!-- Bands add header rows: a band cell spans its visible leaves with\r\n colspan, and a leaf above the bottom row spans down with rowspan.\r\n That is dx's own banded markup, and it keeps the LAST header row\r\n all-leaves - which the auto-width, resize and drag code relies on. -->\r\n @for (hrow of headerRows(); track $index; let hi = $index) {\r\n <tr class=\"dx-row dx-header-row\" [class.dx-column-lines]=\"showColumnLines()\">\r\n @if (hi === 0) {\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td\r\n class=\"dx-command-expand dx-datagrid-group-space dx-cell-focus-disabled\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td\r\n class=\"dx-command-expand\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td\r\n class=\"dx-command-select\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\">\r\n @if (selectionMode() === 'multiple') {\r\n <span\r\n class=\"dx-select-checkbox nxe-grid-checkbox\"\r\n [class.nxe-checked]=\"allSelected()\"\r\n [class.nxe-indeterminate]=\"someSelected() && !allSelected()\"\r\n (click)=\"toggleSelectAll($event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n }\r\n </td>\r\n }\r\n }\r\n @for (cell of hrow; track $index) {\r\n @if (cell.isBand) {\r\n <td\r\n class=\"nxe-band-cell\"\r\n [class]=\"cell.column.cssClass()\"\r\n [attr.colspan]=\"cell.colspan > 1 ? cell.colspan : null\"\r\n [style.text-align]=\"cell.column.alignment() ?? 'center'\">\r\n <div class=\"dx-datagrid-text-content\">{{ cell.column.caption() }}</div>\r\n </td>\r\n } @else {\r\n <td\r\n [class]=\"cell.column.cssClass()\"\r\n [attr.aria-colindex]=\"leadCellCount() + cell.leafIndex + 1\"\r\n [attr.rowspan]=\"cell.rowspan > 1 ? cell.rowspan : null\"\r\n [style.text-align]=\"align(cell.column)\"\r\n [class.dx-cell-focus-disabled]=\"!cell.column.allowSorting()\"\r\n [class.dx-datagrid-drag-action]=\"headerDraggable(cell.column)\"\r\n (pointerdown)=\"onHeaderPointerDown(cell.column, cell.leafIndex, $event)\"\r\n (click)=\"onHeaderClick(cell.column)\">\r\n <!-- Both indicator classes are load-bearing, not decorative: they are what\r\n gate dx's caption max-widths (`.dx-header-row .dx-sort-indicator\r\n {max-width:calc(100% - 17px)}` and the .dx-header-filter-indicator\r\n family), i.e. the 17px-per-indicator budget that stops a long\r\n caption from colliding with the arrow and the funnel. -->\r\n <div\r\n class=\"dx-datagrid-text-content\"\r\n [class.dx-sort-indicator]=\"!!sortDir(cell.column)\"\r\n [class.dx-header-filter-indicator]=\"headerFilterVisible(cell.column)\">\r\n {{ cell.column.caption() }}\r\n </div>\r\n <!-- dx wraps the arrow and the funnel in one indicator block, and the\r\n group-panel chip below already did. Matching it here means the two\r\n places share a single set of metrics instead of drifting. -->\r\n @if (sortDir(cell.column) || headerFilterVisible(cell.column)) {\r\n <div class=\"dx-column-indicators\" role=\"presentation\">\r\n @if (sortDir(cell.column); as dir) {\r\n <span\r\n class=\"dx-sort\"\r\n [class.dx-sort-up]=\"dir === 'asc'\"\r\n [class.dx-sort-down]=\"dir === 'desc'\"></span>\r\n }\r\n @if (headerFilterVisible(cell.column)) {\r\n <span\r\n class=\"nxe-grid-hf-icon\"\r\n [class.nxe-hf-active]=\"hasHeaderFilter(cell.column)\"\r\n (click)=\"openHeaderFilter(cell.column, $event)\"></span>\r\n }\r\n </div>\r\n }\r\n @if (resizeHandleVisible(cell.column, cell.leafIndex)) {\r\n <span\r\n class=\"nxe-col-resize-handle\"\r\n (pointerdown)=\"startColumnResize(cell.column, cell.leafIndex, $event)\"></span>\r\n }\r\n </td>\r\n }\r\n }\r\n @if (hi === 0 && rowEditCommandVisible()) {\r\n <td\r\n class=\"dx-command-edit\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\"></td>\r\n }\r\n </tr>\r\n }\r\n @if (filterRowVisible()) {\r\n <tr class=\"dx-row dx-datagrid-filter-row\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"dx-command-expand dx-datagrid-group-space\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"dx-command-expand\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"dx-command-select\"></td>\r\n }\r\n @for (col of visibleColumns(); track $index) {\r\n <!-- dx filter cell: td.dx-editor-cell > .dx-editor-with-menu >\r\n [.dx-menu op chooser][typed editor] (dx-compat.css contract) -->\r\n <td class=\"dx-editor-cell\" [class]=\"col.cssClass()\">\r\n @if (col.allowFiltering()) {\r\n @switch (frEditorKind(col)) {\r\n @case ('lookup') {\r\n <select\r\n class=\"dx-texteditor-input nxe-grid-filter-input\"\r\n [value]=\"frText(col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"setFilterRowText(col, $any($event.target).value)\">\r\n <option value=\"\">(All)</option>\r\n @for (item of col.lookup()!.dataSource; track $index) {\r\n <option [value]=\"item[col.lookup()!.valueExpr ?? 'lovCode']\">\r\n {{ item[col.lookup()!.displayExpr ?? 'lovName'] }}\r\n </option>\r\n }\r\n </select>\r\n }\r\n @case ('boolean') {\r\n <select\r\n class=\"dx-texteditor-input nxe-grid-filter-input\"\r\n [value]=\"frText(col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"setFilterRowText(col, $any($event.target).value)\">\r\n <option value=\"\">(All)</option>\r\n <option value=\"true\">true</option>\r\n <option value=\"false\">false</option>\r\n </select>\r\n }\r\n @default {\r\n <div class=\"dx-editor-with-menu\">\r\n <div class=\"dx-menu dx-menu-base\">\r\n <ul class=\"dx-menu-horizontal\">\r\n <li>\r\n <div\r\n class=\"dx-menu-item\"\r\n title=\"Filter operation\"\r\n (click)=\"openFilterOpMenu(col, $event)\">\r\n <div class=\"dx-menu-item-content\">\r\n <span class=\"dx-icon\" [class]=\"frOpIconClass(col)\"></span>\r\n </div>\r\n </div>\r\n </li>\r\n </ul>\r\n </div>\r\n <input\r\n class=\"dx-texteditor-input nxe-grid-filter-input\"\r\n [type]=\"frEditorKind(col) === 'date' ? 'date' : 'text'\"\r\n [attr.inputmode]=\"frEditorKind(col) === 'number' ? 'decimal' : null\"\r\n [value]=\"frText(col)\"\r\n (input)=\"setFilterRowText(col, $any($event.target).value)\"\r\n (click)=\"$event.stopPropagation()\" />\r\n @if (frOp(col) === 'between') {\r\n <input\r\n class=\"dx-texteditor-input nxe-grid-filter-input nxe-fr-second\"\r\n [type]=\"frEditorKind(col) === 'date' ? 'date' : 'text'\"\r\n [attr.inputmode]=\"frEditorKind(col) === 'number' ? 'decimal' : null\"\r\n [value]=\"frText2(col)\"\r\n (input)=\"setFilterRowText(col, $any($event.target).value, true)\"\r\n (click)=\"$event.stopPropagation()\" />\r\n }\r\n </div>\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"dx-command-edit\"></td>\r\n }\r\n </tr>\r\n }\r\n </thead>\r\n <tbody>\r\n @for (item of renderRows(); track $index; let ri = $index) {\r\n @if (item.kind === 'group') {\r\n <!-- dx group-row anatomy (bright audit): [indent cells][expand chevron\r\n cell][dx-group-cell colspan][alignByColumn summary cells] -->\r\n <tr\r\n class=\"dx-row dx-group-row\"\r\n [class.dx-column-lines]=\"showColumnLines()\"\r\n [attr.aria-roledescription]=\"item.collapsed ? 'Collapsed row' : 'Expanded row'\">\r\n @for (i of indents(item.level); track i) {\r\n <td class=\"dx-command-expand dx-datagrid-group-space\"></td>\r\n }\r\n <td\r\n class=\"dx-command-expand dx-datagrid-group-space dx-datagrid-expand dx-selection-disabled\"\r\n [attr.aria-label]=\"item.collapsed ? 'Expand' : 'Collapse'\"\r\n (click)=\"toggleGroup(item.groupKey)\">\r\n <div\r\n [class.dx-datagrid-group-opened]=\"!item.collapsed\"\r\n [class.dx-datagrid-group-closed]=\"item.collapsed\"></div>\r\n </td>\r\n <td class=\"dx-group-cell\" [attr.colspan]=\"groupCellColspan(item)\">\r\n @if (templateFor(item.column.groupCellTemplate()); as gtpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"gtpl\"\r\n [ngTemplateOutletContext]=\"groupCellContext(item)\" />\r\n } @else {\r\n {{ item.column.caption() }}: {{ item.text }}{{ item.captionSuffix }}\r\n }\r\n </td>\r\n @for (col of groupRowTrailing(item); track col.dataField()) {\r\n <td class=\"dx-datagrid-summary-item\" [style.text-align]=\"align(col)\">\r\n {{ item.alignedSums.get(col.dataField()) || '' }}\r\n </td>\r\n }\r\n @if (groupRowTrailing(item).length && rowEditCommandVisible()) {\r\n <td class=\"dx-command-edit\"></td>\r\n }\r\n </tr>\r\n } @else if (item.kind === 'groupFooter') {\r\n <tr\r\n class=\"dx-row dx-group-footer-row nxe-group-footer-row\"\r\n [class]=\"'nxe-group-footer-level-' + item.level\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"dx-datagrid-group-space\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"dx-command-expand\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"dx-command-select\"></td>\r\n }\r\n @for (col of visibleColumns(); track col.dataField()) {\r\n <td class=\"dx-datagrid-summary-item\" [style.text-align]=\"align(col)\">\r\n {{ item.sums.get(col.dataField()) || '' }}\r\n </td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"dx-command-edit\"></td>\r\n }\r\n </tr>\r\n } @else {\r\n <tr\r\n class=\"dx-row dx-data-row\"\r\n [class]=\"rowClassFor(item.row)\"\r\n [attr.aria-rowindex]=\"item.rowIndex + 1\"\r\n [class.dx-row-lines]=\"showRowLines()\"\r\n [class.dx-column-lines]=\"showColumnLines()\"\r\n [class.dx-row-alt]=\"rowAlternationEnabled() && item.rowIndex % 2 === 1\"\r\n [class.dx-selection]=\"isSelected(item.row)\"\r\n [class.dx-state-hover]=\"isHovered(item.rowIndex)\"\r\n [class.dx-edit-row]=\"isRowEditing(item.row)\"\r\n (mouseenter)=\"onRowEnter(item.rowIndex)\"\r\n (mouseleave)=\"onRowLeave()\"\r\n (click)=\"onDataRowClick(item.row, item.rowIndex)\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"dx-datagrid-group-space\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"dx-command-expand dx-datagrid-expand\">\r\n <span\r\n class=\"nxe-detail-expander\"\r\n [class.nxe-expanded]=\"isDetailExpanded(item.row)\"\r\n (click)=\"toggleDetail(item.row, $event)\"></span>\r\n </td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"dx-command-select\">\r\n <span\r\n class=\"dx-select-checkbox nxe-grid-checkbox\"\r\n [class.nxe-checked]=\"isSelected(item.row)\"\r\n (click)=\"onCheckboxClick(item.row, $event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n </td>\r\n }\r\n @for (col of visibleColumns(); track $index; let ci = $index) {\r\n @if (!isMergeSuppressed(ri, col)) {\r\n <td\r\n [class]=\"col.cssClass()\"\r\n [attr.aria-colindex]=\"leadCellCount() + ci + 1\"\r\n [attr.aria-roledescription]=\"\r\n editableCell(col) && (cellEditingEnabled() || rowEditingEnabled())\r\n ? 'Editable'\r\n : null\r\n \"\r\n [style.text-align]=\"align(col)\"\r\n [style.padding-left.px]=\"treeMode && ci === 0 ? (item.level || 0) * 18 + 4 : null\"\r\n [attr.rowspan]=\"mergeSpan(ri, col)\"\r\n [class.dx-editor-cell]=\"isEditingCell(item.row, col) || (isRowEditing(item.row) && editableCell(col))\"\r\n (click)=\"onCellClick(item.row, col, $event)\"\r\n (focusout)=\"onEditCellFocusOut(item.row, col, $event)\">\r\n @if (treeMode && ci === 0) {\r\n @if (item.hasChildren) {\r\n <span\r\n class=\"nxe-tree-expander\"\r\n [class.nxe-collapsed]=\"!item.expanded\"\r\n (click)=\"toggleTreeNode(item.nodeKey, $event)\"></span>\r\n } @else {\r\n <span class=\"nxe-tree-expander-stub\"></span>\r\n }\r\n }\r\n @if (isEditingCell(item.row, col) || (isRowEditing(item.row) && editableCell(col))) {\r\n @if (templateFor(col.editCellTemplate()); as etpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"etpl\"\r\n [ngTemplateOutletContext]=\"editCellContext(item.row, item.rowIndex, ci, col)\" />\r\n } @else if (isEditingCell(item.row, col)) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"cellEditor\"\r\n [ngTemplateOutletContext]=\"{ row: item.row, col: col, mode: 'cell' }\" />\r\n } @else {\r\n <ng-container\r\n [ngTemplateOutlet]=\"cellEditor\"\r\n [ngTemplateOutletContext]=\"{ row: item.row, col: col, mode: 'row' }\" />\r\n }\r\n } @else if (templateFor(col.cellTemplate()); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"cellContext(item.row, item.rowIndex, ci, col)\" />\r\n } @else if (isBooleanCell(col, item.row)) {\r\n <!-- dx parity: boolean cells render a read-only dx-style checkbox, not \"true\"/\"false\" -->\r\n <span\r\n class=\"nxe-grid-checkbox nxe-grid-checkbox-readonly\"\r\n [class.nxe-checked]=\"getRaw(item.row, col.dataField()) === true\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n } @else {\r\n {{ cellText(col, item.row) }}\r\n }\r\n </td>\r\n }\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"dx-command-edit dx-datagrid-drag-action\">\r\n @if (isRowEditing(item.row)) {\r\n <a class=\"dx-link dx-link-save\" (click)=\"saveRowEdit(item.row, $event)\">Save</a>\r\n <a class=\"dx-link dx-link-cancel\" (click)=\"cancelRowEdit($event)\">Cancel</a>\r\n } @else {\r\n <a class=\"dx-link dx-link-edit\" (click)=\"startRowEdit(item.row, $event)\">Edit</a>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n @if (masterDetailEnabled() && isDetailExpanded(item.row)) {\r\n <tr class=\"dx-row dx-master-detail-row\">\r\n <td [attr.colspan]=\"allColCount()\">\r\n @if (detailTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"detailContext(item.row, item.rowIndex)\" />\r\n }\r\n </td>\r\n </tr>\r\n }\r\n }\r\n }\r\n <tr class=\"dx-row dx-freespace-row\">\r\n <td [attr.colspan]=\"allColCount()\"></td>\r\n </tr>\r\n </tbody>\r\n @if (hasTotalFooter()) {\r\n <tfoot class=\"dx-datagrid-total-footer\">\r\n <tr class=\"dx-row\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"dx-datagrid-group-space\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"dx-command-expand\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"dx-command-select\"></td>\r\n }\r\n @for (col of visibleColumns(); track col.dataField()) {\r\n <td class=\"dx-datagrid-summary-item\" [style.text-align]=\"align(col)\">\r\n {{ totalSums().get(col.dataField()) || '' }}\r\n </td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"dx-command-edit\"></td>\r\n }\r\n </tr>\r\n </tfoot>\r\n }\r\n </table>\r\n </div>\r\n\r\n @if (displayRows().length === 0 && !loading()) {\r\n <div class=\"dx-datagrid-nodata\">{{ noDataText() }}</div>\r\n }\r\n @if (loading()) {\r\n <div class=\"dx-datagrid-loadpanel\"><span class=\"nxe-grid-spinner\"></span></div>\r\n }\r\n </div>\r\n\r\n @if (pagerVisible()) {\r\n <div class=\"dx-datagrid-pager dx-pager nxe-grid-pager\">\r\n @if (pagerCmp()?.showPageSizeSelector() && pageSizes().length > 1) {\r\n <select class=\"nxe-pager-size\" (change)=\"onPageSizeSelect($event)\">\r\n @for (s of pageSizes(); track s) {\r\n <option [value]=\"s\" [selected]=\"s === effectivePageSize()\">{{ s }}</option>\r\n }\r\n </select>\r\n }\r\n <div class=\"nxe-pager-pages\">\r\n @if (pagerCmp()?.showNavigationButtons()) {\r\n <button type=\"button\" class=\"nxe-pager-nav\" [disabled]=\"pageIndex() === 0\" (click)=\"prevPage()\">\u2039</button>\r\n }\r\n @for (p of pageNumbers(); track p) {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-pager-page\"\r\n [class.nxe-active]=\"p === pageIndex()\"\r\n (click)=\"goToPage(p)\">\r\n {{ p + 1 }}\r\n </button>\r\n }\r\n @if (pagerCmp()?.showNavigationButtons()) {\r\n <button type=\"button\" class=\"nxe-pager-nav\" [disabled]=\"pageIndex() >= pageCount() - 1\" (click)=\"nextPage()\">\u203A</button>\r\n }\r\n </div>\r\n @if (pagerCmp()?.showInfo()) {\r\n <span class=\"nxe-pager-info\">{{ pageInfo() }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n\r\n<!-- Inline cell/row editor (dxo-editing, orbit addition). `mode: 'cell'` commits on blur/Enter;\r\n `mode: 'row'` buffers on input until the command column's Save. -->\r\n<ng-template #cellEditor let-row=\"row\" let-col=\"col\" let-mode=\"mode\">\r\n @if (col.lookup(); as lookup) {\r\n <select\r\n class=\"dx-texteditor-input nxe-grid-cell-editor\"\r\n [value]=\"editorInputValue(row, col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"\r\n mode === 'cell'\r\n ? commitCellEdit(row, col, $any($event.target).value)\r\n : bufferRowEdit(col, $any($event.target).value)\r\n \">\r\n <option value=\"\"></option>\r\n @for (item of lookup.dataSource; track $index) {\r\n <option [value]=\"item[lookup.valueExpr ?? 'lovCode']\">\r\n {{ item[lookup.displayExpr ?? 'lovName'] }}\r\n </option>\r\n }\r\n </select>\r\n } @else if (col.dataType() === 'boolean') {\r\n <input\r\n type=\"checkbox\"\r\n class=\"nxe-grid-cell-editor-check\"\r\n [checked]=\"!!editorInputValue(row, col) && editorInputValue(row, col) !== 'false'\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"\r\n mode === 'cell'\r\n ? commitCellEdit(row, col, $any($event.target).checked)\r\n : bufferRowEdit(col, $any($event.target).checked)\r\n \" />\r\n } @else {\r\n <input\r\n type=\"text\"\r\n class=\"dx-texteditor-input nxe-grid-cell-editor\"\r\n [value]=\"editorInputValue(row, col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.enter)=\"mode === 'cell' && commitCellEdit(row, col, $any($event.target).value)\"\r\n (keydown.escape)=\"mode === 'cell' && cancelCellEdit()\"\r\n (blur)=\"mode === 'cell' && commitCellEdit(row, col, $any($event.target).value)\"\r\n (input)=\"mode === 'row' && bufferRowEdit(col, $any($event.target).value)\" />\r\n }\r\n</ng-template>\r\n\r\n<!-- The header-filter popup (#hfPanel) moved to nxe-header-filter-panel.component.html \u2014\r\n openHeaderFilter attaches it through openNxeHeaderFilterOverlay (pivot parity Phase 3a). -->\r\n\r\n<!-- Filter-row operation-chooser dropdown (dx filter menu) -->\r\n<ng-template #frOpMenu>\r\n <div class=\"nxe-fr-op-menu dx-datagrid\">\r\n @for (item of frMenuOps(); track item.op) {\r\n <div\r\n class=\"dx-menu-item\"\r\n [class.dx-menu-item-selected]=\"frMenuColumn() && frOp(frMenuColumn()!) === item.op\"\r\n (click)=\"selectFilterOp(item.op)\">\r\n <span class=\"dx-icon dx-icon-filter-operation-{{ opIconSlug(item.op) }}\"></span>\r\n <span class=\"dx-menu-item-text\">{{ item.label }}</span>\r\n </div>\r\n }\r\n <div class=\"dx-menu-item nxe-fr-reset\" (click)=\"resetFilterRow()\">\r\n <span class=\"dx-icon\"></span>\r\n <span class=\"dx-menu-item-text\">Reset</span>\r\n </div>\r\n </div>\r\n</ng-template>\r\n\r\n", styles: ["@charset \"UTF-8\";.nxe-data-grid{display:block}.nxe-data-grid .dx-datagrid{height:100%;position:relative}.nxe-data-grid .dx-gridbase-container{display:flex;flex-direction:column;height:100%}.nxe-data-grid .dx-datagrid-rowsview{position:relative;overflow:auto;flex:1 1 auto;min-height:0}.nxe-data-grid .dx-datagrid-content{display:block;min-height:100%}.nxe-data-grid table.dx-datagrid-table{display:table;width:100%;table-layout:fixed;border-collapse:collapse;margin:0}.nxe-data-grid .dx-datagrid-content .dx-datagrid-table{max-width:none}.nxe-data-grid .dx-datagrid-headers{display:table-header-group;position:sticky;top:0;z-index:1;background:#fff;border-bottom:0}.nxe-data-grid tbody{display:table-row-group}.nxe-data-grid .dx-row{display:table-row}.nxe-data-grid .dx-datagrid-headers .dx-header-row>td{border-bottom:1px solid #e2e8f0}.nxe-data-grid .dx-datagrid-nodata{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#333;font-size:17px;opacity:.7;pointer-events:none}.nxe-data-grid .dx-datagrid-loadpanel{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#ffffff80;z-index:2}.nxe-data-grid .nxe-grid-spinner{width:32px;height:32px;border:3px solid rgba(48,55,76,.2);border-top-color:#30374c;border-radius:50%;animation:nxe-grid-spin .8s linear infinite}.nxe-data-grid .dx-column-indicators{display:inline-block;vertical-align:top;white-space:nowrap}.nxe-data-grid .dx-sort{display:inline-block;position:relative;width:14px;height:14px;vertical-align:middle}.nxe-data-grid .dx-sort:after{content:\"\";position:absolute;left:50%;top:50%;width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent}.nxe-data-grid .dx-sort.dx-sort-up:after{border-bottom:5px solid #757575;transform:translate(-50%,-50%)}.nxe-data-grid .dx-sort.dx-sort-down:after{border-top:5px solid #757575;transform:translate(-50%,-50%)}.nxe-data-grid .dx-header-row>td{cursor:default;-webkit-user-select:none;user-select:none;height:27px}.nxe-data-grid .dx-header-row>td{position:relative}.nxe-data-grid .nxe-grid-hf-icon{display:inline-block;position:relative;width:14px;height:14px;cursor:pointer;vertical-align:middle;opacity:.5}.nxe-data-grid .nxe-grid-hf-icon:before{content:\"\";position:absolute;inset:0;background-color:#757575;clip-path:polygon(0 0,100% 0,58% 50%,58% 100%,42% 85%,42% 50%)}.nxe-data-grid .nxe-grid-hf-icon:after{content:\"\";position:absolute;inset:0;margin:-7px}.nxe-data-grid .nxe-grid-hf-icon:hover{opacity:.75}.nxe-data-grid .nxe-grid-hf-icon.nxe-hf-active{opacity:1}.nxe-data-grid .nxe-grid-pager{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:8px 12px;border-top:1px solid #ddd}.nxe-data-grid .nxe-pager-size{padding:2px 6px;border:1px solid #ddd;border-radius:3px;font:inherit}.nxe-data-grid .nxe-pager-pages{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.nxe-data-grid .nxe-pager-page,.nxe-data-grid .nxe-pager-nav{min-width:24px;padding:3px 7px 4px;border:1px solid transparent;border-radius:3px;background:transparent;color:#333;cursor:pointer;font:inherit;line-height:1}.nxe-data-grid .nxe-pager-page:hover:not(:disabled),.nxe-data-grid .nxe-pager-nav:hover:not(:disabled){background:#f4f4f4}.nxe-data-grid .nxe-pager-page:disabled,.nxe-data-grid .nxe-pager-nav:disabled{opacity:.4;cursor:default}.nxe-data-grid .nxe-pager-page.nxe-active,.nxe-data-grid .nxe-pager-nav.nxe-active{background:#d4d4d4;border-color:transparent}.nxe-data-grid .nxe-pager-info{color:#757575;margin-left:auto}.nxe-data-grid .dx-datagrid-header-panel{padding:.5rem .75rem}.nxe-data-grid .dx-datagrid-group-panel{align-items:center;min-height:36px}.nxe-data-grid .nxe-tree-expander,.nxe-data-grid .nxe-tree-expander-stub{display:inline-block;width:16px;height:16px;flex:0 0 16px;vertical-align:middle;margin-right:2px}.nxe-data-grid .nxe-tree-expander{position:relative;cursor:pointer}.nxe-data-grid .nxe-tree-expander:before{content:\"\";position:absolute;left:4px;top:4px;width:6px;height:6px;border-right:1.5px solid #555;border-bottom:1.5px solid #555;transform:rotate(45deg)}.nxe-data-grid .nxe-tree-expander.nxe-collapsed:before{transform:rotate(-45deg)}.nxe-data-grid .dx-group-row>td{padding:4px 8px;background-color:#f7f7f7}.nxe-data-grid .dx-group-row>td.dx-group-cell{font-weight:700}.nxe-data-grid .dx-group-row>td.dx-datagrid-expand{cursor:pointer}.nxe-data-grid .dx-group-row>td.dx-datagrid-summary-item{font-weight:400;color:#333}.nxe-data-grid .dx-datagrid-group-opened,.nxe-data-grid .dx-datagrid-group-closed{display:inline-block;position:relative;width:14px;height:14px;cursor:pointer}.nxe-data-grid .dx-datagrid-group-opened:before,.nxe-data-grid .dx-datagrid-group-closed:before{content:\"\"!important;position:absolute;left:3px;right:auto;top:2px;width:6px;height:6px;border-right:1.5px solid #555;border-bottom:1.5px solid #555;transform:rotate(45deg)}.nxe-data-grid .dx-datagrid-group-closed:before{transform:rotate(-45deg)}.nxe-data-grid .dx-datagrid-total-footer>tr>td,.nxe-data-grid tfoot.dx-datagrid-total-footer td{border-top:1px solid #ddd;padding:6px 4px;font-weight:700;color:#959595}.nxe-data-grid .dx-datagrid-filter-row>td{padding:2px 4px}.nxe-data-grid .nxe-grid-filter-input{width:100%;box-sizing:border-box;padding:4px 6px;border:1px solid #ddd;border-radius:2px;font:inherit;outline:none}.nxe-data-grid .nxe-grid-filter-input:focus{border-color:#30374c}.nxe-data-grid .dx-command-select{width:40px;min-width:40px;max-width:40px;text-align:center;vertical-align:middle}.nxe-data-grid .nxe-grid-checkbox{display:inline-block;cursor:pointer;line-height:0}.nxe-data-grid .nxe-grid-checkbox .nxe-grid-checkbox-box{display:inline-block;position:relative;width:16px;height:16px;box-sizing:border-box;border:1px solid #ddd;border-radius:2px;background:#fff;vertical-align:middle}.nxe-data-grid .nxe-grid-checkbox.nxe-checked .nxe-grid-checkbox-box{border-color:#ddd;background:#fff}.nxe-data-grid .nxe-grid-checkbox.nxe-checked .nxe-grid-checkbox-box:after{content:\"\";position:absolute;left:4px;top:1px;width:5px;height:9px;border:solid #337ab7;border-width:0 2px 2px 0;transform:rotate(45deg)}.nxe-data-grid .nxe-grid-checkbox.nxe-indeterminate .nxe-grid-checkbox-box{border-color:#ddd}.nxe-data-grid .nxe-grid-checkbox.nxe-indeterminate .nxe-grid-checkbox-box:after{content:\"\";position:absolute;left:3px;top:3px;width:8px;height:8px;background:#337ab7}.nxe-data-grid .nxe-grid-checkbox.nxe-grid-checkbox-readonly{display:block;text-align:center;cursor:default;height:18px;line-height:18px}@keyframes nxe-grid-spin{to{transform:rotate(360deg)}}.nxe-data-grid .dx-command-expand{width:30px;text-align:center;cursor:pointer}.nxe-detail-expander{display:inline-block;width:0;height:0;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:6px solid #999;cursor:pointer;transition:transform .15s ease}.nxe-detail-expander.nxe-expanded{transform:rotate(90deg)}.nxe-data-grid .dx-master-detail-row>td{padding:8px 12px;background:#fafafa}.nxe-grid-cell-editor{width:100%;box-sizing:border-box;border:1px solid #337ab7;padding:2px 4px;font:inherit;background:#fff;outline:none}.nxe-data-grid .dx-command-edit{text-align:center;white-space:nowrap}.nxe-data-grid .dx-command-edit .dx-link{color:#337ab7;cursor:pointer;margin:0 4px;text-decoration:none}.nxe-data-grid .dx-command-edit .dx-link:hover{text-decoration:underline}.nxe-data-grid .dx-header-row td.dx-datagrid-drag-action,.nxe-data-grid .dx-group-panel-item{touch-action:none}.nxe-data-grid .nxe-drag-source{opacity:.5}.nxe-data-grid .dx-datagrid-filter-row .dx-menu ul{list-style:none;margin:0;padding:0}.nxe-data-grid .dx-datagrid-filter-row .dx-editor-with-menu{display:flex}.nxe-data-grid .dx-datagrid-filter-row .dx-editor-with-menu .nxe-grid-filter-input{flex:1 1 0;min-width:0}.nxe-data-grid .dx-datagrid-filter-row .dx-editor-with-menu .nxe-fr-second{padding-left:4px!important;border-left:1px solid #ddd}.nxe-data-grid .dx-datagrid-filter-row .dx-icon[class*=dx-icon-filter-operation-],.nxe-fr-op-menu .dx-icon[class*=dx-icon-filter-operation-]{font:600 12px/18px inherit!important;font-family:inherit!important;display:inline-block;width:18px;height:18px;text-align:center;color:#757575}.nxe-data-grid .dx-icon-filter-operation-contains:before,.nxe-fr-op-menu .dx-icon-filter-operation-contains:before{content:\"*\"!important}.nxe-data-grid .dx-icon-filter-operation-not-contains:before,.nxe-fr-op-menu .dx-icon-filter-operation-not-contains:before{content:\"!*\"!important}.nxe-data-grid .dx-icon-filter-operation-starts-with:before,.nxe-fr-op-menu .dx-icon-filter-operation-starts-with:before{content:\"a*\"!important}.nxe-data-grid .dx-icon-filter-operation-ends-with:before,.nxe-fr-op-menu .dx-icon-filter-operation-ends-with:before{content:\"*a\"!important}.nxe-data-grid .dx-icon-filter-operation-equals:before,.nxe-fr-op-menu .dx-icon-filter-operation-equals:before{content:\"=\"!important}.nxe-data-grid .dx-icon-filter-operation-not-equals:before,.nxe-fr-op-menu .dx-icon-filter-operation-not-equals:before{content:\"\\2260\"!important}.nxe-data-grid .dx-icon-filter-operation-less:before,.nxe-fr-op-menu .dx-icon-filter-operation-less:before{content:\"<\"!important}.nxe-data-grid .dx-icon-filter-operation-greater:before,.nxe-fr-op-menu .dx-icon-filter-operation-greater:before{content:\">\"!important}.nxe-data-grid .dx-icon-filter-operation-less-equal:before,.nxe-fr-op-menu .dx-icon-filter-operation-less-equal:before{content:\"\\2264\"!important}.nxe-data-grid .dx-icon-filter-operation-greater-equal:before,.nxe-fr-op-menu .dx-icon-filter-operation-greater-equal:before{content:\"\\2265\"!important}.nxe-data-grid .dx-icon-filter-operation-between:before,.nxe-fr-op-menu .dx-icon-filter-operation-between:before{content:\"\\2194\"!important}.nxe-fr-op-menu{background:#fff;border:1px solid #ddd;box-shadow:0 2px 6px #00000026;padding:4px 0;min-width:180px}.nxe-fr-op-menu .dx-menu-item{display:flex;align-items:center;gap:8px;padding:6px 24px 6px 8px;cursor:pointer;color:#333}.nxe-fr-op-menu .dx-menu-item:hover{background:#f0f0f0}.nxe-fr-op-menu .dx-menu-item.dx-menu-item-selected{background:#e6e6e6}.nxe-fr-op-menu .dx-menu-item .dx-icon{width:18px;text-align:center}.nxe-data-grid .nxe-col-resize-handle{position:absolute;right:-4px;top:0;bottom:0;width:8px;cursor:col-resize;z-index:2;touch-action:none}body.nxe-grid-resizing{cursor:col-resize;user-select:none;-webkit-user-select:none}.nxe-col-drop-indicator{position:fixed;width:2px;background:#337ab7;z-index:10000;pointer-events:none}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
5107
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeDataGridComponent, isStandalone: true, selector: "nxe-data-grid, nxe-tree-list", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, keyExpr: { classPropertyName: "keyExpr", publicName: "keyExpr", isSignal: true, isRequired: false, transformFunction: null }, hoverStateEnabledInput: { classPropertyName: "hoverStateEnabledInput", publicName: "hoverStateEnabled", isSignal: true, isRequired: false, transformFunction: null }, showColumnLinesInput: { classPropertyName: "showColumnLinesInput", publicName: "showColumnLines", isSignal: true, isRequired: false, transformFunction: null }, showRowLinesInput: { classPropertyName: "showRowLinesInput", publicName: "showRowLines", isSignal: true, isRequired: false, transformFunction: null }, showBordersInput: { classPropertyName: "showBordersInput", publicName: "showBorders", isSignal: true, isRequired: false, transformFunction: null }, wordWrapEnabledInput: { classPropertyName: "wordWrapEnabledInput", publicName: "wordWrapEnabled", isSignal: true, isRequired: false, transformFunction: null }, columnAutoWidthInput: { classPropertyName: "columnAutoWidthInput", publicName: "columnAutoWidth", isSignal: true, isRequired: false, transformFunction: null }, rowAlternationEnabledInput: { classPropertyName: "rowAlternationEnabledInput", publicName: "rowAlternationEnabled", isSignal: true, isRequired: false, transformFunction: null }, remoteOperations: { classPropertyName: "remoteOperations", publicName: "remoteOperations", isSignal: true, isRequired: false, transformFunction: null }, syncLookupFilterValues: { classPropertyName: "syncLookupFilterValues", publicName: "syncLookupFilterValues", isSignal: true, isRequired: false, transformFunction: null }, columnMinWidth: { classPropertyName: "columnMinWidth", publicName: "columnMinWidth", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, noDataTextInput: { classPropertyName: "noDataTextInput", publicName: "noDataText", isSignal: true, isRequired: false, transformFunction: null }, loadPanel: { classPropertyName: "loadPanel", publicName: "loadPanel", isSignal: true, isRequired: false, transformFunction: null }, paging: { classPropertyName: "paging", publicName: "paging", isSignal: true, isRequired: false, transformFunction: null }, filterRow: { classPropertyName: "filterRow", publicName: "filterRow", isSignal: true, isRequired: false, transformFunction: null }, headerFilterOptions: { classPropertyName: "headerFilterOptions", publicName: "headerFilter", isSignal: true, isRequired: false, transformFunction: null }, groupingOptions: { classPropertyName: "groupingOptions", publicName: "grouping", isSignal: true, isRequired: false, transformFunction: null }, groupPanelOptions: { classPropertyName: "groupPanelOptions", publicName: "groupPanel", isSignal: true, isRequired: false, transformFunction: null }, scrolling: { classPropertyName: "scrolling", publicName: "scrolling", isSignal: true, isRequired: false, transformFunction: null }, sorting: { classPropertyName: "sorting", publicName: "sorting", isSignal: true, isRequired: false, transformFunction: null }, allowColumnResizing: { classPropertyName: "allowColumnResizing", publicName: "allowColumnResizing", isSignal: true, isRequired: false, transformFunction: null }, columnResizingMode: { classPropertyName: "columnResizingMode", publicName: "columnResizingMode", isSignal: true, isRequired: false, transformFunction: null }, allowColumnReordering: { classPropertyName: "allowColumnReordering", publicName: "allowColumnReordering", isSignal: true, isRequired: false, transformFunction: null }, repaintChangesOnly: { classPropertyName: "repaintChangesOnly", publicName: "repaintChangesOnly", isSignal: true, isRequired: false, transformFunction: null }, showColumnHeaders: { classPropertyName: "showColumnHeaders", publicName: "showColumnHeaders", isSignal: true, isRequired: false, transformFunction: null }, parentIdExpr: { classPropertyName: "parentIdExpr", publicName: "parentIdExpr", isSignal: true, isRequired: false, transformFunction: null }, dataStructure: { classPropertyName: "dataStructure", publicName: "dataStructure", isSignal: true, isRequired: false, transformFunction: null }, rootValue: { classPropertyName: "rootValue", publicName: "rootValue", isSignal: true, isRequired: false, transformFunction: null }, autoExpandAll: { classPropertyName: "autoExpandAll", publicName: "autoExpandAll", isSignal: true, isRequired: false, transformFunction: null }, expandedRowKeys: { classPropertyName: "expandedRowKeys", publicName: "expandedRowKeys", isSignal: true, isRequired: false, transformFunction: null }, selectedRowKeys: { classPropertyName: "selectedRowKeys", publicName: "selectedRowKeys", isSignal: true, isRequired: false, transformFunction: null }, mergeColumns: { classPropertyName: "mergeColumns", publicName: "mergeColumns", isSignal: true, isRequired: false, transformFunction: null }, rowClass: { classPropertyName: "rowClass", publicName: "rowClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandedRowKeys: "expandedRowKeysChange", selectedRowKeys: "selectedRowKeysChange", onSelectionChanged: "onSelectionChanged", onRowClick: "onRowClick", onContentReady: "onContentReady", onOptionChanged: "onOptionChanged", onCellPrepared: "onCellPrepared", onRowPrepared: "onRowPrepared", onRowExpanding: "onRowExpanding", onRowExpanded: "onRowExpanded" }, host: { classAttribute: "nxe-widget nxe-data-grid" }, queries: [{ propertyName: "declaredColumns", predicate: NxeColumnComponent, isSignal: true }, { propertyName: "templates", predicate: NxeTemplateDirective, isSignal: true }, { propertyName: "selection", first: true, predicate: NxeSelectionComponent, descendants: true, isSignal: true }, { propertyName: "headerFilter", first: true, predicate: NxeHeaderFilterComponent, descendants: true, isSignal: true }, { propertyName: "grouping", first: true, predicate: NxeGroupingComponent, descendants: true, isSignal: true }, { propertyName: "groupPanel", first: true, predicate: NxeGroupPanelComponent, descendants: true, isSignal: true }, { propertyName: "summaryCmp", first: true, predicate: NxeSummaryComponent, descendants: true, isSignal: true }, { propertyName: "pagingCmp", first: true, predicate: NxePagingComponent, descendants: true, isSignal: true }, { propertyName: "pagerCmp", first: true, predicate: NxePagerComponent, descendants: true, isSignal: true }, { propertyName: "editingCmp", first: true, predicate: NxeEditingComponent, descendants: true, isSignal: true }, { propertyName: "filterRowCmp", first: true, predicate: NxeFilterRowComponent, descendants: true, isSignal: true }, { propertyName: "masterDetail", first: true, predicate: NxeMasterDetailComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "frMenuPanel", first: true, predicate: ["frOpMenu"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\r\n class=\"nxe-widget nxe-datagrid\"\r\n [class.nxe-datagrid-borders]=\"showBorders()\"\r\n [style.height]=\"heightStyle()\">\r\n <div class=\"nxe-gridbase-container\">\r\n @if (groupPanelVisible()) {\r\n <div class=\"nxe-datagrid-header-panel\">\r\n <div class=\"nxe-datagrid-group-panel\">\r\n @if (groupColumns().length === 0) {\r\n <span class=\"nxe-group-panel-message\">Drag a column header here to group by that column</span>\r\n } @else {\r\n @for (col of groupColumns(); track col.dataField(); let li = $index) {\r\n <!-- dx chip DOM: caption + sort indicator + header-filter button (bright audit) -->\r\n <div\r\n class=\"nxe-group-panel-item nxe-datagrid-action nxe-datagrid-drag-action\"\r\n tabindex=\"0\"\r\n (pointerdown)=\"onChipPointerDown(col, li, $event)\"\r\n (click)=\"onChipClick(col)\">{{ col.caption() }}<div class=\"nxe-column-indicators\" role=\"presentation\"><span\r\n class=\"nxe-sort\"\r\n [class.nxe-sort-up]=\"groupDir(col) === 'asc'\"\r\n [class.nxe-sort-down]=\"groupDir(col) === 'desc'\"></span>@if (headerFilterVisible(col)) {<span\r\n class=\"nxe-grid-hf-icon\"\r\n [class.nxe-hf-active]=\"hasHeaderFilter(col)\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"openHeaderFilter(col, $event)\"></span>}</div></div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n <!-- dx drops the last row's bottom line in a bordered grid when no free-space row follows\r\n it, so the rows view's own border is the only line there (m_rows_view.js:931-937). -->\r\n <div\r\n class=\"nxe-datagrid-rowsview nxe-datagrid-after-headers\"\r\n [class.nxe-datagrid-nowrap]=\"!wordWrapEnabled()\"\r\n [class.nxe-empty]=\"isEmpty()\"\r\n [class.nxe-last-row-border]=\"showBorders() && !freeSpaceVisible()\">\r\n <div class=\"nxe-datagrid-content\">\r\n <!-- role=grid (treegrid when hierarchical) rather than the implicit table role:\r\n this is an interactive widget with a single tab stop and arrow-key movement,\r\n not a static table.\r\n\r\n aria-rowcount counts the PAGE, because aria-rowindex is page-relative and the\r\n two have to agree. Making the index absolute would be more literally correct\r\n for a paged grid, but consuming code addresses cells through it\r\n (`tr[aria-rowindex=N] > td[aria-colindex=M]`), so the index is a contract. -->\r\n <table\r\n class=\"nxe-datagrid-table nxe-datagrid-table-fixed\"\r\n [attr.role]=\"treeMode ? 'treegrid' : 'grid'\"\r\n [attr.aria-rowcount]=\"navigableRows().length\"\r\n [attr.aria-colcount]=\"visibleColumns().length + leadCellCount() + (rowEditCommandVisible() ? 1 : 0)\"\r\n [style.width]=\"tableWidthStyle()\"\r\n (keydown)=\"onGridKeydown($event)\">\r\n <colgroup>\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <col style=\"width: 30px\" />\r\n }\r\n @if (masterDetailEnabled()) {\r\n <col style=\"width: 30px\" />\r\n }\r\n @if (showCheckboxes()) {\r\n <col style=\"width: 40px\" />\r\n }\r\n @for (col of visibleColumns(); track $index; let ci = $index) {\r\n <col [style.width]=\"effectiveColWidth(col, ci)\" />\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <col style=\"width: 100px\" />\r\n }\r\n </colgroup>\r\n <thead class=\"nxe-datagrid-headers\">\r\n <!-- Bands add header rows: a band cell spans its visible leaves with\r\n colspan, and a leaf above the bottom row spans down with rowspan.\r\n That is dx's own banded markup, and it keeps the LAST header row\r\n all-leaves - which the auto-width, resize and drag code relies on. -->\r\n @for (hrow of headerRows(); track $index; let hi = $index) {\r\n <tr\r\n class=\"nxe-row nxe-header-row\"\r\n role=\"row\"\r\n [class.nxe-column-lines]=\"showColumnLines()\">\r\n @if (hi === 0) {\r\n <!-- dx numbers the header's command cells like the body's\r\n (m_columns_view.js:681-693); they are not role=columnheader\r\n (m_column_headers.js:193), which keeps them out of the leaf\r\n header list (nxeHeaderLeafCells). -->\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td\r\n class=\"nxe-command-expand nxe-datagrid-group-space nxe-cell-focus-disabled\"\r\n [attr.aria-colindex]=\"i + 1\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <!-- dx gives every cell of an 'expand' command column the\r\n group-space class (m_columns_view.js:290-293), which drops\r\n the column line between it and the next cell. -->\r\n <td\r\n class=\"nxe-command-expand nxe-datagrid-group-space\"\r\n [attr.aria-colindex]=\"expandColIndex()\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td\r\n class=\"nxe-command-select\"\r\n [attr.aria-colindex]=\"selectColIndex()\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\">\r\n @if (selectionMode() === 'multiple') {\r\n <span\r\n class=\"nxe-select-checkbox nxe-grid-checkbox nxe-checkbox nxe-datagrid-checkbox-size\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"allSelected() ? 'true' : someSelected() ? 'mixed' : 'false'\"\r\n [class.nxe-checked]=\"allSelected()\"\r\n [class.nxe-indeterminate]=\"someSelected() && !allSelected()\"\r\n (click)=\"toggleSelectAll($event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n }\r\n </td>\r\n }\r\n }\r\n @for (cell of hrow; track $index) {\r\n @if (cell.isBand) {\r\n <!-- dx gives a band the alignment of any column: left unless set\r\n (a band has no dataType to right-align or centre it by). -->\r\n <td\r\n class=\"nxe-band-cell\"\r\n role=\"columnheader\"\r\n [class]=\"cell.column.cssClass()\"\r\n [attr.colspan]=\"cell.colspan > 1 ? cell.colspan : null\"\r\n [style.text-align]=\"align(cell.column)\">\r\n <div class=\"nxe-datagrid-text-content\">{{ cell.column.caption() }}</div>\r\n </td>\r\n } @else {\r\n @let hDir = sortDir(cell.column);\r\n @let hHf = headerFilterVisible(cell.column);\r\n @let hSortSlot = sortSlot(cell.column);\r\n @let hInd = hSortSlot || hHf;\r\n @let hAlign = align(cell.column);\r\n <!-- dx places the indicator block before the caption when column lines are\r\n on or the column is right-aligned (m_column_headers.js createCellContent,\r\n m_column_state_mixin.js _applyColumnState), floated to the cell edge\r\n only with column lines: right, or left for a right-aligned column. -->\r\n @let hLead = hInd && (showColumnLines() || hAlign === 'right');\r\n @let hFloat = showColumnLines() ? (hAlign === 'right' ? 'left' : 'right') : null;\r\n <td\r\n [class]=\"cell.column.cssClass()\"\r\n role=\"columnheader\"\r\n [attr.aria-colindex]=\"leadCellCount() + cell.leafIndex + 1\"\r\n [attr.aria-sort]=\"ariaSort(cell.column)\"\r\n [attr.tabindex]=\"isCellTabStop(-1, cell.leafIndex) ? 0 : -1\"\r\n (focus)=\"focusCell(-1, cell.leafIndex)\"\r\n [attr.rowspan]=\"cell.rowspan > 1 ? cell.rowspan : null\"\r\n [style.text-align]=\"hAlign\"\r\n [class.nxe-cell-focus-disabled]=\"!cell.column.allowSorting()\"\r\n [class.nxe-datagrid-action]=\"sortAllowed(cell.column)\"\r\n [class.nxe-datagrid-drag-action]=\"headerDraggable(cell.column)\"\r\n (pointerdown)=\"onHeaderPointerDown(cell.column, cell.leafIndex, $event)\"\r\n (click)=\"onHeaderClick(cell.column)\">\r\n @if (hLead) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"headerIndicators\"\r\n [ngTemplateOutletContext]=\"{ $implicit: cell.column, dir: hDir, sortSlot: hSortSlot, hf: hHf, hfFirst: hAlign === 'right', float: hFloat }\" />\r\n }\r\n <!-- dx _alignCaptionByCenter: a centred caption gets an unfloated,\r\n invisible copy of the indicator block on its other side, so the\r\n caption centres on the cell rather than caption + arrow centring\r\n as a group. Spacers only: no sort or funnel classes (specs and\r\n apps pick funnels by index) and no handlers - a click there falls\r\n through to the cell and sorts, as dx's hidden clone lets it. -->\r\n @if (hInd && hAlign === 'center') {\r\n <div\r\n class=\"nxe-column-indicators nxe-visibility-hidden\"\r\n role=\"presentation\"\r\n aria-hidden=\"true\">\r\n @if (hSortSlot) {\r\n <span\r\n class=\"nxe-column-indicator-spacer\"\r\n [class.nxe-column-indicator-spacer-none]=\"!hDir\"></span>\r\n }\r\n @if (hHf) {\r\n <span class=\"nxe-column-indicator-spacer\"></span>\r\n }\r\n </div>\r\n }\r\n <!-- The indicator classes are load-bearing, not decorative: they gate dx's\r\n caption budget (`.dx-header-row .dx-sort-indicator{max-width:calc(100%\r\n - 17px)}` and family) and, with the alignment classes, the 3px gap to\r\n the block. dx sets alignment-left for left and centre columns and\r\n alignment-right for right and centre ones, whenever the cell has an\r\n indicator slot at all - even an unsorted, hidden one. -->\r\n <div\r\n class=\"nxe-datagrid-text-content\"\r\n [class.nxe-text-content-alignment-left]=\"hInd && hAlign !== 'right'\"\r\n [class.nxe-text-content-alignment-right]=\"hInd && hAlign !== 'left'\"\r\n [class.nxe-sort-indicator]=\"hDir !== null\"\r\n [class.nxe-header-filter-indicator]=\"hHf\">\r\n {{ cell.column.caption() }}\r\n </div>\r\n @if (hInd && !hLead) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"headerIndicators\"\r\n [ngTemplateOutletContext]=\"{ $implicit: cell.column, dir: hDir, sortSlot: hSortSlot, hf: hHf, hfFirst: hAlign === 'right', float: null }\" />\r\n }\r\n @if (resizeHandleVisible(cell.column, cell.leafIndex)) {\r\n <span\r\n class=\"nxe-col-resize-handle\"\r\n (pointerdown)=\"startColumnResize(cell.column, cell.leafIndex, $event)\"></span>\r\n }\r\n </td>\r\n }\r\n }\r\n @if (hi === 0 && rowEditCommandVisible()) {\r\n <td\r\n class=\"nxe-command-edit\"\r\n [attr.aria-colindex]=\"editColIndex()\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\"></td>\r\n }\r\n </tr>\r\n }\r\n @if (filterRowVisible()) {\r\n <!-- The headers view toggles column lines on every row it renders, the\r\n filter row included (m_column_headers.js:205-207). -->\r\n <!-- Every filter-row cell is numbered, as the headers view numbers it\r\n (m_columns_view.js:681-693). -->\r\n <tr class=\"nxe-row nxe-datagrid-filter-row\" [class.nxe-column-lines]=\"showColumnLines()\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"i + 1\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"expandColIndex()\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"nxe-command-select\" [attr.aria-colindex]=\"selectColIndex()\"></td>\r\n }\r\n @for (col of visibleColumns(); track $index; let ci = $index) {\r\n <!-- dx filter cell: td.dx-editor-cell > .dx-editor-with-menu >\r\n [.dx-menu op chooser][typed editor] (dx-compat.css contract) -->\r\n <td\r\n class=\"nxe-editor-cell\"\r\n [class]=\"col.cssClass()\"\r\n [attr.aria-colindex]=\"leadCellCount() + ci + 1\">\r\n @if (col.allowFiltering()) {\r\n @switch (frEditorKind(col)) {\r\n @case ('lookup') {\r\n <select\r\n class=\"nxe-texteditor-input nxe-grid-filter-input\"\r\n [value]=\"frText(col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"setFilterRowText(col, $any($event.target).value)\">\r\n <option value=\"\">(All)</option>\r\n @for (item of col.lookup()!.dataSource; track $index) {\r\n <option [value]=\"item[col.lookup()!.valueExpr ?? 'lovCode']\">\r\n {{ item[col.lookup()!.displayExpr ?? 'lovName'] }}\r\n </option>\r\n }\r\n </select>\r\n }\r\n @case ('boolean') {\r\n <select\r\n class=\"nxe-texteditor-input nxe-grid-filter-input\"\r\n [value]=\"frText(col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"setFilterRowText(col, $any($event.target).value)\">\r\n <option value=\"\">(All)</option>\r\n <option value=\"true\">true</option>\r\n <option value=\"false\">false</option>\r\n </select>\r\n }\r\n @default {\r\n <div class=\"nxe-editor-with-menu\">\r\n <div class=\"nxe-menu nxe-menu-base\">\r\n <ul class=\"nxe-menu-horizontal\">\r\n <li>\r\n <div\r\n class=\"nxe-menu-item\"\r\n title=\"Filter operation\"\r\n (click)=\"openFilterOpMenu(col, $event)\">\r\n <div class=\"nxe-menu-item-content\">\r\n <span\r\n class=\"nxe-icon nxe-icon-filter-operation-{{ frOpIconSlug(col) }}\"></span>\r\n </div>\r\n </div>\r\n </li>\r\n </ul>\r\n </div>\r\n <!-- dx sets the FIRST input's text-align to the column\r\n alignment inline, unless centre (m_filter_row.js:390-395). -->\r\n <input\r\n class=\"nxe-texteditor-input nxe-grid-filter-input\"\r\n [type]=\"frEditorKind(col) === 'date' ? 'date' : 'text'\"\r\n [attr.inputmode]=\"frEditorKind(col) === 'number' ? 'decimal' : null\"\r\n [style.text-align]=\"filterCellAlign(col)\"\r\n [value]=\"frText(col)\"\r\n (input)=\"setFilterRowText(col, $any($event.target).value)\"\r\n (click)=\"$event.stopPropagation()\" />\r\n @if (frOp(col) === 'between') {\r\n <input\r\n class=\"nxe-texteditor-input nxe-grid-filter-input nxe-fr-second\"\r\n [type]=\"frEditorKind(col) === 'date' ? 'date' : 'text'\"\r\n [attr.inputmode]=\"frEditorKind(col) === 'number' ? 'decimal' : null\"\r\n [value]=\"frText2(col)\"\r\n (input)=\"setFilterRowText(col, $any($event.target).value, true)\"\r\n (click)=\"$event.stopPropagation()\" />\r\n }\r\n </div>\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"nxe-command-edit\" [attr.aria-colindex]=\"editColIndex()\"></td>\r\n }\r\n </tr>\r\n }\r\n </thead>\r\n <tbody>\r\n @for (item of renderRows(); track $index; let ri = $index) {\r\n @if (item.kind === 'group') {\r\n <!-- dx group-row anatomy (bright audit): [indent cells][expand chevron\r\n cell][dx-group-cell colspan][alignByColumn summary cells]. dx numbers\r\n every cell of a rows-view row, command cells included\r\n (m_columns_view.js:681-693), and the caption as the cell after the\r\n toggle (m_rows_view.js:626-635, columnIndex groupIndex + 1). That\r\n number can be a data column's, so dx looks group rows up with\r\n `:not(.dx-group-cell)` (m_columns_view.js:942), and onCellPrepared\r\n reports the caption as no column. -->\r\n <tr\r\n class=\"nxe-row nxe-group-row\"\r\n [class.nxe-column-lines]=\"showColumnLines()\"\r\n [attr.aria-roledescription]=\"item.collapsed ? 'Collapsed row' : 'Expanded row'\">\r\n @for (i of indents(item.level); track i) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"i + 1\"></td>\r\n }\r\n <td\r\n class=\"nxe-command-expand nxe-datagrid-group-space nxe-datagrid-expand nxe-selection-disabled\"\r\n [attr.aria-colindex]=\"item.level + 1\"\r\n [attr.aria-label]=\"item.collapsed ? 'Expand' : 'Collapse'\"\r\n (click)=\"toggleGroup(item.groupKey)\">\r\n <div\r\n [class.nxe-datagrid-group-opened]=\"!item.collapsed\"\r\n [class.nxe-datagrid-group-closed]=\"item.collapsed\"></div>\r\n </td>\r\n <td\r\n class=\"nxe-group-cell\"\r\n [attr.aria-colindex]=\"item.level + 2\"\r\n [attr.colspan]=\"groupCellColspan(item)\">\r\n @if (templateFor(item.column.groupCellTemplate()); as gtpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"gtpl\"\r\n [ngTemplateOutletContext]=\"groupCellContext(item)\" />\r\n } @else {\r\n {{ item.column.caption() }}: {{ item.text }}{{ item.captionSuffix }}\r\n }\r\n </td>\r\n @for (col of groupRowTrailing(item); track col.dataField()) {\r\n <td\r\n class=\"nxe-datagrid-summary-item\"\r\n [attr.aria-colindex]=\"leadCellCount() + visibleIndexOf(col) + 1\"\r\n [style.text-align]=\"align(col)\">\r\n {{ item.alignedSums.get(col.dataField()) || '' }}\r\n </td>\r\n }\r\n @if (groupRowTrailing(item).length && rowEditCommandVisible()) {\r\n <td class=\"nxe-command-edit\" [attr.aria-colindex]=\"editColIndex()\"></td>\r\n }\r\n </tr>\r\n } @else if (item.kind === 'groupFooter') {\r\n <!-- dx's group footer: dx-datagrid-group-footer, and column lines like\r\n any row the rows view renders (m_rows_view.js:222-229). Row lines are\r\n data rows' only (:228); the line under a footer is the column-lines\r\n rule's. -->\r\n <tr\r\n class=\"nxe-row nxe-group-footer-row nxe-datagrid-group-footer\"\r\n [class]=\"'nxe-group-footer-level-' + item.level\"\r\n [class.nxe-column-lines]=\"showColumnLines()\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"i + 1\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"expandColIndex()\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"nxe-command-select\" [attr.aria-colindex]=\"selectColIndex()\"></td>\r\n }\r\n @for (col of visibleColumns(); track col.dataField()) {\r\n <td\r\n class=\"nxe-datagrid-summary-item\"\r\n [attr.aria-colindex]=\"leadCellCount() + visibleIndexOf(col) + 1\"\r\n [style.text-align]=\"align(col)\">\r\n {{ item.sums.get(col.dataField()) || '' }}\r\n </td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"nxe-command-edit\" [attr.aria-colindex]=\"editColIndex()\"></td>\r\n }\r\n </tr>\r\n } @else {\r\n <tr\r\n class=\"nxe-row nxe-data-row\"\r\n role=\"row\"\r\n [class]=\"rowClassFor(item.row)\"\r\n [attr.aria-rowindex]=\"item.rowIndex + 1\"\r\n [attr.aria-selected]=\"selectionMode() === 'none' ? null : isSelected(item.row)\"\r\n [class.nxe-row-lines]=\"showRowLines()\"\r\n [class.nxe-column-lines]=\"showColumnLines()\"\r\n [class.nxe-row-alt]=\"rowAlternationEnabled() && item.rowIndex % 2 === 1\"\r\n [class.nxe-selection]=\"isSelected(item.row)\"\r\n [class.nxe-state-hover]=\"isHovered(item.rowIndex)\"\r\n [class.nxe-edit-row]=\"isRowEditing(item.row)\"\r\n (mouseenter)=\"onRowEnter(item.rowIndex)\"\r\n (mouseleave)=\"onRowLeave()\"\r\n (click)=\"onDataRowClick(item.row, item.rowIndex)\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"i + 1\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <!-- dx reuses the group row's toggle for the detail row\r\n (m_master_detail.js:33-43, m_utils.js:462-474), and any click\r\n in the cell toggles (m_grouping.js:523-532 `closest('.dx-datagrid-expand')`).\r\n toggleDetail stops the click there, as dx-selection-disabled does. -->\r\n <td\r\n class=\"nxe-command-expand nxe-datagrid-group-space nxe-datagrid-expand nxe-selection-disabled\"\r\n [attr.aria-colindex]=\"expandColIndex()\"\r\n [attr.aria-label]=\"isDetailExpanded(item.row) ? 'Collapse' : 'Expand'\"\r\n (click)=\"toggleDetail(item.row, $event)\">\r\n <div\r\n [class.nxe-datagrid-group-opened]=\"isDetailExpanded(item.row)\"\r\n [class.nxe-datagrid-group-closed]=\"!isDetailExpanded(item.row)\"></div>\r\n </td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"nxe-command-select\" [attr.aria-colindex]=\"selectColIndex()\">\r\n <span\r\n class=\"nxe-select-checkbox nxe-grid-checkbox nxe-checkbox nxe-datagrid-checkbox-size\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"isSelected(item.row)\"\r\n [class.nxe-checked]=\"isSelected(item.row)\"\r\n (click)=\"onCheckboxClick(item.row, $event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n </td>\r\n }\r\n @for (col of visibleColumns(); track $index; let ci = $index) {\r\n @if (!isMergeSuppressed(ri, col)) {\r\n <td\r\n [class]=\"col.cssClass()\"\r\n role=\"gridcell\"\r\n [attr.aria-colindex]=\"leadCellCount() + ci + 1\"\r\n [attr.tabindex]=\"isCellTabStop(item.rowIndex, ci) ? 0 : -1\"\r\n (focus)=\"focusCell(item.rowIndex, ci)\"\r\n [attr.aria-roledescription]=\"\r\n editableCell(col) && (cellEditingEnabled() || rowEditingEnabled())\r\n ? 'Editable'\r\n : null\r\n \"\r\n [style.text-align]=\"align(col)\"\r\n [attr.rowspan]=\"mergeSpan(ri, col)\"\r\n [class.nxe-editor-cell]=\"isEditingCell(item.row, col) || (isRowEditing(item.row) && editableCell(col))\"\r\n (click)=\"onCellClick(item.row, col, $event)\"\r\n (focusout)=\"onEditCellFocusOut(item.row, col, $event)\">\r\n @if (treeMode && ci === 0) {\r\n <!-- dx's tree icons (dx-treelist-icon-container): one 14px slot\r\n per level, then the 14x18 toggle or, on a leaf, one more slot.\r\n Floated as dx floats it in the fixed table, so the text\r\n wraps beside it and starts flush after it. Not named\r\n nxe-treelist-*: the dx mirror would hand it to an app's\r\n newer-dx tree layout (18px slots). -->\r\n <span class=\"nxe-tree-icons\">\r\n @for (i of indents(item.level || 0); track i) {\r\n <span class=\"nxe-tree-expander-stub\"></span>\r\n }\r\n @if (item.hasChildren) {\r\n <span\r\n class=\"nxe-tree-expander\"\r\n [class.nxe-collapsed]=\"!item.expanded\"\r\n (click)=\"toggleTreeNode(item.nodeKey, $event)\"></span>\r\n } @else {\r\n <span class=\"nxe-tree-expander-stub\"></span>\r\n }\r\n </span>\r\n }\r\n @if (isEditingCell(item.row, col) || (isRowEditing(item.row) && editableCell(col))) {\r\n @if (templateFor(col.editCellTemplate()); as etpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"etpl\"\r\n [ngTemplateOutletContext]=\"editCellContext(item.row, item.rowIndex, ci, col)\" />\r\n } @else if (isEditingCell(item.row, col)) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"cellEditor\"\r\n [ngTemplateOutletContext]=\"{ row: item.row, col: col, mode: 'cell' }\" />\r\n } @else {\r\n <ng-container\r\n [ngTemplateOutlet]=\"cellEditor\"\r\n [ngTemplateOutletContext]=\"{ row: item.row, col: col, mode: 'row' }\" />\r\n }\r\n } @else if (templateFor(col.cellTemplate()); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"cellContext(item.row, item.rowIndex, ci, col)\" />\r\n } @else if (isBooleanCell(col)) {\r\n <!-- dx showEditorAlways: a boolean cell is a dx checkbox, not\r\n \"true\"/\"false\". It is read-only unless the grid edits cells\r\n and the column is editable (m_editing.js:210, 1832-1847);\r\n then one click toggles the value in place. -->\r\n <span\r\n class=\"nxe-grid-checkbox nxe-grid-checkbox-readonly nxe-checkbox nxe-datagrid-checkbox-size\"\r\n role=\"checkbox\"\r\n [class.nxe-grid-checkbox-left]=\"align(col) === 'left'\"\r\n [class.nxe-grid-checkbox-right]=\"align(col) === 'right'\"\r\n [class.nxe-checked]=\"getRaw(item.row, col.dataField()) === true\"\r\n [class.nxe-state-readonly]=\"booleanReadOnly(col)\"\r\n [attr.aria-checked]=\"getRaw(item.row, col.dataField()) === true\"\r\n [attr.aria-readonly]=\"booleanReadOnly(col) ? 'true' : null\"\r\n (click)=\"onBooleanCellClick(item.row, col, $event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n } @else {\r\n <!-- In an ng-container so the text node carries no padding space:\r\n after the tree icons one would render as a 3px gap. -->\r\n <ng-container>{{ cellText(col, item.row) }}</ng-container>\r\n }\r\n </td>\r\n }\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"nxe-command-edit nxe-datagrid-drag-action\" [attr.aria-colindex]=\"editColIndex()\">\r\n @if (isRowEditing(item.row)) {\r\n <a class=\"nxe-link nxe-link-save\" (click)=\"saveRowEdit(item.row, $event)\">Save</a>\r\n <a class=\"nxe-link nxe-link-cancel\" (click)=\"cancelRowEdit($event)\">Cancel</a>\r\n } @else {\r\n <a class=\"nxe-link nxe-link-edit\" (click)=\"startRowEdit(item.row, $event)\">Edit</a>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n @if (masterDetailEnabled() && isDetailExpanded(item.row)) {\r\n <!-- dx's detail row takes row lines as well as column lines, and its one\r\n cell is dx-master-detail-cell (m_master_detail.js:290-294, :325),\r\n rendered as column 0 - so aria-colindex 1. -->\r\n <tr\r\n class=\"nxe-row nxe-master-detail-row\"\r\n [class.nxe-row-lines]=\"showRowLines()\"\r\n [class.nxe-column-lines]=\"showColumnLines()\">\r\n <td class=\"nxe-master-detail-cell nxe-cell-focus-disabled\" aria-colindex=\"1\" [attr.colspan]=\"allColCount()\">\r\n @if (detailTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"detailContext(item.row, item.rowIndex)\" />\r\n }\r\n </td>\r\n </tr>\r\n }\r\n }\r\n }\r\n <!-- dx's free-space row (m_rows_view.js:448-466): one cell per column, so the\r\n column lines run on through the space it fills. The row is\r\n role=\"presentation\", as dx's `_createEmptyRow` makes it, so assistive tech\r\n inside role=grid does not meet a row of empty gridcells; its cells take\r\n no role and no aria-colindex: nothing that addresses cells may count them. -->\r\n <tr\r\n role=\"presentation\"\r\n class=\"nxe-row nxe-freespace-row\"\r\n [class.nxe-column-lines]=\"showColumnLines()\"\r\n [style.height.px]=\"freeSpaceHeight() || null\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"nxe-command-select\"></td>\r\n }\r\n @for (col of visibleColumns(); track $index) {\r\n <td [class]=\"col.cssClass()\"></td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"nxe-command-edit\"></td>\r\n }\r\n </tr>\r\n </tbody>\r\n @if (hasTotalFooter()) {\r\n <tfoot class=\"nxe-datagrid-total-footer\">\r\n <tr class=\"nxe-row\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"i + 1\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"expandColIndex()\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"nxe-command-select\" [attr.aria-colindex]=\"selectColIndex()\"></td>\r\n }\r\n @for (col of visibleColumns(); track col.dataField()) {\r\n <td\r\n class=\"nxe-datagrid-summary-item\"\r\n [attr.aria-colindex]=\"leadCellCount() + visibleIndexOf(col) + 1\"\r\n [style.text-align]=\"align(col)\">\r\n {{ totalSums().get(col.dataField()) || '' }}\r\n </td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"nxe-command-edit\" [attr.aria-colindex]=\"editColIndex()\"></td>\r\n }\r\n </tr>\r\n </tfoot>\r\n }\r\n </table>\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <!-- dx `.dx-datagrid-rowsview.dx-empty{height:100px}`: our rows view also holds the\r\n <thead>, so the empty body is its own box after the table. dx appends the nodata\r\n span to the rows view outside its scrollable (m_utils.js:206-210) and positions the\r\n load panel `of:` the rows view, header excluded (m_rows_view.js:1023) - both live\r\n in this box. The load panel can: loading() implies a null source, hence no rows.\r\n If the total footer is ever shown for empty data (dx m_summary.js:487-503), this\r\n box has to move between the <tbody> and the <tfoot>. -->\r\n <div class=\"nxe-datagrid-empty-body\">\r\n @if (!loading()) {\r\n <span class=\"nxe-datagrid-nodata\">{{ noDataText() }}</span>\r\n } @else {\r\n <div class=\"nxe-datagrid-loadpanel\"><span class=\"nxe-grid-spinner\"></span></div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (pagerVisible()) {\r\n <!-- dx's pager (pagination/content.js) under dx's class names: the page sizes float left;\r\n the pages float right - the info text, then prev, the page buttons and next. dx\r\n renders div[role=button]s; these are real buttons with dx's labels. The prev/next\r\n buttons are empty: the chevron is drawn by CSS. At either end dx only takes the\r\n button out of the tab order (page_index_selector.js), so it keeps focus rather than\r\n dropping it as a disabled button would; a click there is a no-op (goToPage clamps). -->\r\n <div\r\n class=\"nxe-datagrid-pager nxe-pager nxe-grid-pager\"\r\n role=\"navigation\"\r\n [attr.aria-label]=\"pagerCmp()?.label()\">\r\n @if (pagerCmp()?.showPageSizeSelector()) {\r\n <div class=\"nxe-page-sizes\">\r\n @if (pagerCompact()) {\r\n @if (pageSizes().length) {\r\n <select class=\"nxe-pager-size\" aria-label=\"Page size\" (change)=\"onPageSizeSelect($event)\">\r\n @for (s of pageSizes(); track $index) {\r\n <option [value]=\"s\" [selected]=\"s === effectivePageSize()\">{{ s }}</option>\r\n }\r\n </select>\r\n }\r\n } @else {\r\n @for (s of pageSizes(); track $index; let first = $first) {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-page-size\"\r\n [class.nxe-selection]=\"s === effectivePageSize()\"\r\n [class.nxe-first-child]=\"first\"\r\n [attr.aria-label]=\"'Items per page: ' + s\"\r\n (click)=\"changePageSize(s)\">{{ s }}</button>\r\n }\r\n }\r\n </div>\r\n }\r\n <div class=\"nxe-pages\">\r\n @if (pagerCmp()?.showInfo()) {\r\n <div class=\"nxe-info nxe-pager-info\">{{ pageInfo() }}</div>\r\n }\r\n <div class=\"nxe-page-indexes nxe-pager-pages\">\r\n @if (pagerNavVisible()) {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-navigate-button nxe-prev-button nxe-pager-nav\"\r\n [class.nxe-button-disable]=\"currentPage() === 0\"\r\n [attr.aria-disabled]=\"currentPage() === 0 ? 'true' : null\"\r\n [attr.tabindex]=\"currentPage() === 0 ? -1 : null\"\r\n aria-label=\"Previous page\"\r\n (click)=\"prevPage()\"></button>\r\n }\r\n @for (item of pageItems(); track item.key) {\r\n @if (item.separator) {\r\n <div class=\"nxe-separator\">. . .</div>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-page nxe-pager-page\"\r\n [class.nxe-selection]=\"item.page === currentPage()\"\r\n [class.nxe-active]=\"item.page === currentPage()\"\r\n [attr.aria-label]=\"'Page ' + (item.page + 1)\"\r\n [attr.aria-current]=\"item.page === currentPage() ? 'page' : null\"\r\n (click)=\"goToPage(item.page)\">{{ item.page + 1 }}</button>\r\n }\r\n }\r\n @if (pagerNavVisible()) {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-navigate-button nxe-next-button nxe-pager-nav\"\r\n [class.nxe-button-disable]=\"currentPage() >= pageCount() - 1\"\r\n [attr.aria-disabled]=\"currentPage() >= pageCount() - 1 ? 'true' : null\"\r\n [attr.tabindex]=\"currentPage() >= pageCount() - 1 ? -1 : null\"\r\n aria-label=\"Next page\"\r\n (click)=\"nextPage()\"></button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n\r\n<!-- Inline cell/row editor (dxo-editing, orbit addition). `mode: 'cell'` commits on blur/Enter;\r\n `mode: 'row'` buffers on input until the command column's Save. -->\r\n<ng-template #cellEditor let-row=\"row\" let-col=\"col\" let-mode=\"mode\">\r\n @if (col.lookup(); as lookup) {\r\n <select\r\n class=\"nxe-texteditor-input nxe-grid-cell-editor\"\r\n [value]=\"editorInputValue(row, col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"\r\n mode === 'cell'\r\n ? commitCellEdit(row, col, $any($event.target).value)\r\n : bufferRowEdit(col, $any($event.target).value)\r\n \">\r\n <option value=\"\"></option>\r\n @for (item of lookup.dataSource; track $index) {\r\n <option [value]=\"item[lookup.valueExpr ?? 'lovCode']\">\r\n {{ item[lookup.displayExpr ?? 'lovName'] }}\r\n </option>\r\n }\r\n </select>\r\n } @else if (colType(col) === 'boolean') {\r\n <!-- dx's checkbox editor, the same box as the resting boolean cell (never the browser's\r\n native checkbox). Cell mode toggles resting cells in place and never gets here; this\r\n is the row being edited, and a click or Space only buffers until Save. It takes focus\r\n as dx's dxCheckBox does (tabIndex 0, Space toggles: check_box.js:125), so Tab reaches\r\n it with the row's other editors. -->\r\n <span\r\n class=\"nxe-grid-checkbox nxe-grid-checkbox-readonly nxe-checkbox nxe-datagrid-checkbox-size nxe-grid-cell-editor-check\"\r\n role=\"checkbox\"\r\n tabindex=\"0\"\r\n [class.nxe-grid-checkbox-left]=\"align(col) === 'left'\"\r\n [class.nxe-grid-checkbox-right]=\"align(col) === 'right'\"\r\n [class.nxe-checked]=\"editorChecked(row, col)\"\r\n [attr.aria-checked]=\"editorChecked(row, col)\"\r\n (click)=\"toggleEditorCheck(row, col, mode, $event)\"\r\n (keydown.space)=\"toggleEditorCheck(row, col, mode, $event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n } @else {\r\n <input\r\n type=\"text\"\r\n class=\"nxe-texteditor-input nxe-grid-cell-editor\"\r\n [value]=\"editorInputValue(row, col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.enter)=\"mode === 'cell' && commitCellEdit(row, col, $any($event.target).value)\"\r\n (keydown.escape)=\"mode === 'cell' && cancelCellEdit()\"\r\n (blur)=\"mode === 'cell' && commitCellEdit(row, col, $any($event.target).value)\"\r\n (input)=\"mode === 'row' && bufferRowEdit(col, $any($event.target).value)\" />\r\n }\r\n</ng-template>\r\n\r\n<!-- A header cell's indicator block (dx .dx-column-indicators). dx creates the sort span first and\r\n the funnel second; the funnel is prepended when the column is right-aligned\r\n (m_header_filter_core.js _renderIndicator), so a right-aligned column reads [funnel][sort] and\r\n every other [sort][funnel]. An unsorted sortable column keeps an empty `nxe-sort-none` span, as\r\n dx does: hidden in the fixed table, measured by the best-fit pass. -->\r\n<ng-template\r\n #headerIndicators\r\n let-col\r\n let-dir=\"dir\"\r\n let-sortSlot=\"sortSlot\"\r\n let-hf=\"hf\"\r\n let-hfFirst=\"hfFirst\"\r\n let-float=\"float\">\r\n <div class=\"nxe-column-indicators\" role=\"presentation\" [style.float]=\"float\">\r\n @for (kind of hfFirst ? ['funnel', 'sort'] : ['sort', 'funnel']; track kind) {\r\n @if (kind === 'funnel') {\r\n @if (hf) {\r\n <span\r\n class=\"nxe-grid-hf-icon\"\r\n [class.nxe-hf-active]=\"hasHeaderFilter(col)\"\r\n (click)=\"openHeaderFilter(col, $event)\"></span>\r\n }\r\n } @else if (sortSlot) {\r\n <span\r\n class=\"nxe-sort\"\r\n [class.nxe-sort-none]=\"!dir\"\r\n [class.nxe-sort-up]=\"dir === 'asc'\"\r\n [class.nxe-sort-down]=\"dir === 'desc'\"></span>\r\n }\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<!-- The header-filter popup (#hfPanel) moved to nxe-header-filter-panel.component.html \u2014\r\n openHeaderFilter attaches it through openNxeHeaderFilterOverlay (pivot parity Phase 3a). -->\r\n\r\n<!-- Filter-row operation-chooser dropdown (dx filter menu) -->\r\n<ng-template #frOpMenu>\r\n <div class=\"nxe-fr-op-menu nxe-datagrid\">\r\n @for (item of frMenuOps(); track item.op) {\r\n <div\r\n class=\"nxe-menu-item\"\r\n [class.nxe-menu-item-selected]=\"frMenuColumn() && frOp(frMenuColumn()!) === item.op\"\r\n (click)=\"selectFilterOp(item.op)\">\r\n <span class=\"nxe-icon nxe-icon-filter-operation-{{ opIconSlug(item.op) }}\"></span>\r\n <span class=\"nxe-menu-item-text\">{{ item.label }}</span>\r\n </div>\r\n }\r\n <!-- dx draws Reset with the chooser's own magnifier. -->\r\n <div class=\"nxe-menu-item nxe-fr-reset\" (click)=\"resetFilterRow()\">\r\n <span class=\"nxe-icon nxe-icon-filter-operation-default\"></span>\r\n <span class=\"nxe-menu-item-text\">Reset</span>\r\n </div>\r\n </div>\r\n</ng-template>\r\n\r\n", styles: ["@charset \"UTF-8\";.nxe-data-grid{display:block}.nxe-data-grid .nxe-datagrid{height:100%;position:relative;white-space:normal}.nxe-data-grid .nxe-gridbase-container{display:flex;flex-direction:column;height:100%}.nxe-data-grid .nxe-datagrid-rowsview{position:relative;overflow:auto;flex:1 1 auto;min-height:0;background-color:#fff}.nxe-data-grid .nxe-datagrid-content{display:block;min-height:100%}.nxe-data-grid .nxe-datagrid-rowsview.nxe-empty{display:flex;flex-direction:column;height:auto}.nxe-data-grid .nxe-datagrid-rowsview.nxe-empty>.nxe-datagrid-content{flex:none;min-height:0}.nxe-data-grid .nxe-datagrid-rowsview.nxe-empty>.nxe-datagrid-empty-body{position:sticky;inset-inline-start:0;flex:1 1 var(--nxe-ui-grid-empty-height, 100px);min-height:0}.nxe-data-grid table.nxe-datagrid-table{display:table;width:100%;table-layout:fixed;border-collapse:collapse;margin:0;overflow-x:clip}.nxe-data-grid .nxe-datagrid-content .nxe-datagrid-table{max-width:none}.nxe-data-grid .nxe-datagrid-headers{display:table-header-group;position:sticky;top:0;z-index:1;color:#757575;font-weight:400;background:#fff;border-bottom:0}.nxe-data-grid tbody{display:table-row-group}.nxe-data-grid .nxe-row{display:table-row}.nxe-data-grid .nxe-datagrid-rowsview tbody>.nxe-row{border-top:var(--nxe-ui-grid-line-width, 1px) solid transparent;border-bottom:var(--nxe-ui-grid-line-width, 1px) solid transparent}.nxe-data-grid .nxe-datagrid-headers>.nxe-row{border-top:0;border-bottom:0}.nxe-data-grid .nxe-datagrid-headers:before{content:none}.nxe-data-grid .nxe-datagrid-headers>.nxe-row>td{border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-header-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview.nxe-datagrid-after-headers{border-top:0}.nxe-data-grid .nxe-datagrid .nxe-row-lines>td{border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-row-lines:first-child,.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-row-lines:first-child>td{border-top:none}.nxe-data-grid .nxe-datagrid .nxe-column-lines>td{border-left:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-column-line-color, #ddd);border-right:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-column-line-color, #ddd)}.nxe-data-grid .nxe-datagrid .nxe-column-lines>td:first-child{border-left:none}.nxe-data-grid .nxe-datagrid .nxe-column-lines>td:last-child{border-right:none}.nxe-data-grid .nxe-datagrid-content .nxe-datagrid-table .nxe-row>td:first-child{border-left:0}.nxe-data-grid .nxe-datagrid-content .nxe-datagrid-table .nxe-row>td.nxe-datagrid-group-space{border-right:none}.nxe-data-grid .nxe-datagrid-content .nxe-datagrid-table .nxe-row>td.nxe-datagrid-group-space+td{border-left:none}.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-group-row>td{border-top:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd);border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-group-row:first-child,.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-group-row:first-child>td{border-top:none}.nxe-data-grid .nxe-datagrid-rowsview .nxe-group-row.nxe-row>td{border-left-color:transparent;border-right-color:transparent}.nxe-data-grid .nxe-row.nxe-datagrid-group-footer>td{border-top:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd);background-color:#fff;border-left-width:0;border-right-width:0}.nxe-data-grid .nxe-row.nxe-datagrid-group-footer.nxe-column-lines{border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-edit-row>td{border-top:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd);border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-edit-row:first-child>td{border-top-width:0;border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused):not(.nxe-row-lines)>td,.nxe-data-grid .nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused):hover:not(.nxe-row-lines)>td{border-top:var(--nxe-ui-grid-line-width, 1px) solid #e6e6e6;border-bottom:var(--nxe-ui-grid-line-width, 1px) solid #e6e6e6}.nxe-data-grid .nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused).nxe-column-lines>td,.nxe-data-grid .nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused):hover.nxe-column-lines>td{border-left-color:var(--nxe-ui-grid-column-line-color, #ddd);border-right-color:var(--nxe-ui-grid-column-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused).nxe-row-lines>td,.nxe-data-grid .nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused):hover.nxe-row-lines>td{border-bottom-color:var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-data-grid .nxe-datagrid .nxe-row-alt>td{background-color:var(--nxe-ui-grid-alt-row-bg, #f5f5f5)}.nxe-data-grid .nxe-datagrid .nxe-row-alt.nxe-row:not(.nxe-row-removed){border-bottom-color:transparent}.nxe-data-grid .nxe-datagrid-borders>.nxe-gridbase-container>.nxe-datagrid-rowsview{border:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-border-color, #ddd)}.nxe-data-grid .nxe-datagrid-borders>.nxe-gridbase-container>.nxe-datagrid-header-panel{border-bottom:0}.nxe-data-grid .nxe-datagrid-borders>.nxe-gridbase-container>.nxe-grid-pager{margin-top:-1px}.nxe-data-grid .nxe-datagrid-borders>.nxe-gridbase-container>.nxe-datagrid-rowsview.nxe-last-row-border>.nxe-datagrid-content>.nxe-datagrid-table>tbody>.nxe-data-row:nth-last-child(2),.nxe-data-grid .nxe-datagrid-borders>.nxe-gridbase-container>.nxe-datagrid-rowsview.nxe-last-row-border>.nxe-datagrid-content>.nxe-datagrid-table>tbody>.nxe-data-row:nth-last-child(2)>td{border-bottom-width:0}.nxe-data-grid .nxe-datagrid-nodata{position:absolute;top:50%;left:50%;width:100%;text-align:center;transform:translate(-50%,-50%);color:#333;font-size:var(--nxe-ui-grid-nodata-font-size, 17px);opacity:.7;pointer-events:none}.nxe-data-grid .nxe-datagrid-loadpanel{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#ffffff80;z-index:2}.nxe-data-grid .nxe-grid-spinner{width:32px;height:32px;border:3px solid rgba(48,55,76,.2);border-top-color:#30374c;border-radius:50%;animation:nxe-grid-spin .8s linear infinite}.nxe-data-grid .nxe-header-row>td{position:relative;cursor:default;-webkit-user-select:none;user-select:none;white-space:nowrap;overflow:clip;overflow-clip-margin:4px}.nxe-data-grid .nxe-header-row>td.nxe-datagrid-action{cursor:pointer}.nxe-data-grid .nxe-header-row>td>.nxe-datagrid-text-content{display:inline-block;vertical-align:top;white-space:normal;overflow:hidden;text-overflow:ellipsis}.nxe-data-grid .nxe-datagrid-nowrap .nxe-header-row>td>.nxe-datagrid-text-content{white-space:nowrap}.nxe-data-grid .nxe-header-row>td>.nxe-datagrid-text-content:not(.nxe-sort-indicator):not(.nxe-header-filter-indicator){max-width:100%}.nxe-data-grid .nxe-header-row .nxe-sort-indicator,.nxe-data-grid .nxe-header-row .nxe-header-filter-indicator{max-width:calc(100% - 17px)}.nxe-data-grid .nxe-header-row .nxe-sort-indicator.nxe-text-content-alignment-left,.nxe-data-grid .nxe-header-row .nxe-header-filter-indicator.nxe-text-content-alignment-left{margin-right:3px}.nxe-data-grid .nxe-header-row .nxe-sort-indicator.nxe-text-content-alignment-right,.nxe-data-grid .nxe-header-row .nxe-header-filter-indicator.nxe-text-content-alignment-right{margin-left:3px}.nxe-data-grid .nxe-header-row .nxe-sort-indicator.nxe-text-content-alignment-left.nxe-text-content-alignment-right,.nxe-data-grid .nxe-header-row .nxe-header-filter-indicator.nxe-text-content-alignment-left.nxe-text-content-alignment-right{max-width:calc(100% - 34px)}.nxe-data-grid .nxe-header-row .nxe-sort-indicator.nxe-header-filter-indicator{max-width:calc(100% - 31px)}.nxe-data-grid .nxe-header-row .nxe-sort-indicator.nxe-header-filter-indicator.nxe-text-content-alignment-left.nxe-text-content-alignment-right{max-width:calc(100% - 62px)}.nxe-data-grid .nxe-column-indicators{display:inline-block;vertical-align:top;white-space:nowrap}.nxe-data-grid .nxe-column-indicators.nxe-visibility-hidden{visibility:hidden}.nxe-data-grid .nxe-column-indicator-spacer{display:inline-block;width:14px;height:14px;vertical-align:top}.nxe-data-grid .nxe-datagrid-table-fixed .nxe-column-indicators .nxe-sort.nxe-sort-none,.nxe-data-grid .nxe-datagrid-table-fixed .nxe-column-indicators .nxe-column-indicator-spacer-none{display:none}.nxe-data-grid .nxe-datagrid-best-fit .nxe-column-indicators{float:none!important}.nxe-data-grid .nxe-datagrid-best-fit .nxe-column-indicators>span{width:14px}.nxe-data-grid .nxe-datagrid-best-fit .nxe-column-indicators .nxe-sort.nxe-sort-none,.nxe-data-grid .nxe-datagrid-best-fit .nxe-column-indicators .nxe-column-indicator-spacer-none{display:inline-block!important}.nxe-data-grid .nxe-datagrid-best-fit .nxe-header-row .nxe-datagrid-text-content{max-width:none!important}.nxe-data-grid .nxe-datagrid-best-fit .nxe-text-content-alignment-left{margin-right:3px}.nxe-data-grid .nxe-datagrid-best-fit .nxe-text-content-alignment-right{margin-left:3px}.nxe-data-grid .nxe-sort{display:inline-block;position:relative;width:14px;height:14px;vertical-align:top}.nxe-data-grid .nxe-sort:after{content:\"\";position:absolute;left:0;top:0;width:14px;height:14px;background-color:currentColor}@media(forced-colors:active){.nxe-data-grid .nxe-sort:after{forced-color-adjust:none;background-color:CanvasText}}.nxe-data-grid .nxe-sort.nxe-sort-up:after{clip-path:polygon(6.5px 0,9px 3px,7px 3px,7px 14px,6px 14px,6px 3px,4px 3px)}.nxe-data-grid .nxe-sort.nxe-sort-down:after{clip-path:polygon(6px 0,7px 0,7px 11px,9px 11px,6.5px 14px,4px 11px,6px 11px)}.nxe-data-grid .nxe-sort.nxe-sort-none:after{content:none}.nxe-data-grid .nxe-datagrid-group-panel .nxe-sort{color:#757575}.nxe-data-grid .nxe-datagrid-group-panel .nxe-group-panel-item .nxe-sort{margin-left:6px}.nxe-data-grid .nxe-grid-hf-icon{display:inline-block;position:relative;width:14px;height:14px;cursor:pointer;vertical-align:top;opacity:.5}.nxe-data-grid .nxe-grid-hf-icon:before{content:\"\";position:absolute;inset:0;background-color:#757575;clip-path:polygon(0 0,14px 0,14px 3px,9px 8px,9px 14px,5px 14px,5px 8px,0 3px)}@media(forced-colors:active){.nxe-data-grid .nxe-grid-hf-icon:before{forced-color-adjust:none;background-color:CanvasText}}.nxe-data-grid .nxe-grid-hf-icon:after{content:\"\";position:absolute;inset:0;margin:-7px}.nxe-data-grid .nxe-grid-hf-icon.nxe-hf-active{opacity:1}.nxe-data-grid .nxe-grid-pager{display:block;flex:none;overflow:hidden;width:100%;padding:9px 0;border-top:1px solid var(--nxe-ui-grid-border-color, #ddd);color:#333;line-height:normal;-webkit-user-select:none;user-select:none}.nxe-data-grid .nxe-grid-pager .nxe-page-sizes{float:left}.nxe-data-grid .nxe-grid-pager .nxe-pages{float:right;white-space:nowrap}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-info{display:inline-block;margin-right:9px;opacity:.7}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-page-indexes{display:inline-block}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-navigate-button,.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-page,.nxe-data-grid .nxe-grid-pager .nxe-page-sizes .nxe-page-size{appearance:none;margin:0;background:transparent;color:inherit;font:inherit;text-align:center;outline:0;cursor:pointer}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-navigate-button:focus-visible,.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-page:focus-visible,.nxe-data-grid .nxe-grid-pager .nxe-page-sizes .nxe-page-size:focus-visible{outline:2px solid #5c95c5;outline-offset:-2px}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-page{display:inline-block;margin:0 1px 0 5px;padding:7px 9px 8px;border:1px solid transparent;border-radius:3px}.nxe-data-grid .nxe-grid-pager .nxe-page-sizes .nxe-page-size{display:inline-block;margin:0 1px 0 4px;padding:7px 9px 8px 10px;border:1px solid transparent;border-radius:3px}.nxe-data-grid .nxe-grid-pager .nxe-page-sizes .nxe-page-size.nxe-first-child,.nxe-data-grid .nxe-grid-pager .nxe-page-sizes .nxe-page-size:first-child{margin-left:1px}.nxe-data-grid .nxe-grid-pager .nxe-page-sizes .nxe-selection,.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-selection{cursor:inherit;text-shadow:none;color:#333;border-color:transparent;background-color:#d4d4d4}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-separator{display:inline-block;padding:0 8px}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-navigate-button{position:relative;display:inline-block;box-sizing:content-box;width:9px;height:17px;padding:9px 13px;border:0;vertical-align:top}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-navigate-button:before{content:\"\";position:absolute;display:block;box-sizing:border-box;top:50%;left:50%;width:21px;height:21px;margin:-10.5px 0 0 -10.5px;background-color:currentColor}@media(forced-colors:active){.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-navigate-button:before{forced-color-adjust:none;background-color:CanvasText}}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-navigate-button.nxe-button-disable{opacity:.3;cursor:inherit}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-prev-button:before{clip-path:polygon(5.75px 10.25px,13.75px 2.25px,15.25px 3.75px,8.75px 10.25px,15.25px 16.75px,13.75px 18.25px)}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-next-button:before{clip-path:polygon(15.25px 10.25px,7.25px 2.25px,5.75px 3.75px,12.25px 10.25px,5.75px 16.75px,7.25px 18.25px)}.nxe-data-grid .nxe-grid-pager .nxe-pager-size{padding:2px 6px;border:1px solid #ddd;border-radius:3px;font:inherit}.nxe-data-grid .nxe-datagrid-header-panel{padding:.5rem .75rem}.nxe-data-grid .nxe-datagrid-group-panel{align-items:center;min-height:36px}.nxe-data-grid .nxe-tree-icons{display:inline-block;float:left;white-space:nowrap;vertical-align:top;line-height:1}.nxe-data-grid .nxe-tree-expander,.nxe-data-grid .nxe-tree-expander-stub{display:inline-block;position:relative;width:14px;height:14px;vertical-align:top}.nxe-data-grid .nxe-tree-expander{height:18px;margin-right:4px;color:#959595;cursor:pointer}.nxe-data-grid .nxe-tree-expander:before{content:\"\";position:absolute;left:0;top:8px;width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid currentColor}.nxe-data-grid .nxe-tree-expander.nxe-collapsed:before{left:1px;top:6px;border-top:4px solid transparent;border-right:0;border-bottom:4px solid transparent;border-left:4px solid currentColor}.nxe-data-grid .nxe-group-row{font-weight:700;color:var(--nxe-ui-grid-group-row-color, #222)}.nxe-data-grid .nxe-group-row>td{background-color:#f7f7f7}.nxe-data-grid .nxe-group-row>td.nxe-group-cell{font-weight:700}.nxe-data-grid .nxe-group-row>td.nxe-datagrid-summary-item,.nxe-data-grid .nxe-group-footer-row>td.nxe-datagrid-summary-item{font-weight:700;color:var(--nxe-ui-grid-summary-color, rgba(51, 51, 51, .7))}.nxe-data-grid .nxe-datagrid-group-opened,.nxe-data-grid .nxe-datagrid-group-closed{display:block;position:relative;width:100%;height:18px;padding:0;color:#959595;cursor:pointer}.nxe-data-grid .nxe-datagrid-group-opened:before,.nxe-data-grid .nxe-datagrid-group-closed:before{content:\"\"!important;position:absolute;left:50%;right:auto;width:0;height:0}.nxe-data-grid .nxe-datagrid-group-opened:before{top:7px;margin-left:-4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid currentColor}.nxe-data-grid .nxe-datagrid-group-closed:before{top:5px;margin-left:-2px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid currentColor}.nxe-data-grid .nxe-datagrid-total-footer>tr>td,.nxe-data-grid tfoot.nxe-datagrid-total-footer td{border-top:1px solid #ddd;padding:6px 4px;font-weight:700;color:#959595}.nxe-data-grid .nxe-datagrid-filter-row>td{padding:2px 4px}.nxe-data-grid .nxe-grid-filter-input{width:100%;box-sizing:border-box;padding:4px 6px;border:1px solid #ddd;border-radius:2px;font:inherit;color:#333;outline:none}.nxe-data-grid .nxe-grid-filter-input:focus{border-color:#30374c}.nxe-data-grid .nxe-datagrid-filter-row select.nxe-grid-filter-input{appearance:none;background:#fff url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 4'%3E%3Cpath d='M0 0h8L4 4z' fill='%23333'/%3E%3C/svg%3E\") no-repeat right 12px center/8px 4px;padding-right:34px!important}.nxe-data-grid .nxe-command-select{width:40px;min-width:40px;max-width:40px;text-align:center;vertical-align:middle}.nxe-data-grid .nxe-grid-checkbox{display:block;width:16px;margin:0 auto;padding-bottom:2px;line-height:0;cursor:pointer}.nxe-data-grid .nxe-grid-checkbox.nxe-grid-checkbox-left{margin-left:0}.nxe-data-grid .nxe-grid-checkbox.nxe-grid-checkbox-right{margin-right:0}.nxe-data-grid .nxe-grid-checkbox:focus{outline:none}.nxe-data-grid .nxe-grid-checkbox:focus .nxe-grid-checkbox-box{border-color:var(--nxe-ui-accent, #337ab7)}.nxe-data-grid .nxe-grid-checkbox .nxe-grid-checkbox-box{display:block;position:relative;width:16px;height:16px;box-sizing:border-box;border:1px solid #ddd;border-radius:2px;background:#fff}.nxe-data-grid .nxe-grid-checkbox:not(.nxe-state-readonly):hover .nxe-grid-checkbox-box{border-color:var(--nxe-ui-checkbox-hover-border, #265a87)}.nxe-data-grid .nxe-grid-checkbox.nxe-checked .nxe-grid-checkbox-box:after{content:\"\";position:absolute;inset:-1px;background-color:var(--nxe-ui-accent, #337ab7);clip-path:polygon(6.75px 12px,2.5px 7.75px,3.5px 6.75px,6.75px 10px,12.75px 4px,13.75px 5px)}@media(forced-colors:active){.nxe-data-grid .nxe-grid-checkbox.nxe-checked .nxe-grid-checkbox-box:after{forced-color-adjust:none;background-color:CanvasText}}.nxe-data-grid .nxe-grid-checkbox.nxe-indeterminate .nxe-grid-checkbox-box:after{content:\"\";position:absolute;left:4px;top:4px;width:6px;height:6px;background-color:var(--nxe-ui-accent, #337ab7)}@media(forced-colors:active){.nxe-data-grid .nxe-grid-checkbox.nxe-indeterminate .nxe-grid-checkbox-box:after{forced-color-adjust:none;background-color:CanvasText}}.nxe-data-grid .nxe-grid-checkbox.nxe-state-readonly{cursor:default}.nxe-data-grid .nxe-grid-checkbox.nxe-state-readonly .nxe-grid-checkbox-box{border-color:#f4f4f4;background-color:#fff}@keyframes nxe-grid-spin{to{transform:rotate(360deg)}}.nxe-datagrid-rowsview .nxe-datagrid-table .nxe-row.nxe-freespace-row{border-top:0;border-bottom:0}.nxe-datagrid-rowsview .nxe-datagrid-table .nxe-row.nxe-freespace-row>td{padding-top:0;padding-bottom:0}.nxe-datagrid-nowrap{white-space:nowrap}.nxe-datagrid-rowsview :where(tbody)>.nxe-row>td{overflow:hidden;text-overflow:ellipsis}.nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused)>td,.nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused):hover>td{background-color:#e6e6e6;color:#333}.nxe-data-grid .nxe-data-row>td.nxe-editor-cell input:not([type=hidden]){text-align:inherit}.nxe-datagrid-content .nxe-datagrid-table .nxe-row .nxe-command-expand{padding:0;width:30px;min-width:30px;max-width:30px;-webkit-user-select:none;user-select:none}.nxe-datagrid-table .nxe-row .nxe-command-expand.nxe-datagrid-group-space{padding-top:var(--nxe-ui-grid-expand-offset, 7px)}.nxe-datagrid-content .nxe-datagrid-table .nxe-row>td.nxe-datagrid-group-space{vertical-align:top}.nxe-datagrid-content .nxe-datagrid-table .nxe-row .nxe-command-select{padding:0}.nxe-datagrid-rowsview .nxe-master-detail-row>.nxe-master-detail-cell{padding:20px}.nxe-datagrid-rowsview .nxe-master-detail-row .nxe-master-detail-cell:not(.nxe-row-lines){border-top:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd);border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-datagrid-rowsview .nxe-master-detail-row:not(.nxe-datagrid-edit-form) .nxe-master-detail-cell{background-color:#fafafa}.nxe-grid-cell-editor{width:100%;box-sizing:border-box;border:1px solid #337ab7;padding:2px 4px;font:inherit;background:#fff;outline:none}.nxe-data-grid .nxe-command-edit{text-align:center;white-space:nowrap}.nxe-datagrid .nxe-link{text-decoration:underline;cursor:pointer;color:#337ab7}.nxe-datagrid-content .nxe-datagrid-table .nxe-row .nxe-command-edit .nxe-link{display:inline-block}.nxe-datagrid .nxe-datagrid-content .nxe-datagrid-table .nxe-row .nxe-command-edit .nxe-link:not(.nxe-link-icon){margin:0 2px;min-width:20px}.nxe-data-row.nxe-state-hover:not(.nxe-selection):not(.nxe-row-modified):not(.nxe-row-inserted):not(.nxe-row-removed):not(.nxe-edit-row)>td:not(.nxe-focused) .nxe-link,.nxe-datagrid-rowsview .nxe-selection.nxe-row:hover>td .nxe-link,.nxe-datagrid-rowsview .nxe-selection>td .nxe-link{color:inherit}.nxe-data-grid .nxe-header-row td.nxe-datagrid-drag-action,.nxe-data-grid .nxe-group-panel-item{touch-action:none}.nxe-data-grid .nxe-drag-source{opacity:.5}.nxe-data-grid .nxe-datagrid-filter-row .nxe-menu ul{list-style:none;margin:0;padding:0}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu{display:flex;position:relative}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu>.nxe-menu{position:absolute;top:0;left:0;bottom:0;height:auto;z-index:1;cursor:pointer}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu>.nxe-menu ul,.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu>.nxe-menu li{height:100%}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu>.nxe-menu .nxe-menu-item{display:flex;align-items:center;box-sizing:border-box;height:100%;padding:1px 8px 0}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu>.nxe-menu .nxe-menu-item-content{display:flex;align-items:center;padding:0}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu .nxe-grid-filter-input{flex:1 1 0;min-width:0}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu>.nxe-grid-filter-input:not(.nxe-fr-second){padding-left:32px!important}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu .nxe-fr-second{padding-left:4px!important;border-left:1px solid #ddd}.nxe-data-grid .nxe-datagrid-filter-row .nxe-menu-item-content .nxe-icon,.nxe-fr-op-menu .nxe-menu-item .nxe-icon{display:block;flex:0 0 14px;width:14px;height:14px;margin:0;padding:0;font-size:0;line-height:0;color:#898989;background-color:currentColor;-webkit-mask:var(--nxe-ui-grid-fr-op-glyph, linear-gradient(transparent, transparent)) center/14px 14px no-repeat;mask:var(--nxe-ui-grid-fr-op-glyph, linear-gradient(transparent, transparent)) center/14px 14px no-repeat}@media(forced-colors:active){.nxe-data-grid .nxe-datagrid-filter-row .nxe-menu-item-content .nxe-icon,.nxe-fr-op-menu .nxe-menu-item .nxe-icon{forced-color-adjust:none;background-color:CanvasText}}.nxe-data-grid .nxe-datagrid-filter-row .nxe-menu-item-content .nxe-icon:not(.nxe-icon-filter-operation-default),.nxe-fr-op-menu .nxe-menu-item .nxe-icon:not(.nxe-icon-filter-operation-default){position:relative;top:-1px}.nxe-data-grid .nxe-datagrid-filter-row .nxe-menu-item-content .nxe-icon:before,.nxe-fr-op-menu .nxe-menu-item .nxe-icon:before{content:none}.nxe-icon-filter-operation-default{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.5'%3E%3Ccircle cx='5.5' cy='5.5' r='4'/%3E%3Cpath d='M8.5 8.5l4 4'/%3E%3C/g%3E%3C/svg%3E\")}.nxe-icon-filter-operation-contains{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cmask id='m'%3E%3Crect width='14' height='14' fill='%23fff'/%3E%3Cg fill='none' stroke='%23000'%3E%3Ccircle cx='7' cy='9.25' r='1.75'/%3E%3Cpath d='M5.25 3.5v8'/%3E%3C/g%3E%3C/mask%3E%3Crect x='4.5' y='2.5' width='5' height='9.5' mask='url(%23m)'/%3E%3Cg fill='none' stroke='%23000'%3E%3Ccircle cx='2.25' cy='9.25' r='1.75'/%3E%3Cpath d='M4 7v4.5'/%3E%3Cpath d='M13.4 7.9A1.75 1.75 0 1 0 13.4 10.6'/%3E%3C/g%3E%3C/svg%3E\")}.nxe-icon-filter-operation-not-contains{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cmask id='m'%3E%3Crect width='14' height='14' fill='%23fff'/%3E%3Cg fill='none' stroke='%23000'%3E%3Ccircle cx='2.25' cy='9.25' r='1.75'/%3E%3Cpath d='M4 7v4.5'/%3E%3Ccircle cx='7' cy='9.25' r='1.75'/%3E%3Cpath d='M5.25 3.5v8'/%3E%3Cpath d='M13.4 7.9A1.75 1.75 0 1 0 13.4 10.6'/%3E%3C/g%3E%3Crect y='8.75' width='14' height='1'/%3E%3C/mask%3E%3Crect x='0' y='2.5' width='14' height='9.5' mask='url(%23m)'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-starts-with{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cmask id='m'%3E%3Crect width='14' height='14' fill='%23fff'/%3E%3Cg fill='none' stroke='%23000'%3E%3Ccircle cx='2.25' cy='9.25' r='1.75'/%3E%3Cpath d='M4 7v4.5'/%3E%3Ccircle cx='7' cy='9.25' r='1.75'/%3E%3Cpath d='M5.25 3.5v8'/%3E%3C/g%3E%3C/mask%3E%3Crect x='0' y='2.5' width='9.5' height='9.5' mask='url(%23m)'/%3E%3Cg fill='none' stroke='%23000'%3E%3Cpath d='M13.4 7.9A1.75 1.75 0 1 0 13.4 10.6'/%3E%3C/g%3E%3C/svg%3E\")}.nxe-icon-filter-operation-ends-with{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cg fill='none' stroke='%23000'%3E%3Ccircle cx='2.25' cy='9.25' r='1.75'/%3E%3Cpath d='M4 7v4.5'/%3E%3C/g%3E%3Cmask id='m'%3E%3Crect width='14' height='14' fill='%23fff'/%3E%3Cg fill='none' stroke='%23000'%3E%3Ccircle cx='7' cy='9.25' r='1.75'/%3E%3Cpath d='M5.25 3.5v8'/%3E%3Cpath d='M13.4 7.9A1.75 1.75 0 1 0 13.4 10.6'/%3E%3C/g%3E%3C/mask%3E%3Crect x='4.5' y='2.5' width='9.5' height='9.5' mask='url(%23m)'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-equals{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2 5h10v1H2zM2 8h10v1H2z'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-not-equals{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2 5h10v1H2zM2 8h10v1H2zM9.3 2h1.4l-6 10H3.3z'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-less{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M10.75 3.5 3.75 7l7 3.5' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-greater{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M3.25 3.5l7 3.5-7 3.5' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-less-equal{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M10.75 2 3.75 5.5l7 3.5M3.75 8.5l7 3.5' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-greater-equal{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M3.25 2l7 3.5-7 3.5M10.25 8.5l-7 3.5' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-between{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2 5h1v5H2zM11 5h1v5h-1zM3 7h8v1H3z'/%3E%3C/svg%3E\")}.nxe-fr-op-menu{background:#fff;border:1px solid #ddd;box-shadow:0 2px 6px #00000026;padding:4px 0;min-width:180px}.nxe-fr-op-menu .nxe-menu-item{display:flex;align-items:center;gap:5px;box-sizing:border-box;height:31px;padding:0 24px 0 5px;cursor:pointer;color:#333}.nxe-fr-op-menu .nxe-menu-item:hover{background:#f0f0f0}.nxe-fr-op-menu .nxe-menu-item.nxe-menu-item-selected{background:#e6e6e6}.nxe-data-grid .nxe-col-resize-handle{position:absolute;right:-4px;top:0;bottom:0;width:8px;cursor:col-resize;z-index:2;touch-action:none}body.nxe-grid-resizing{cursor:col-resize;user-select:none;-webkit-user-select:none}.nxe-col-drop-indicator{position:fixed;width:2px;background:#337ab7;z-index:10000;pointer-events:none}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4128
5108
  }
4129
5109
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDataGridComponent, decorators: [{
4130
5110
  type: Component,
4131
- args: [{ selector: 'nxe-data-grid, nxe-tree-list', standalone: true, imports: [NgTemplateOutlet], host: { class: 'nxe-widget nxe-data-grid' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"dx-widget dx-datagrid\" [class.dx-datagrid-borders]=\"showBorders()\">\r\n <div class=\"dx-gridbase-container\">\r\n @if (groupPanelVisible()) {\r\n <div class=\"dx-datagrid-header-panel\">\r\n <div class=\"dx-datagrid-group-panel\">\r\n @if (groupColumns().length === 0) {\r\n <span class=\"dx-group-panel-message\">Drag a column header here to group by that column</span>\r\n } @else {\r\n @for (col of groupColumns(); track col.dataField(); let li = $index) {\r\n <!-- dx chip DOM: caption + sort indicator + header-filter button (bright audit) -->\r\n <div\r\n class=\"dx-group-panel-item dx-datagrid-action dx-datagrid-drag-action\"\r\n tabindex=\"0\"\r\n (pointerdown)=\"onChipPointerDown(col, li, $event)\"\r\n (click)=\"onChipClick(col)\">{{ col.caption() }}<div class=\"dx-column-indicators\" role=\"presentation\"><span\r\n class=\"dx-sort\"\r\n [class.dx-sort-up]=\"groupDir(col) === 'asc'\"\r\n [class.dx-sort-down]=\"groupDir(col) === 'desc'\"></span>@if (headerFilterVisible(col)) {<span\r\n class=\"nxe-grid-hf-icon\"\r\n [class.nxe-hf-active]=\"hasHeaderFilter(col)\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"openHeaderFilter(col, $event)\"></span>}</div></div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n <div\r\n class=\"dx-datagrid-rowsview dx-datagrid-after-headers\"\r\n [class.dx-datagrid-nowrap]=\"!wordWrapEnabled()\">\r\n <div class=\"dx-datagrid-content\">\r\n <table class=\"dx-datagrid-table dx-datagrid-table-fixed\" [style.width]=\"tableWidthStyle()\">\r\n <colgroup>\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <col style=\"width: 30px\" />\r\n }\r\n @if (masterDetailEnabled()) {\r\n <col style=\"width: 30px\" />\r\n }\r\n @if (showCheckboxes()) {\r\n <col style=\"width: 40px\" />\r\n }\r\n @for (col of visibleColumns(); track $index; let ci = $index) {\r\n <col [style.width]=\"effectiveColWidth(col, ci)\" />\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <col style=\"width: 100px\" />\r\n }\r\n </colgroup>\r\n <thead class=\"dx-datagrid-headers\">\r\n <!-- Bands add header rows: a band cell spans its visible leaves with\r\n colspan, and a leaf above the bottom row spans down with rowspan.\r\n That is dx's own banded markup, and it keeps the LAST header row\r\n all-leaves - which the auto-width, resize and drag code relies on. -->\r\n @for (hrow of headerRows(); track $index; let hi = $index) {\r\n <tr class=\"dx-row dx-header-row\" [class.dx-column-lines]=\"showColumnLines()\">\r\n @if (hi === 0) {\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td\r\n class=\"dx-command-expand dx-datagrid-group-space dx-cell-focus-disabled\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td\r\n class=\"dx-command-expand\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td\r\n class=\"dx-command-select\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\">\r\n @if (selectionMode() === 'multiple') {\r\n <span\r\n class=\"dx-select-checkbox nxe-grid-checkbox\"\r\n [class.nxe-checked]=\"allSelected()\"\r\n [class.nxe-indeterminate]=\"someSelected() && !allSelected()\"\r\n (click)=\"toggleSelectAll($event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n }\r\n </td>\r\n }\r\n }\r\n @for (cell of hrow; track $index) {\r\n @if (cell.isBand) {\r\n <td\r\n class=\"nxe-band-cell\"\r\n [class]=\"cell.column.cssClass()\"\r\n [attr.colspan]=\"cell.colspan > 1 ? cell.colspan : null\"\r\n [style.text-align]=\"cell.column.alignment() ?? 'center'\">\r\n <div class=\"dx-datagrid-text-content\">{{ cell.column.caption() }}</div>\r\n </td>\r\n } @else {\r\n <td\r\n [class]=\"cell.column.cssClass()\"\r\n [attr.aria-colindex]=\"leadCellCount() + cell.leafIndex + 1\"\r\n [attr.rowspan]=\"cell.rowspan > 1 ? cell.rowspan : null\"\r\n [style.text-align]=\"align(cell.column)\"\r\n [class.dx-cell-focus-disabled]=\"!cell.column.allowSorting()\"\r\n [class.dx-datagrid-drag-action]=\"headerDraggable(cell.column)\"\r\n (pointerdown)=\"onHeaderPointerDown(cell.column, cell.leafIndex, $event)\"\r\n (click)=\"onHeaderClick(cell.column)\">\r\n <!-- Both indicator classes are load-bearing, not decorative: they are what\r\n gate dx's caption max-widths (`.dx-header-row .dx-sort-indicator\r\n {max-width:calc(100% - 17px)}` and the .dx-header-filter-indicator\r\n family), i.e. the 17px-per-indicator budget that stops a long\r\n caption from colliding with the arrow and the funnel. -->\r\n <div\r\n class=\"dx-datagrid-text-content\"\r\n [class.dx-sort-indicator]=\"!!sortDir(cell.column)\"\r\n [class.dx-header-filter-indicator]=\"headerFilterVisible(cell.column)\">\r\n {{ cell.column.caption() }}\r\n </div>\r\n <!-- dx wraps the arrow and the funnel in one indicator block, and the\r\n group-panel chip below already did. Matching it here means the two\r\n places share a single set of metrics instead of drifting. -->\r\n @if (sortDir(cell.column) || headerFilterVisible(cell.column)) {\r\n <div class=\"dx-column-indicators\" role=\"presentation\">\r\n @if (sortDir(cell.column); as dir) {\r\n <span\r\n class=\"dx-sort\"\r\n [class.dx-sort-up]=\"dir === 'asc'\"\r\n [class.dx-sort-down]=\"dir === 'desc'\"></span>\r\n }\r\n @if (headerFilterVisible(cell.column)) {\r\n <span\r\n class=\"nxe-grid-hf-icon\"\r\n [class.nxe-hf-active]=\"hasHeaderFilter(cell.column)\"\r\n (click)=\"openHeaderFilter(cell.column, $event)\"></span>\r\n }\r\n </div>\r\n }\r\n @if (resizeHandleVisible(cell.column, cell.leafIndex)) {\r\n <span\r\n class=\"nxe-col-resize-handle\"\r\n (pointerdown)=\"startColumnResize(cell.column, cell.leafIndex, $event)\"></span>\r\n }\r\n </td>\r\n }\r\n }\r\n @if (hi === 0 && rowEditCommandVisible()) {\r\n <td\r\n class=\"dx-command-edit\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\"></td>\r\n }\r\n </tr>\r\n }\r\n @if (filterRowVisible()) {\r\n <tr class=\"dx-row dx-datagrid-filter-row\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"dx-command-expand dx-datagrid-group-space\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"dx-command-expand\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"dx-command-select\"></td>\r\n }\r\n @for (col of visibleColumns(); track $index) {\r\n <!-- dx filter cell: td.dx-editor-cell > .dx-editor-with-menu >\r\n [.dx-menu op chooser][typed editor] (dx-compat.css contract) -->\r\n <td class=\"dx-editor-cell\" [class]=\"col.cssClass()\">\r\n @if (col.allowFiltering()) {\r\n @switch (frEditorKind(col)) {\r\n @case ('lookup') {\r\n <select\r\n class=\"dx-texteditor-input nxe-grid-filter-input\"\r\n [value]=\"frText(col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"setFilterRowText(col, $any($event.target).value)\">\r\n <option value=\"\">(All)</option>\r\n @for (item of col.lookup()!.dataSource; track $index) {\r\n <option [value]=\"item[col.lookup()!.valueExpr ?? 'lovCode']\">\r\n {{ item[col.lookup()!.displayExpr ?? 'lovName'] }}\r\n </option>\r\n }\r\n </select>\r\n }\r\n @case ('boolean') {\r\n <select\r\n class=\"dx-texteditor-input nxe-grid-filter-input\"\r\n [value]=\"frText(col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"setFilterRowText(col, $any($event.target).value)\">\r\n <option value=\"\">(All)</option>\r\n <option value=\"true\">true</option>\r\n <option value=\"false\">false</option>\r\n </select>\r\n }\r\n @default {\r\n <div class=\"dx-editor-with-menu\">\r\n <div class=\"dx-menu dx-menu-base\">\r\n <ul class=\"dx-menu-horizontal\">\r\n <li>\r\n <div\r\n class=\"dx-menu-item\"\r\n title=\"Filter operation\"\r\n (click)=\"openFilterOpMenu(col, $event)\">\r\n <div class=\"dx-menu-item-content\">\r\n <span class=\"dx-icon\" [class]=\"frOpIconClass(col)\"></span>\r\n </div>\r\n </div>\r\n </li>\r\n </ul>\r\n </div>\r\n <input\r\n class=\"dx-texteditor-input nxe-grid-filter-input\"\r\n [type]=\"frEditorKind(col) === 'date' ? 'date' : 'text'\"\r\n [attr.inputmode]=\"frEditorKind(col) === 'number' ? 'decimal' : null\"\r\n [value]=\"frText(col)\"\r\n (input)=\"setFilterRowText(col, $any($event.target).value)\"\r\n (click)=\"$event.stopPropagation()\" />\r\n @if (frOp(col) === 'between') {\r\n <input\r\n class=\"dx-texteditor-input nxe-grid-filter-input nxe-fr-second\"\r\n [type]=\"frEditorKind(col) === 'date' ? 'date' : 'text'\"\r\n [attr.inputmode]=\"frEditorKind(col) === 'number' ? 'decimal' : null\"\r\n [value]=\"frText2(col)\"\r\n (input)=\"setFilterRowText(col, $any($event.target).value, true)\"\r\n (click)=\"$event.stopPropagation()\" />\r\n }\r\n </div>\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"dx-command-edit\"></td>\r\n }\r\n </tr>\r\n }\r\n </thead>\r\n <tbody>\r\n @for (item of renderRows(); track $index; let ri = $index) {\r\n @if (item.kind === 'group') {\r\n <!-- dx group-row anatomy (bright audit): [indent cells][expand chevron\r\n cell][dx-group-cell colspan][alignByColumn summary cells] -->\r\n <tr\r\n class=\"dx-row dx-group-row\"\r\n [class.dx-column-lines]=\"showColumnLines()\"\r\n [attr.aria-roledescription]=\"item.collapsed ? 'Collapsed row' : 'Expanded row'\">\r\n @for (i of indents(item.level); track i) {\r\n <td class=\"dx-command-expand dx-datagrid-group-space\"></td>\r\n }\r\n <td\r\n class=\"dx-command-expand dx-datagrid-group-space dx-datagrid-expand dx-selection-disabled\"\r\n [attr.aria-label]=\"item.collapsed ? 'Expand' : 'Collapse'\"\r\n (click)=\"toggleGroup(item.groupKey)\">\r\n <div\r\n [class.dx-datagrid-group-opened]=\"!item.collapsed\"\r\n [class.dx-datagrid-group-closed]=\"item.collapsed\"></div>\r\n </td>\r\n <td class=\"dx-group-cell\" [attr.colspan]=\"groupCellColspan(item)\">\r\n @if (templateFor(item.column.groupCellTemplate()); as gtpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"gtpl\"\r\n [ngTemplateOutletContext]=\"groupCellContext(item)\" />\r\n } @else {\r\n {{ item.column.caption() }}: {{ item.text }}{{ item.captionSuffix }}\r\n }\r\n </td>\r\n @for (col of groupRowTrailing(item); track col.dataField()) {\r\n <td class=\"dx-datagrid-summary-item\" [style.text-align]=\"align(col)\">\r\n {{ item.alignedSums.get(col.dataField()) || '' }}\r\n </td>\r\n }\r\n @if (groupRowTrailing(item).length && rowEditCommandVisible()) {\r\n <td class=\"dx-command-edit\"></td>\r\n }\r\n </tr>\r\n } @else if (item.kind === 'groupFooter') {\r\n <tr\r\n class=\"dx-row dx-group-footer-row nxe-group-footer-row\"\r\n [class]=\"'nxe-group-footer-level-' + item.level\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"dx-datagrid-group-space\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"dx-command-expand\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"dx-command-select\"></td>\r\n }\r\n @for (col of visibleColumns(); track col.dataField()) {\r\n <td class=\"dx-datagrid-summary-item\" [style.text-align]=\"align(col)\">\r\n {{ item.sums.get(col.dataField()) || '' }}\r\n </td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"dx-command-edit\"></td>\r\n }\r\n </tr>\r\n } @else {\r\n <tr\r\n class=\"dx-row dx-data-row\"\r\n [class]=\"rowClassFor(item.row)\"\r\n [attr.aria-rowindex]=\"item.rowIndex + 1\"\r\n [class.dx-row-lines]=\"showRowLines()\"\r\n [class.dx-column-lines]=\"showColumnLines()\"\r\n [class.dx-row-alt]=\"rowAlternationEnabled() && item.rowIndex % 2 === 1\"\r\n [class.dx-selection]=\"isSelected(item.row)\"\r\n [class.dx-state-hover]=\"isHovered(item.rowIndex)\"\r\n [class.dx-edit-row]=\"isRowEditing(item.row)\"\r\n (mouseenter)=\"onRowEnter(item.rowIndex)\"\r\n (mouseleave)=\"onRowLeave()\"\r\n (click)=\"onDataRowClick(item.row, item.rowIndex)\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"dx-datagrid-group-space\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"dx-command-expand dx-datagrid-expand\">\r\n <span\r\n class=\"nxe-detail-expander\"\r\n [class.nxe-expanded]=\"isDetailExpanded(item.row)\"\r\n (click)=\"toggleDetail(item.row, $event)\"></span>\r\n </td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"dx-command-select\">\r\n <span\r\n class=\"dx-select-checkbox nxe-grid-checkbox\"\r\n [class.nxe-checked]=\"isSelected(item.row)\"\r\n (click)=\"onCheckboxClick(item.row, $event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n </td>\r\n }\r\n @for (col of visibleColumns(); track $index; let ci = $index) {\r\n @if (!isMergeSuppressed(ri, col)) {\r\n <td\r\n [class]=\"col.cssClass()\"\r\n [attr.aria-colindex]=\"leadCellCount() + ci + 1\"\r\n [attr.aria-roledescription]=\"\r\n editableCell(col) && (cellEditingEnabled() || rowEditingEnabled())\r\n ? 'Editable'\r\n : null\r\n \"\r\n [style.text-align]=\"align(col)\"\r\n [style.padding-left.px]=\"treeMode && ci === 0 ? (item.level || 0) * 18 + 4 : null\"\r\n [attr.rowspan]=\"mergeSpan(ri, col)\"\r\n [class.dx-editor-cell]=\"isEditingCell(item.row, col) || (isRowEditing(item.row) && editableCell(col))\"\r\n (click)=\"onCellClick(item.row, col, $event)\"\r\n (focusout)=\"onEditCellFocusOut(item.row, col, $event)\">\r\n @if (treeMode && ci === 0) {\r\n @if (item.hasChildren) {\r\n <span\r\n class=\"nxe-tree-expander\"\r\n [class.nxe-collapsed]=\"!item.expanded\"\r\n (click)=\"toggleTreeNode(item.nodeKey, $event)\"></span>\r\n } @else {\r\n <span class=\"nxe-tree-expander-stub\"></span>\r\n }\r\n }\r\n @if (isEditingCell(item.row, col) || (isRowEditing(item.row) && editableCell(col))) {\r\n @if (templateFor(col.editCellTemplate()); as etpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"etpl\"\r\n [ngTemplateOutletContext]=\"editCellContext(item.row, item.rowIndex, ci, col)\" />\r\n } @else if (isEditingCell(item.row, col)) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"cellEditor\"\r\n [ngTemplateOutletContext]=\"{ row: item.row, col: col, mode: 'cell' }\" />\r\n } @else {\r\n <ng-container\r\n [ngTemplateOutlet]=\"cellEditor\"\r\n [ngTemplateOutletContext]=\"{ row: item.row, col: col, mode: 'row' }\" />\r\n }\r\n } @else if (templateFor(col.cellTemplate()); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"cellContext(item.row, item.rowIndex, ci, col)\" />\r\n } @else if (isBooleanCell(col, item.row)) {\r\n <!-- dx parity: boolean cells render a read-only dx-style checkbox, not \"true\"/\"false\" -->\r\n <span\r\n class=\"nxe-grid-checkbox nxe-grid-checkbox-readonly\"\r\n [class.nxe-checked]=\"getRaw(item.row, col.dataField()) === true\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n } @else {\r\n {{ cellText(col, item.row) }}\r\n }\r\n </td>\r\n }\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"dx-command-edit dx-datagrid-drag-action\">\r\n @if (isRowEditing(item.row)) {\r\n <a class=\"dx-link dx-link-save\" (click)=\"saveRowEdit(item.row, $event)\">Save</a>\r\n <a class=\"dx-link dx-link-cancel\" (click)=\"cancelRowEdit($event)\">Cancel</a>\r\n } @else {\r\n <a class=\"dx-link dx-link-edit\" (click)=\"startRowEdit(item.row, $event)\">Edit</a>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n @if (masterDetailEnabled() && isDetailExpanded(item.row)) {\r\n <tr class=\"dx-row dx-master-detail-row\">\r\n <td [attr.colspan]=\"allColCount()\">\r\n @if (detailTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"detailContext(item.row, item.rowIndex)\" />\r\n }\r\n </td>\r\n </tr>\r\n }\r\n }\r\n }\r\n <tr class=\"dx-row dx-freespace-row\">\r\n <td [attr.colspan]=\"allColCount()\"></td>\r\n </tr>\r\n </tbody>\r\n @if (hasTotalFooter()) {\r\n <tfoot class=\"dx-datagrid-total-footer\">\r\n <tr class=\"dx-row\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"dx-datagrid-group-space\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"dx-command-expand\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"dx-command-select\"></td>\r\n }\r\n @for (col of visibleColumns(); track col.dataField()) {\r\n <td class=\"dx-datagrid-summary-item\" [style.text-align]=\"align(col)\">\r\n {{ totalSums().get(col.dataField()) || '' }}\r\n </td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"dx-command-edit\"></td>\r\n }\r\n </tr>\r\n </tfoot>\r\n }\r\n </table>\r\n </div>\r\n\r\n @if (displayRows().length === 0 && !loading()) {\r\n <div class=\"dx-datagrid-nodata\">{{ noDataText() }}</div>\r\n }\r\n @if (loading()) {\r\n <div class=\"dx-datagrid-loadpanel\"><span class=\"nxe-grid-spinner\"></span></div>\r\n }\r\n </div>\r\n\r\n @if (pagerVisible()) {\r\n <div class=\"dx-datagrid-pager dx-pager nxe-grid-pager\">\r\n @if (pagerCmp()?.showPageSizeSelector() && pageSizes().length > 1) {\r\n <select class=\"nxe-pager-size\" (change)=\"onPageSizeSelect($event)\">\r\n @for (s of pageSizes(); track s) {\r\n <option [value]=\"s\" [selected]=\"s === effectivePageSize()\">{{ s }}</option>\r\n }\r\n </select>\r\n }\r\n <div class=\"nxe-pager-pages\">\r\n @if (pagerCmp()?.showNavigationButtons()) {\r\n <button type=\"button\" class=\"nxe-pager-nav\" [disabled]=\"pageIndex() === 0\" (click)=\"prevPage()\">\u2039</button>\r\n }\r\n @for (p of pageNumbers(); track p) {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-pager-page\"\r\n [class.nxe-active]=\"p === pageIndex()\"\r\n (click)=\"goToPage(p)\">\r\n {{ p + 1 }}\r\n </button>\r\n }\r\n @if (pagerCmp()?.showNavigationButtons()) {\r\n <button type=\"button\" class=\"nxe-pager-nav\" [disabled]=\"pageIndex() >= pageCount() - 1\" (click)=\"nextPage()\">\u203A</button>\r\n }\r\n </div>\r\n @if (pagerCmp()?.showInfo()) {\r\n <span class=\"nxe-pager-info\">{{ pageInfo() }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n\r\n<!-- Inline cell/row editor (dxo-editing, orbit addition). `mode: 'cell'` commits on blur/Enter;\r\n `mode: 'row'` buffers on input until the command column's Save. -->\r\n<ng-template #cellEditor let-row=\"row\" let-col=\"col\" let-mode=\"mode\">\r\n @if (col.lookup(); as lookup) {\r\n <select\r\n class=\"dx-texteditor-input nxe-grid-cell-editor\"\r\n [value]=\"editorInputValue(row, col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"\r\n mode === 'cell'\r\n ? commitCellEdit(row, col, $any($event.target).value)\r\n : bufferRowEdit(col, $any($event.target).value)\r\n \">\r\n <option value=\"\"></option>\r\n @for (item of lookup.dataSource; track $index) {\r\n <option [value]=\"item[lookup.valueExpr ?? 'lovCode']\">\r\n {{ item[lookup.displayExpr ?? 'lovName'] }}\r\n </option>\r\n }\r\n </select>\r\n } @else if (col.dataType() === 'boolean') {\r\n <input\r\n type=\"checkbox\"\r\n class=\"nxe-grid-cell-editor-check\"\r\n [checked]=\"!!editorInputValue(row, col) && editorInputValue(row, col) !== 'false'\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"\r\n mode === 'cell'\r\n ? commitCellEdit(row, col, $any($event.target).checked)\r\n : bufferRowEdit(col, $any($event.target).checked)\r\n \" />\r\n } @else {\r\n <input\r\n type=\"text\"\r\n class=\"dx-texteditor-input nxe-grid-cell-editor\"\r\n [value]=\"editorInputValue(row, col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.enter)=\"mode === 'cell' && commitCellEdit(row, col, $any($event.target).value)\"\r\n (keydown.escape)=\"mode === 'cell' && cancelCellEdit()\"\r\n (blur)=\"mode === 'cell' && commitCellEdit(row, col, $any($event.target).value)\"\r\n (input)=\"mode === 'row' && bufferRowEdit(col, $any($event.target).value)\" />\r\n }\r\n</ng-template>\r\n\r\n<!-- The header-filter popup (#hfPanel) moved to nxe-header-filter-panel.component.html \u2014\r\n openHeaderFilter attaches it through openNxeHeaderFilterOverlay (pivot parity Phase 3a). -->\r\n\r\n<!-- Filter-row operation-chooser dropdown (dx filter menu) -->\r\n<ng-template #frOpMenu>\r\n <div class=\"nxe-fr-op-menu dx-datagrid\">\r\n @for (item of frMenuOps(); track item.op) {\r\n <div\r\n class=\"dx-menu-item\"\r\n [class.dx-menu-item-selected]=\"frMenuColumn() && frOp(frMenuColumn()!) === item.op\"\r\n (click)=\"selectFilterOp(item.op)\">\r\n <span class=\"dx-icon dx-icon-filter-operation-{{ opIconSlug(item.op) }}\"></span>\r\n <span class=\"dx-menu-item-text\">{{ item.label }}</span>\r\n </div>\r\n }\r\n <div class=\"dx-menu-item nxe-fr-reset\" (click)=\"resetFilterRow()\">\r\n <span class=\"dx-icon\"></span>\r\n <span class=\"dx-menu-item-text\">Reset</span>\r\n </div>\r\n </div>\r\n</ng-template>\r\n\r\n", styles: ["@charset \"UTF-8\";.nxe-data-grid{display:block}.nxe-data-grid .dx-datagrid{height:100%;position:relative}.nxe-data-grid .dx-gridbase-container{display:flex;flex-direction:column;height:100%}.nxe-data-grid .dx-datagrid-rowsview{position:relative;overflow:auto;flex:1 1 auto;min-height:0}.nxe-data-grid .dx-datagrid-content{display:block;min-height:100%}.nxe-data-grid table.dx-datagrid-table{display:table;width:100%;table-layout:fixed;border-collapse:collapse;margin:0}.nxe-data-grid .dx-datagrid-content .dx-datagrid-table{max-width:none}.nxe-data-grid .dx-datagrid-headers{display:table-header-group;position:sticky;top:0;z-index:1;background:#fff;border-bottom:0}.nxe-data-grid tbody{display:table-row-group}.nxe-data-grid .dx-row{display:table-row}.nxe-data-grid .dx-datagrid-headers .dx-header-row>td{border-bottom:1px solid #e2e8f0}.nxe-data-grid .dx-datagrid-nodata{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#333;font-size:17px;opacity:.7;pointer-events:none}.nxe-data-grid .dx-datagrid-loadpanel{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#ffffff80;z-index:2}.nxe-data-grid .nxe-grid-spinner{width:32px;height:32px;border:3px solid rgba(48,55,76,.2);border-top-color:#30374c;border-radius:50%;animation:nxe-grid-spin .8s linear infinite}.nxe-data-grid .dx-column-indicators{display:inline-block;vertical-align:top;white-space:nowrap}.nxe-data-grid .dx-sort{display:inline-block;position:relative;width:14px;height:14px;vertical-align:middle}.nxe-data-grid .dx-sort:after{content:\"\";position:absolute;left:50%;top:50%;width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent}.nxe-data-grid .dx-sort.dx-sort-up:after{border-bottom:5px solid #757575;transform:translate(-50%,-50%)}.nxe-data-grid .dx-sort.dx-sort-down:after{border-top:5px solid #757575;transform:translate(-50%,-50%)}.nxe-data-grid .dx-header-row>td{cursor:default;-webkit-user-select:none;user-select:none;height:27px}.nxe-data-grid .dx-header-row>td{position:relative}.nxe-data-grid .nxe-grid-hf-icon{display:inline-block;position:relative;width:14px;height:14px;cursor:pointer;vertical-align:middle;opacity:.5}.nxe-data-grid .nxe-grid-hf-icon:before{content:\"\";position:absolute;inset:0;background-color:#757575;clip-path:polygon(0 0,100% 0,58% 50%,58% 100%,42% 85%,42% 50%)}.nxe-data-grid .nxe-grid-hf-icon:after{content:\"\";position:absolute;inset:0;margin:-7px}.nxe-data-grid .nxe-grid-hf-icon:hover{opacity:.75}.nxe-data-grid .nxe-grid-hf-icon.nxe-hf-active{opacity:1}.nxe-data-grid .nxe-grid-pager{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:8px 12px;border-top:1px solid #ddd}.nxe-data-grid .nxe-pager-size{padding:2px 6px;border:1px solid #ddd;border-radius:3px;font:inherit}.nxe-data-grid .nxe-pager-pages{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.nxe-data-grid .nxe-pager-page,.nxe-data-grid .nxe-pager-nav{min-width:24px;padding:3px 7px 4px;border:1px solid transparent;border-radius:3px;background:transparent;color:#333;cursor:pointer;font:inherit;line-height:1}.nxe-data-grid .nxe-pager-page:hover:not(:disabled),.nxe-data-grid .nxe-pager-nav:hover:not(:disabled){background:#f4f4f4}.nxe-data-grid .nxe-pager-page:disabled,.nxe-data-grid .nxe-pager-nav:disabled{opacity:.4;cursor:default}.nxe-data-grid .nxe-pager-page.nxe-active,.nxe-data-grid .nxe-pager-nav.nxe-active{background:#d4d4d4;border-color:transparent}.nxe-data-grid .nxe-pager-info{color:#757575;margin-left:auto}.nxe-data-grid .dx-datagrid-header-panel{padding:.5rem .75rem}.nxe-data-grid .dx-datagrid-group-panel{align-items:center;min-height:36px}.nxe-data-grid .nxe-tree-expander,.nxe-data-grid .nxe-tree-expander-stub{display:inline-block;width:16px;height:16px;flex:0 0 16px;vertical-align:middle;margin-right:2px}.nxe-data-grid .nxe-tree-expander{position:relative;cursor:pointer}.nxe-data-grid .nxe-tree-expander:before{content:\"\";position:absolute;left:4px;top:4px;width:6px;height:6px;border-right:1.5px solid #555;border-bottom:1.5px solid #555;transform:rotate(45deg)}.nxe-data-grid .nxe-tree-expander.nxe-collapsed:before{transform:rotate(-45deg)}.nxe-data-grid .dx-group-row>td{padding:4px 8px;background-color:#f7f7f7}.nxe-data-grid .dx-group-row>td.dx-group-cell{font-weight:700}.nxe-data-grid .dx-group-row>td.dx-datagrid-expand{cursor:pointer}.nxe-data-grid .dx-group-row>td.dx-datagrid-summary-item{font-weight:400;color:#333}.nxe-data-grid .dx-datagrid-group-opened,.nxe-data-grid .dx-datagrid-group-closed{display:inline-block;position:relative;width:14px;height:14px;cursor:pointer}.nxe-data-grid .dx-datagrid-group-opened:before,.nxe-data-grid .dx-datagrid-group-closed:before{content:\"\"!important;position:absolute;left:3px;right:auto;top:2px;width:6px;height:6px;border-right:1.5px solid #555;border-bottom:1.5px solid #555;transform:rotate(45deg)}.nxe-data-grid .dx-datagrid-group-closed:before{transform:rotate(-45deg)}.nxe-data-grid .dx-datagrid-total-footer>tr>td,.nxe-data-grid tfoot.dx-datagrid-total-footer td{border-top:1px solid #ddd;padding:6px 4px;font-weight:700;color:#959595}.nxe-data-grid .dx-datagrid-filter-row>td{padding:2px 4px}.nxe-data-grid .nxe-grid-filter-input{width:100%;box-sizing:border-box;padding:4px 6px;border:1px solid #ddd;border-radius:2px;font:inherit;outline:none}.nxe-data-grid .nxe-grid-filter-input:focus{border-color:#30374c}.nxe-data-grid .dx-command-select{width:40px;min-width:40px;max-width:40px;text-align:center;vertical-align:middle}.nxe-data-grid .nxe-grid-checkbox{display:inline-block;cursor:pointer;line-height:0}.nxe-data-grid .nxe-grid-checkbox .nxe-grid-checkbox-box{display:inline-block;position:relative;width:16px;height:16px;box-sizing:border-box;border:1px solid #ddd;border-radius:2px;background:#fff;vertical-align:middle}.nxe-data-grid .nxe-grid-checkbox.nxe-checked .nxe-grid-checkbox-box{border-color:#ddd;background:#fff}.nxe-data-grid .nxe-grid-checkbox.nxe-checked .nxe-grid-checkbox-box:after{content:\"\";position:absolute;left:4px;top:1px;width:5px;height:9px;border:solid #337ab7;border-width:0 2px 2px 0;transform:rotate(45deg)}.nxe-data-grid .nxe-grid-checkbox.nxe-indeterminate .nxe-grid-checkbox-box{border-color:#ddd}.nxe-data-grid .nxe-grid-checkbox.nxe-indeterminate .nxe-grid-checkbox-box:after{content:\"\";position:absolute;left:3px;top:3px;width:8px;height:8px;background:#337ab7}.nxe-data-grid .nxe-grid-checkbox.nxe-grid-checkbox-readonly{display:block;text-align:center;cursor:default;height:18px;line-height:18px}@keyframes nxe-grid-spin{to{transform:rotate(360deg)}}.nxe-data-grid .dx-command-expand{width:30px;text-align:center;cursor:pointer}.nxe-detail-expander{display:inline-block;width:0;height:0;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:6px solid #999;cursor:pointer;transition:transform .15s ease}.nxe-detail-expander.nxe-expanded{transform:rotate(90deg)}.nxe-data-grid .dx-master-detail-row>td{padding:8px 12px;background:#fafafa}.nxe-grid-cell-editor{width:100%;box-sizing:border-box;border:1px solid #337ab7;padding:2px 4px;font:inherit;background:#fff;outline:none}.nxe-data-grid .dx-command-edit{text-align:center;white-space:nowrap}.nxe-data-grid .dx-command-edit .dx-link{color:#337ab7;cursor:pointer;margin:0 4px;text-decoration:none}.nxe-data-grid .dx-command-edit .dx-link:hover{text-decoration:underline}.nxe-data-grid .dx-header-row td.dx-datagrid-drag-action,.nxe-data-grid .dx-group-panel-item{touch-action:none}.nxe-data-grid .nxe-drag-source{opacity:.5}.nxe-data-grid .dx-datagrid-filter-row .dx-menu ul{list-style:none;margin:0;padding:0}.nxe-data-grid .dx-datagrid-filter-row .dx-editor-with-menu{display:flex}.nxe-data-grid .dx-datagrid-filter-row .dx-editor-with-menu .nxe-grid-filter-input{flex:1 1 0;min-width:0}.nxe-data-grid .dx-datagrid-filter-row .dx-editor-with-menu .nxe-fr-second{padding-left:4px!important;border-left:1px solid #ddd}.nxe-data-grid .dx-datagrid-filter-row .dx-icon[class*=dx-icon-filter-operation-],.nxe-fr-op-menu .dx-icon[class*=dx-icon-filter-operation-]{font:600 12px/18px inherit!important;font-family:inherit!important;display:inline-block;width:18px;height:18px;text-align:center;color:#757575}.nxe-data-grid .dx-icon-filter-operation-contains:before,.nxe-fr-op-menu .dx-icon-filter-operation-contains:before{content:\"*\"!important}.nxe-data-grid .dx-icon-filter-operation-not-contains:before,.nxe-fr-op-menu .dx-icon-filter-operation-not-contains:before{content:\"!*\"!important}.nxe-data-grid .dx-icon-filter-operation-starts-with:before,.nxe-fr-op-menu .dx-icon-filter-operation-starts-with:before{content:\"a*\"!important}.nxe-data-grid .dx-icon-filter-operation-ends-with:before,.nxe-fr-op-menu .dx-icon-filter-operation-ends-with:before{content:\"*a\"!important}.nxe-data-grid .dx-icon-filter-operation-equals:before,.nxe-fr-op-menu .dx-icon-filter-operation-equals:before{content:\"=\"!important}.nxe-data-grid .dx-icon-filter-operation-not-equals:before,.nxe-fr-op-menu .dx-icon-filter-operation-not-equals:before{content:\"\\2260\"!important}.nxe-data-grid .dx-icon-filter-operation-less:before,.nxe-fr-op-menu .dx-icon-filter-operation-less:before{content:\"<\"!important}.nxe-data-grid .dx-icon-filter-operation-greater:before,.nxe-fr-op-menu .dx-icon-filter-operation-greater:before{content:\">\"!important}.nxe-data-grid .dx-icon-filter-operation-less-equal:before,.nxe-fr-op-menu .dx-icon-filter-operation-less-equal:before{content:\"\\2264\"!important}.nxe-data-grid .dx-icon-filter-operation-greater-equal:before,.nxe-fr-op-menu .dx-icon-filter-operation-greater-equal:before{content:\"\\2265\"!important}.nxe-data-grid .dx-icon-filter-operation-between:before,.nxe-fr-op-menu .dx-icon-filter-operation-between:before{content:\"\\2194\"!important}.nxe-fr-op-menu{background:#fff;border:1px solid #ddd;box-shadow:0 2px 6px #00000026;padding:4px 0;min-width:180px}.nxe-fr-op-menu .dx-menu-item{display:flex;align-items:center;gap:8px;padding:6px 24px 6px 8px;cursor:pointer;color:#333}.nxe-fr-op-menu .dx-menu-item:hover{background:#f0f0f0}.nxe-fr-op-menu .dx-menu-item.dx-menu-item-selected{background:#e6e6e6}.nxe-fr-op-menu .dx-menu-item .dx-icon{width:18px;text-align:center}.nxe-data-grid .nxe-col-resize-handle{position:absolute;right:-4px;top:0;bottom:0;width:8px;cursor:col-resize;z-index:2;touch-action:none}body.nxe-grid-resizing{cursor:col-resize;user-select:none;-webkit-user-select:none}.nxe-col-drop-indicator{position:fixed;width:2px;background:#337ab7;z-index:10000;pointer-events:none}\n"] }]
4132
- }], ctorParameters: () => [], propDecorators: { declaredColumns: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NxeColumnComponent), { ...{ descendants: false }, isSignal: true }] }], templates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NxeTemplateDirective), { isSignal: true }] }], selection: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeSelectionComponent), { isSignal: true }] }], headerFilter: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeHeaderFilterComponent), { isSignal: true }] }], grouping: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeGroupingComponent), { isSignal: true }] }], groupPanel: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeGroupPanelComponent), { isSignal: true }] }], summaryCmp: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeSummaryComponent), { isSignal: true }] }], pagingCmp: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxePagingComponent), { isSignal: true }] }], pagerCmp: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxePagerComponent), { isSignal: true }] }], editingCmp: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeEditingComponent), { isSignal: true }] }], filterRowCmp: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeFilterRowComponent), { isSignal: true }] }], masterDetail: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeMasterDetailComponent), { isSignal: true }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }], keyExpr: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyExpr", required: false }] }], hoverStateEnabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverStateEnabled", required: false }] }], showColumnLinesInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showColumnLines", required: false }] }], showRowLinesInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRowLines", required: false }] }], showBordersInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBorders", required: false }] }], wordWrapEnabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "wordWrapEnabled", required: false }] }], columnAutoWidthInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnAutoWidth", required: false }] }], rowAlternationEnabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowAlternationEnabled", required: false }] }], remoteOperations: [{ type: i0.Input, args: [{ isSignal: true, alias: "remoteOperations", required: false }] }], syncLookupFilterValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncLookupFilterValues", required: false }] }], columnMinWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnMinWidth", required: false }] }], noDataTextInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "noDataText", required: false }] }], loadPanel: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadPanel", required: false }] }], paging: [{ type: i0.Input, args: [{ isSignal: true, alias: "paging", required: false }] }], filterRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterRow", required: false }] }], headerFilterOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerFilter", required: false }] }], groupingOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "grouping", required: false }] }], groupPanelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupPanel", required: false }] }], scrolling: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrolling", required: false }] }], sorting: [{ type: i0.Input, args: [{ isSignal: true, alias: "sorting", required: false }] }], allowColumnResizing: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowColumnResizing", required: false }] }], columnResizingMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnResizingMode", required: false }] }], allowColumnReordering: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowColumnReordering", required: false }] }], repaintChangesOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "repaintChangesOnly", required: false }] }], showColumnHeaders: [{ type: i0.Input, args: [{ isSignal: true, alias: "showColumnHeaders", required: false }] }], parentIdExpr: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentIdExpr", required: false }] }], dataStructure: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataStructure", required: false }] }], rootValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootValue", required: false }] }], autoExpandAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoExpandAll", required: false }] }], expandedRowKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedRowKeys", required: false }] }, { type: i0.Output, args: ["expandedRowKeysChange"] }], selectedRowKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedRowKeys", required: false }] }, { type: i0.Output, args: ["selectedRowKeysChange"] }], mergeColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "mergeColumns", required: false }] }], onSelectionChanged: [{ type: i0.Output, args: ["onSelectionChanged"] }], onRowClick: [{ type: i0.Output, args: ["onRowClick"] }], onContentReady: [{ type: i0.Output, args: ["onContentReady"] }], onOptionChanged: [{ type: i0.Output, args: ["onOptionChanged"] }], rowClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClass", required: false }] }], onCellPrepared: [{
5111
+ args: [{ selector: 'nxe-data-grid, nxe-tree-list', standalone: true, imports: [NgTemplateOutlet], host: { class: 'nxe-widget nxe-data-grid' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div\r\n class=\"nxe-widget nxe-datagrid\"\r\n [class.nxe-datagrid-borders]=\"showBorders()\"\r\n [style.height]=\"heightStyle()\">\r\n <div class=\"nxe-gridbase-container\">\r\n @if (groupPanelVisible()) {\r\n <div class=\"nxe-datagrid-header-panel\">\r\n <div class=\"nxe-datagrid-group-panel\">\r\n @if (groupColumns().length === 0) {\r\n <span class=\"nxe-group-panel-message\">Drag a column header here to group by that column</span>\r\n } @else {\r\n @for (col of groupColumns(); track col.dataField(); let li = $index) {\r\n <!-- dx chip DOM: caption + sort indicator + header-filter button (bright audit) -->\r\n <div\r\n class=\"nxe-group-panel-item nxe-datagrid-action nxe-datagrid-drag-action\"\r\n tabindex=\"0\"\r\n (pointerdown)=\"onChipPointerDown(col, li, $event)\"\r\n (click)=\"onChipClick(col)\">{{ col.caption() }}<div class=\"nxe-column-indicators\" role=\"presentation\"><span\r\n class=\"nxe-sort\"\r\n [class.nxe-sort-up]=\"groupDir(col) === 'asc'\"\r\n [class.nxe-sort-down]=\"groupDir(col) === 'desc'\"></span>@if (headerFilterVisible(col)) {<span\r\n class=\"nxe-grid-hf-icon\"\r\n [class.nxe-hf-active]=\"hasHeaderFilter(col)\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"openHeaderFilter(col, $event)\"></span>}</div></div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n <!-- dx drops the last row's bottom line in a bordered grid when no free-space row follows\r\n it, so the rows view's own border is the only line there (m_rows_view.js:931-937). -->\r\n <div\r\n class=\"nxe-datagrid-rowsview nxe-datagrid-after-headers\"\r\n [class.nxe-datagrid-nowrap]=\"!wordWrapEnabled()\"\r\n [class.nxe-empty]=\"isEmpty()\"\r\n [class.nxe-last-row-border]=\"showBorders() && !freeSpaceVisible()\">\r\n <div class=\"nxe-datagrid-content\">\r\n <!-- role=grid (treegrid when hierarchical) rather than the implicit table role:\r\n this is an interactive widget with a single tab stop and arrow-key movement,\r\n not a static table.\r\n\r\n aria-rowcount counts the PAGE, because aria-rowindex is page-relative and the\r\n two have to agree. Making the index absolute would be more literally correct\r\n for a paged grid, but consuming code addresses cells through it\r\n (`tr[aria-rowindex=N] > td[aria-colindex=M]`), so the index is a contract. -->\r\n <table\r\n class=\"nxe-datagrid-table nxe-datagrid-table-fixed\"\r\n [attr.role]=\"treeMode ? 'treegrid' : 'grid'\"\r\n [attr.aria-rowcount]=\"navigableRows().length\"\r\n [attr.aria-colcount]=\"visibleColumns().length + leadCellCount() + (rowEditCommandVisible() ? 1 : 0)\"\r\n [style.width]=\"tableWidthStyle()\"\r\n (keydown)=\"onGridKeydown($event)\">\r\n <colgroup>\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <col style=\"width: 30px\" />\r\n }\r\n @if (masterDetailEnabled()) {\r\n <col style=\"width: 30px\" />\r\n }\r\n @if (showCheckboxes()) {\r\n <col style=\"width: 40px\" />\r\n }\r\n @for (col of visibleColumns(); track $index; let ci = $index) {\r\n <col [style.width]=\"effectiveColWidth(col, ci)\" />\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <col style=\"width: 100px\" />\r\n }\r\n </colgroup>\r\n <thead class=\"nxe-datagrid-headers\">\r\n <!-- Bands add header rows: a band cell spans its visible leaves with\r\n colspan, and a leaf above the bottom row spans down with rowspan.\r\n That is dx's own banded markup, and it keeps the LAST header row\r\n all-leaves - which the auto-width, resize and drag code relies on. -->\r\n @for (hrow of headerRows(); track $index; let hi = $index) {\r\n <tr\r\n class=\"nxe-row nxe-header-row\"\r\n role=\"row\"\r\n [class.nxe-column-lines]=\"showColumnLines()\">\r\n @if (hi === 0) {\r\n <!-- dx numbers the header's command cells like the body's\r\n (m_columns_view.js:681-693); they are not role=columnheader\r\n (m_column_headers.js:193), which keeps them out of the leaf\r\n header list (nxeHeaderLeafCells). -->\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td\r\n class=\"nxe-command-expand nxe-datagrid-group-space nxe-cell-focus-disabled\"\r\n [attr.aria-colindex]=\"i + 1\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <!-- dx gives every cell of an 'expand' command column the\r\n group-space class (m_columns_view.js:290-293), which drops\r\n the column line between it and the next cell. -->\r\n <td\r\n class=\"nxe-command-expand nxe-datagrid-group-space\"\r\n [attr.aria-colindex]=\"expandColIndex()\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td\r\n class=\"nxe-command-select\"\r\n [attr.aria-colindex]=\"selectColIndex()\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\">\r\n @if (selectionMode() === 'multiple') {\r\n <span\r\n class=\"nxe-select-checkbox nxe-grid-checkbox nxe-checkbox nxe-datagrid-checkbox-size\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"allSelected() ? 'true' : someSelected() ? 'mixed' : 'false'\"\r\n [class.nxe-checked]=\"allSelected()\"\r\n [class.nxe-indeterminate]=\"someSelected() && !allSelected()\"\r\n (click)=\"toggleSelectAll($event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n }\r\n </td>\r\n }\r\n }\r\n @for (cell of hrow; track $index) {\r\n @if (cell.isBand) {\r\n <!-- dx gives a band the alignment of any column: left unless set\r\n (a band has no dataType to right-align or centre it by). -->\r\n <td\r\n class=\"nxe-band-cell\"\r\n role=\"columnheader\"\r\n [class]=\"cell.column.cssClass()\"\r\n [attr.colspan]=\"cell.colspan > 1 ? cell.colspan : null\"\r\n [style.text-align]=\"align(cell.column)\">\r\n <div class=\"nxe-datagrid-text-content\">{{ cell.column.caption() }}</div>\r\n </td>\r\n } @else {\r\n @let hDir = sortDir(cell.column);\r\n @let hHf = headerFilterVisible(cell.column);\r\n @let hSortSlot = sortSlot(cell.column);\r\n @let hInd = hSortSlot || hHf;\r\n @let hAlign = align(cell.column);\r\n <!-- dx places the indicator block before the caption when column lines are\r\n on or the column is right-aligned (m_column_headers.js createCellContent,\r\n m_column_state_mixin.js _applyColumnState), floated to the cell edge\r\n only with column lines: right, or left for a right-aligned column. -->\r\n @let hLead = hInd && (showColumnLines() || hAlign === 'right');\r\n @let hFloat = showColumnLines() ? (hAlign === 'right' ? 'left' : 'right') : null;\r\n <td\r\n [class]=\"cell.column.cssClass()\"\r\n role=\"columnheader\"\r\n [attr.aria-colindex]=\"leadCellCount() + cell.leafIndex + 1\"\r\n [attr.aria-sort]=\"ariaSort(cell.column)\"\r\n [attr.tabindex]=\"isCellTabStop(-1, cell.leafIndex) ? 0 : -1\"\r\n (focus)=\"focusCell(-1, cell.leafIndex)\"\r\n [attr.rowspan]=\"cell.rowspan > 1 ? cell.rowspan : null\"\r\n [style.text-align]=\"hAlign\"\r\n [class.nxe-cell-focus-disabled]=\"!cell.column.allowSorting()\"\r\n [class.nxe-datagrid-action]=\"sortAllowed(cell.column)\"\r\n [class.nxe-datagrid-drag-action]=\"headerDraggable(cell.column)\"\r\n (pointerdown)=\"onHeaderPointerDown(cell.column, cell.leafIndex, $event)\"\r\n (click)=\"onHeaderClick(cell.column)\">\r\n @if (hLead) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"headerIndicators\"\r\n [ngTemplateOutletContext]=\"{ $implicit: cell.column, dir: hDir, sortSlot: hSortSlot, hf: hHf, hfFirst: hAlign === 'right', float: hFloat }\" />\r\n }\r\n <!-- dx _alignCaptionByCenter: a centred caption gets an unfloated,\r\n invisible copy of the indicator block on its other side, so the\r\n caption centres on the cell rather than caption + arrow centring\r\n as a group. Spacers only: no sort or funnel classes (specs and\r\n apps pick funnels by index) and no handlers - a click there falls\r\n through to the cell and sorts, as dx's hidden clone lets it. -->\r\n @if (hInd && hAlign === 'center') {\r\n <div\r\n class=\"nxe-column-indicators nxe-visibility-hidden\"\r\n role=\"presentation\"\r\n aria-hidden=\"true\">\r\n @if (hSortSlot) {\r\n <span\r\n class=\"nxe-column-indicator-spacer\"\r\n [class.nxe-column-indicator-spacer-none]=\"!hDir\"></span>\r\n }\r\n @if (hHf) {\r\n <span class=\"nxe-column-indicator-spacer\"></span>\r\n }\r\n </div>\r\n }\r\n <!-- The indicator classes are load-bearing, not decorative: they gate dx's\r\n caption budget (`.dx-header-row .dx-sort-indicator{max-width:calc(100%\r\n - 17px)}` and family) and, with the alignment classes, the 3px gap to\r\n the block. dx sets alignment-left for left and centre columns and\r\n alignment-right for right and centre ones, whenever the cell has an\r\n indicator slot at all - even an unsorted, hidden one. -->\r\n <div\r\n class=\"nxe-datagrid-text-content\"\r\n [class.nxe-text-content-alignment-left]=\"hInd && hAlign !== 'right'\"\r\n [class.nxe-text-content-alignment-right]=\"hInd && hAlign !== 'left'\"\r\n [class.nxe-sort-indicator]=\"hDir !== null\"\r\n [class.nxe-header-filter-indicator]=\"hHf\">\r\n {{ cell.column.caption() }}\r\n </div>\r\n @if (hInd && !hLead) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"headerIndicators\"\r\n [ngTemplateOutletContext]=\"{ $implicit: cell.column, dir: hDir, sortSlot: hSortSlot, hf: hHf, hfFirst: hAlign === 'right', float: null }\" />\r\n }\r\n @if (resizeHandleVisible(cell.column, cell.leafIndex)) {\r\n <span\r\n class=\"nxe-col-resize-handle\"\r\n (pointerdown)=\"startColumnResize(cell.column, cell.leafIndex, $event)\"></span>\r\n }\r\n </td>\r\n }\r\n }\r\n @if (hi === 0 && rowEditCommandVisible()) {\r\n <td\r\n class=\"nxe-command-edit\"\r\n [attr.aria-colindex]=\"editColIndex()\"\r\n [attr.rowspan]=\"headerDepth() > 1 ? headerDepth() : null\"></td>\r\n }\r\n </tr>\r\n }\r\n @if (filterRowVisible()) {\r\n <!-- The headers view toggles column lines on every row it renders, the\r\n filter row included (m_column_headers.js:205-207). -->\r\n <!-- Every filter-row cell is numbered, as the headers view numbers it\r\n (m_columns_view.js:681-693). -->\r\n <tr class=\"nxe-row nxe-datagrid-filter-row\" [class.nxe-column-lines]=\"showColumnLines()\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"i + 1\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"expandColIndex()\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"nxe-command-select\" [attr.aria-colindex]=\"selectColIndex()\"></td>\r\n }\r\n @for (col of visibleColumns(); track $index; let ci = $index) {\r\n <!-- dx filter cell: td.dx-editor-cell > .dx-editor-with-menu >\r\n [.dx-menu op chooser][typed editor] (dx-compat.css contract) -->\r\n <td\r\n class=\"nxe-editor-cell\"\r\n [class]=\"col.cssClass()\"\r\n [attr.aria-colindex]=\"leadCellCount() + ci + 1\">\r\n @if (col.allowFiltering()) {\r\n @switch (frEditorKind(col)) {\r\n @case ('lookup') {\r\n <select\r\n class=\"nxe-texteditor-input nxe-grid-filter-input\"\r\n [value]=\"frText(col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"setFilterRowText(col, $any($event.target).value)\">\r\n <option value=\"\">(All)</option>\r\n @for (item of col.lookup()!.dataSource; track $index) {\r\n <option [value]=\"item[col.lookup()!.valueExpr ?? 'lovCode']\">\r\n {{ item[col.lookup()!.displayExpr ?? 'lovName'] }}\r\n </option>\r\n }\r\n </select>\r\n }\r\n @case ('boolean') {\r\n <select\r\n class=\"nxe-texteditor-input nxe-grid-filter-input\"\r\n [value]=\"frText(col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"setFilterRowText(col, $any($event.target).value)\">\r\n <option value=\"\">(All)</option>\r\n <option value=\"true\">true</option>\r\n <option value=\"false\">false</option>\r\n </select>\r\n }\r\n @default {\r\n <div class=\"nxe-editor-with-menu\">\r\n <div class=\"nxe-menu nxe-menu-base\">\r\n <ul class=\"nxe-menu-horizontal\">\r\n <li>\r\n <div\r\n class=\"nxe-menu-item\"\r\n title=\"Filter operation\"\r\n (click)=\"openFilterOpMenu(col, $event)\">\r\n <div class=\"nxe-menu-item-content\">\r\n <span\r\n class=\"nxe-icon nxe-icon-filter-operation-{{ frOpIconSlug(col) }}\"></span>\r\n </div>\r\n </div>\r\n </li>\r\n </ul>\r\n </div>\r\n <!-- dx sets the FIRST input's text-align to the column\r\n alignment inline, unless centre (m_filter_row.js:390-395). -->\r\n <input\r\n class=\"nxe-texteditor-input nxe-grid-filter-input\"\r\n [type]=\"frEditorKind(col) === 'date' ? 'date' : 'text'\"\r\n [attr.inputmode]=\"frEditorKind(col) === 'number' ? 'decimal' : null\"\r\n [style.text-align]=\"filterCellAlign(col)\"\r\n [value]=\"frText(col)\"\r\n (input)=\"setFilterRowText(col, $any($event.target).value)\"\r\n (click)=\"$event.stopPropagation()\" />\r\n @if (frOp(col) === 'between') {\r\n <input\r\n class=\"nxe-texteditor-input nxe-grid-filter-input nxe-fr-second\"\r\n [type]=\"frEditorKind(col) === 'date' ? 'date' : 'text'\"\r\n [attr.inputmode]=\"frEditorKind(col) === 'number' ? 'decimal' : null\"\r\n [value]=\"frText2(col)\"\r\n (input)=\"setFilterRowText(col, $any($event.target).value, true)\"\r\n (click)=\"$event.stopPropagation()\" />\r\n }\r\n </div>\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"nxe-command-edit\" [attr.aria-colindex]=\"editColIndex()\"></td>\r\n }\r\n </tr>\r\n }\r\n </thead>\r\n <tbody>\r\n @for (item of renderRows(); track $index; let ri = $index) {\r\n @if (item.kind === 'group') {\r\n <!-- dx group-row anatomy (bright audit): [indent cells][expand chevron\r\n cell][dx-group-cell colspan][alignByColumn summary cells]. dx numbers\r\n every cell of a rows-view row, command cells included\r\n (m_columns_view.js:681-693), and the caption as the cell after the\r\n toggle (m_rows_view.js:626-635, columnIndex groupIndex + 1). That\r\n number can be a data column's, so dx looks group rows up with\r\n `:not(.dx-group-cell)` (m_columns_view.js:942), and onCellPrepared\r\n reports the caption as no column. -->\r\n <tr\r\n class=\"nxe-row nxe-group-row\"\r\n [class.nxe-column-lines]=\"showColumnLines()\"\r\n [attr.aria-roledescription]=\"item.collapsed ? 'Collapsed row' : 'Expanded row'\">\r\n @for (i of indents(item.level); track i) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"i + 1\"></td>\r\n }\r\n <td\r\n class=\"nxe-command-expand nxe-datagrid-group-space nxe-datagrid-expand nxe-selection-disabled\"\r\n [attr.aria-colindex]=\"item.level + 1\"\r\n [attr.aria-label]=\"item.collapsed ? 'Expand' : 'Collapse'\"\r\n (click)=\"toggleGroup(item.groupKey)\">\r\n <div\r\n [class.nxe-datagrid-group-opened]=\"!item.collapsed\"\r\n [class.nxe-datagrid-group-closed]=\"item.collapsed\"></div>\r\n </td>\r\n <td\r\n class=\"nxe-group-cell\"\r\n [attr.aria-colindex]=\"item.level + 2\"\r\n [attr.colspan]=\"groupCellColspan(item)\">\r\n @if (templateFor(item.column.groupCellTemplate()); as gtpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"gtpl\"\r\n [ngTemplateOutletContext]=\"groupCellContext(item)\" />\r\n } @else {\r\n {{ item.column.caption() }}: {{ item.text }}{{ item.captionSuffix }}\r\n }\r\n </td>\r\n @for (col of groupRowTrailing(item); track col.dataField()) {\r\n <td\r\n class=\"nxe-datagrid-summary-item\"\r\n [attr.aria-colindex]=\"leadCellCount() + visibleIndexOf(col) + 1\"\r\n [style.text-align]=\"align(col)\">\r\n {{ item.alignedSums.get(col.dataField()) || '' }}\r\n </td>\r\n }\r\n @if (groupRowTrailing(item).length && rowEditCommandVisible()) {\r\n <td class=\"nxe-command-edit\" [attr.aria-colindex]=\"editColIndex()\"></td>\r\n }\r\n </tr>\r\n } @else if (item.kind === 'groupFooter') {\r\n <!-- dx's group footer: dx-datagrid-group-footer, and column lines like\r\n any row the rows view renders (m_rows_view.js:222-229). Row lines are\r\n data rows' only (:228); the line under a footer is the column-lines\r\n rule's. -->\r\n <tr\r\n class=\"nxe-row nxe-group-footer-row nxe-datagrid-group-footer\"\r\n [class]=\"'nxe-group-footer-level-' + item.level\"\r\n [class.nxe-column-lines]=\"showColumnLines()\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"i + 1\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"expandColIndex()\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"nxe-command-select\" [attr.aria-colindex]=\"selectColIndex()\"></td>\r\n }\r\n @for (col of visibleColumns(); track col.dataField()) {\r\n <td\r\n class=\"nxe-datagrid-summary-item\"\r\n [attr.aria-colindex]=\"leadCellCount() + visibleIndexOf(col) + 1\"\r\n [style.text-align]=\"align(col)\">\r\n {{ item.sums.get(col.dataField()) || '' }}\r\n </td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"nxe-command-edit\" [attr.aria-colindex]=\"editColIndex()\"></td>\r\n }\r\n </tr>\r\n } @else {\r\n <tr\r\n class=\"nxe-row nxe-data-row\"\r\n role=\"row\"\r\n [class]=\"rowClassFor(item.row)\"\r\n [attr.aria-rowindex]=\"item.rowIndex + 1\"\r\n [attr.aria-selected]=\"selectionMode() === 'none' ? null : isSelected(item.row)\"\r\n [class.nxe-row-lines]=\"showRowLines()\"\r\n [class.nxe-column-lines]=\"showColumnLines()\"\r\n [class.nxe-row-alt]=\"rowAlternationEnabled() && item.rowIndex % 2 === 1\"\r\n [class.nxe-selection]=\"isSelected(item.row)\"\r\n [class.nxe-state-hover]=\"isHovered(item.rowIndex)\"\r\n [class.nxe-edit-row]=\"isRowEditing(item.row)\"\r\n (mouseenter)=\"onRowEnter(item.rowIndex)\"\r\n (mouseleave)=\"onRowLeave()\"\r\n (click)=\"onDataRowClick(item.row, item.rowIndex)\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"i + 1\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <!-- dx reuses the group row's toggle for the detail row\r\n (m_master_detail.js:33-43, m_utils.js:462-474), and any click\r\n in the cell toggles (m_grouping.js:523-532 `closest('.dx-datagrid-expand')`).\r\n toggleDetail stops the click there, as dx-selection-disabled does. -->\r\n <td\r\n class=\"nxe-command-expand nxe-datagrid-group-space nxe-datagrid-expand nxe-selection-disabled\"\r\n [attr.aria-colindex]=\"expandColIndex()\"\r\n [attr.aria-label]=\"isDetailExpanded(item.row) ? 'Collapse' : 'Expand'\"\r\n (click)=\"toggleDetail(item.row, $event)\">\r\n <div\r\n [class.nxe-datagrid-group-opened]=\"isDetailExpanded(item.row)\"\r\n [class.nxe-datagrid-group-closed]=\"!isDetailExpanded(item.row)\"></div>\r\n </td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"nxe-command-select\" [attr.aria-colindex]=\"selectColIndex()\">\r\n <span\r\n class=\"nxe-select-checkbox nxe-grid-checkbox nxe-checkbox nxe-datagrid-checkbox-size\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"isSelected(item.row)\"\r\n [class.nxe-checked]=\"isSelected(item.row)\"\r\n (click)=\"onCheckboxClick(item.row, $event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n </td>\r\n }\r\n @for (col of visibleColumns(); track $index; let ci = $index) {\r\n @if (!isMergeSuppressed(ri, col)) {\r\n <td\r\n [class]=\"col.cssClass()\"\r\n role=\"gridcell\"\r\n [attr.aria-colindex]=\"leadCellCount() + ci + 1\"\r\n [attr.tabindex]=\"isCellTabStop(item.rowIndex, ci) ? 0 : -1\"\r\n (focus)=\"focusCell(item.rowIndex, ci)\"\r\n [attr.aria-roledescription]=\"\r\n editableCell(col) && (cellEditingEnabled() || rowEditingEnabled())\r\n ? 'Editable'\r\n : null\r\n \"\r\n [style.text-align]=\"align(col)\"\r\n [attr.rowspan]=\"mergeSpan(ri, col)\"\r\n [class.nxe-editor-cell]=\"isEditingCell(item.row, col) || (isRowEditing(item.row) && editableCell(col))\"\r\n (click)=\"onCellClick(item.row, col, $event)\"\r\n (focusout)=\"onEditCellFocusOut(item.row, col, $event)\">\r\n @if (treeMode && ci === 0) {\r\n <!-- dx's tree icons (dx-treelist-icon-container): one 14px slot\r\n per level, then the 14x18 toggle or, on a leaf, one more slot.\r\n Floated as dx floats it in the fixed table, so the text\r\n wraps beside it and starts flush after it. Not named\r\n nxe-treelist-*: the dx mirror would hand it to an app's\r\n newer-dx tree layout (18px slots). -->\r\n <span class=\"nxe-tree-icons\">\r\n @for (i of indents(item.level || 0); track i) {\r\n <span class=\"nxe-tree-expander-stub\"></span>\r\n }\r\n @if (item.hasChildren) {\r\n <span\r\n class=\"nxe-tree-expander\"\r\n [class.nxe-collapsed]=\"!item.expanded\"\r\n (click)=\"toggleTreeNode(item.nodeKey, $event)\"></span>\r\n } @else {\r\n <span class=\"nxe-tree-expander-stub\"></span>\r\n }\r\n </span>\r\n }\r\n @if (isEditingCell(item.row, col) || (isRowEditing(item.row) && editableCell(col))) {\r\n @if (templateFor(col.editCellTemplate()); as etpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"etpl\"\r\n [ngTemplateOutletContext]=\"editCellContext(item.row, item.rowIndex, ci, col)\" />\r\n } @else if (isEditingCell(item.row, col)) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"cellEditor\"\r\n [ngTemplateOutletContext]=\"{ row: item.row, col: col, mode: 'cell' }\" />\r\n } @else {\r\n <ng-container\r\n [ngTemplateOutlet]=\"cellEditor\"\r\n [ngTemplateOutletContext]=\"{ row: item.row, col: col, mode: 'row' }\" />\r\n }\r\n } @else if (templateFor(col.cellTemplate()); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"cellContext(item.row, item.rowIndex, ci, col)\" />\r\n } @else if (isBooleanCell(col)) {\r\n <!-- dx showEditorAlways: a boolean cell is a dx checkbox, not\r\n \"true\"/\"false\". It is read-only unless the grid edits cells\r\n and the column is editable (m_editing.js:210, 1832-1847);\r\n then one click toggles the value in place. -->\r\n <span\r\n class=\"nxe-grid-checkbox nxe-grid-checkbox-readonly nxe-checkbox nxe-datagrid-checkbox-size\"\r\n role=\"checkbox\"\r\n [class.nxe-grid-checkbox-left]=\"align(col) === 'left'\"\r\n [class.nxe-grid-checkbox-right]=\"align(col) === 'right'\"\r\n [class.nxe-checked]=\"getRaw(item.row, col.dataField()) === true\"\r\n [class.nxe-state-readonly]=\"booleanReadOnly(col)\"\r\n [attr.aria-checked]=\"getRaw(item.row, col.dataField()) === true\"\r\n [attr.aria-readonly]=\"booleanReadOnly(col) ? 'true' : null\"\r\n (click)=\"onBooleanCellClick(item.row, col, $event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n } @else {\r\n <!-- In an ng-container so the text node carries no padding space:\r\n after the tree icons one would render as a 3px gap. -->\r\n <ng-container>{{ cellText(col, item.row) }}</ng-container>\r\n }\r\n </td>\r\n }\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"nxe-command-edit nxe-datagrid-drag-action\" [attr.aria-colindex]=\"editColIndex()\">\r\n @if (isRowEditing(item.row)) {\r\n <a class=\"nxe-link nxe-link-save\" (click)=\"saveRowEdit(item.row, $event)\">Save</a>\r\n <a class=\"nxe-link nxe-link-cancel\" (click)=\"cancelRowEdit($event)\">Cancel</a>\r\n } @else {\r\n <a class=\"nxe-link nxe-link-edit\" (click)=\"startRowEdit(item.row, $event)\">Edit</a>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n @if (masterDetailEnabled() && isDetailExpanded(item.row)) {\r\n <!-- dx's detail row takes row lines as well as column lines, and its one\r\n cell is dx-master-detail-cell (m_master_detail.js:290-294, :325),\r\n rendered as column 0 - so aria-colindex 1. -->\r\n <tr\r\n class=\"nxe-row nxe-master-detail-row\"\r\n [class.nxe-row-lines]=\"showRowLines()\"\r\n [class.nxe-column-lines]=\"showColumnLines()\">\r\n <td class=\"nxe-master-detail-cell nxe-cell-focus-disabled\" aria-colindex=\"1\" [attr.colspan]=\"allColCount()\">\r\n @if (detailTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"detailContext(item.row, item.rowIndex)\" />\r\n }\r\n </td>\r\n </tr>\r\n }\r\n }\r\n }\r\n <!-- dx's free-space row (m_rows_view.js:448-466): one cell per column, so the\r\n column lines run on through the space it fills. The row is\r\n role=\"presentation\", as dx's `_createEmptyRow` makes it, so assistive tech\r\n inside role=grid does not meet a row of empty gridcells; its cells take\r\n no role and no aria-colindex: nothing that addresses cells may count them. -->\r\n <tr\r\n role=\"presentation\"\r\n class=\"nxe-row nxe-freespace-row\"\r\n [class.nxe-column-lines]=\"showColumnLines()\"\r\n [style.height.px]=\"freeSpaceHeight() || null\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"nxe-command-select\"></td>\r\n }\r\n @for (col of visibleColumns(); track $index) {\r\n <td [class]=\"col.cssClass()\"></td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"nxe-command-edit\"></td>\r\n }\r\n </tr>\r\n </tbody>\r\n @if (hasTotalFooter()) {\r\n <tfoot class=\"nxe-datagrid-total-footer\">\r\n <tr class=\"nxe-row\">\r\n @for (i of indents(groupLevelCount()); track i) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"i + 1\"></td>\r\n }\r\n @if (masterDetailEnabled()) {\r\n <td class=\"nxe-command-expand nxe-datagrid-group-space\" [attr.aria-colindex]=\"expandColIndex()\"></td>\r\n }\r\n @if (showCheckboxes()) {\r\n <td class=\"nxe-command-select\" [attr.aria-colindex]=\"selectColIndex()\"></td>\r\n }\r\n @for (col of visibleColumns(); track col.dataField()) {\r\n <td\r\n class=\"nxe-datagrid-summary-item\"\r\n [attr.aria-colindex]=\"leadCellCount() + visibleIndexOf(col) + 1\"\r\n [style.text-align]=\"align(col)\">\r\n {{ totalSums().get(col.dataField()) || '' }}\r\n </td>\r\n }\r\n @if (rowEditCommandVisible()) {\r\n <td class=\"nxe-command-edit\" [attr.aria-colindex]=\"editColIndex()\"></td>\r\n }\r\n </tr>\r\n </tfoot>\r\n }\r\n </table>\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <!-- dx `.dx-datagrid-rowsview.dx-empty{height:100px}`: our rows view also holds the\r\n <thead>, so the empty body is its own box after the table. dx appends the nodata\r\n span to the rows view outside its scrollable (m_utils.js:206-210) and positions the\r\n load panel `of:` the rows view, header excluded (m_rows_view.js:1023) - both live\r\n in this box. The load panel can: loading() implies a null source, hence no rows.\r\n If the total footer is ever shown for empty data (dx m_summary.js:487-503), this\r\n box has to move between the <tbody> and the <tfoot>. -->\r\n <div class=\"nxe-datagrid-empty-body\">\r\n @if (!loading()) {\r\n <span class=\"nxe-datagrid-nodata\">{{ noDataText() }}</span>\r\n } @else {\r\n <div class=\"nxe-datagrid-loadpanel\"><span class=\"nxe-grid-spinner\"></span></div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (pagerVisible()) {\r\n <!-- dx's pager (pagination/content.js) under dx's class names: the page sizes float left;\r\n the pages float right - the info text, then prev, the page buttons and next. dx\r\n renders div[role=button]s; these are real buttons with dx's labels. The prev/next\r\n buttons are empty: the chevron is drawn by CSS. At either end dx only takes the\r\n button out of the tab order (page_index_selector.js), so it keeps focus rather than\r\n dropping it as a disabled button would; a click there is a no-op (goToPage clamps). -->\r\n <div\r\n class=\"nxe-datagrid-pager nxe-pager nxe-grid-pager\"\r\n role=\"navigation\"\r\n [attr.aria-label]=\"pagerCmp()?.label()\">\r\n @if (pagerCmp()?.showPageSizeSelector()) {\r\n <div class=\"nxe-page-sizes\">\r\n @if (pagerCompact()) {\r\n @if (pageSizes().length) {\r\n <select class=\"nxe-pager-size\" aria-label=\"Page size\" (change)=\"onPageSizeSelect($event)\">\r\n @for (s of pageSizes(); track $index) {\r\n <option [value]=\"s\" [selected]=\"s === effectivePageSize()\">{{ s }}</option>\r\n }\r\n </select>\r\n }\r\n } @else {\r\n @for (s of pageSizes(); track $index; let first = $first) {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-page-size\"\r\n [class.nxe-selection]=\"s === effectivePageSize()\"\r\n [class.nxe-first-child]=\"first\"\r\n [attr.aria-label]=\"'Items per page: ' + s\"\r\n (click)=\"changePageSize(s)\">{{ s }}</button>\r\n }\r\n }\r\n </div>\r\n }\r\n <div class=\"nxe-pages\">\r\n @if (pagerCmp()?.showInfo()) {\r\n <div class=\"nxe-info nxe-pager-info\">{{ pageInfo() }}</div>\r\n }\r\n <div class=\"nxe-page-indexes nxe-pager-pages\">\r\n @if (pagerNavVisible()) {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-navigate-button nxe-prev-button nxe-pager-nav\"\r\n [class.nxe-button-disable]=\"currentPage() === 0\"\r\n [attr.aria-disabled]=\"currentPage() === 0 ? 'true' : null\"\r\n [attr.tabindex]=\"currentPage() === 0 ? -1 : null\"\r\n aria-label=\"Previous page\"\r\n (click)=\"prevPage()\"></button>\r\n }\r\n @for (item of pageItems(); track item.key) {\r\n @if (item.separator) {\r\n <div class=\"nxe-separator\">. . .</div>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-page nxe-pager-page\"\r\n [class.nxe-selection]=\"item.page === currentPage()\"\r\n [class.nxe-active]=\"item.page === currentPage()\"\r\n [attr.aria-label]=\"'Page ' + (item.page + 1)\"\r\n [attr.aria-current]=\"item.page === currentPage() ? 'page' : null\"\r\n (click)=\"goToPage(item.page)\">{{ item.page + 1 }}</button>\r\n }\r\n }\r\n @if (pagerNavVisible()) {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-navigate-button nxe-next-button nxe-pager-nav\"\r\n [class.nxe-button-disable]=\"currentPage() >= pageCount() - 1\"\r\n [attr.aria-disabled]=\"currentPage() >= pageCount() - 1 ? 'true' : null\"\r\n [attr.tabindex]=\"currentPage() >= pageCount() - 1 ? -1 : null\"\r\n aria-label=\"Next page\"\r\n (click)=\"nextPage()\"></button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n\r\n<!-- Inline cell/row editor (dxo-editing, orbit addition). `mode: 'cell'` commits on blur/Enter;\r\n `mode: 'row'` buffers on input until the command column's Save. -->\r\n<ng-template #cellEditor let-row=\"row\" let-col=\"col\" let-mode=\"mode\">\r\n @if (col.lookup(); as lookup) {\r\n <select\r\n class=\"nxe-texteditor-input nxe-grid-cell-editor\"\r\n [value]=\"editorInputValue(row, col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (change)=\"\r\n mode === 'cell'\r\n ? commitCellEdit(row, col, $any($event.target).value)\r\n : bufferRowEdit(col, $any($event.target).value)\r\n \">\r\n <option value=\"\"></option>\r\n @for (item of lookup.dataSource; track $index) {\r\n <option [value]=\"item[lookup.valueExpr ?? 'lovCode']\">\r\n {{ item[lookup.displayExpr ?? 'lovName'] }}\r\n </option>\r\n }\r\n </select>\r\n } @else if (colType(col) === 'boolean') {\r\n <!-- dx's checkbox editor, the same box as the resting boolean cell (never the browser's\r\n native checkbox). Cell mode toggles resting cells in place and never gets here; this\r\n is the row being edited, and a click or Space only buffers until Save. It takes focus\r\n as dx's dxCheckBox does (tabIndex 0, Space toggles: check_box.js:125), so Tab reaches\r\n it with the row's other editors. -->\r\n <span\r\n class=\"nxe-grid-checkbox nxe-grid-checkbox-readonly nxe-checkbox nxe-datagrid-checkbox-size nxe-grid-cell-editor-check\"\r\n role=\"checkbox\"\r\n tabindex=\"0\"\r\n [class.nxe-grid-checkbox-left]=\"align(col) === 'left'\"\r\n [class.nxe-grid-checkbox-right]=\"align(col) === 'right'\"\r\n [class.nxe-checked]=\"editorChecked(row, col)\"\r\n [attr.aria-checked]=\"editorChecked(row, col)\"\r\n (click)=\"toggleEditorCheck(row, col, mode, $event)\"\r\n (keydown.space)=\"toggleEditorCheck(row, col, mode, $event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n } @else {\r\n <input\r\n type=\"text\"\r\n class=\"nxe-texteditor-input nxe-grid-cell-editor\"\r\n [value]=\"editorInputValue(row, col)\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.enter)=\"mode === 'cell' && commitCellEdit(row, col, $any($event.target).value)\"\r\n (keydown.escape)=\"mode === 'cell' && cancelCellEdit()\"\r\n (blur)=\"mode === 'cell' && commitCellEdit(row, col, $any($event.target).value)\"\r\n (input)=\"mode === 'row' && bufferRowEdit(col, $any($event.target).value)\" />\r\n }\r\n</ng-template>\r\n\r\n<!-- A header cell's indicator block (dx .dx-column-indicators). dx creates the sort span first and\r\n the funnel second; the funnel is prepended when the column is right-aligned\r\n (m_header_filter_core.js _renderIndicator), so a right-aligned column reads [funnel][sort] and\r\n every other [sort][funnel]. An unsorted sortable column keeps an empty `nxe-sort-none` span, as\r\n dx does: hidden in the fixed table, measured by the best-fit pass. -->\r\n<ng-template\r\n #headerIndicators\r\n let-col\r\n let-dir=\"dir\"\r\n let-sortSlot=\"sortSlot\"\r\n let-hf=\"hf\"\r\n let-hfFirst=\"hfFirst\"\r\n let-float=\"float\">\r\n <div class=\"nxe-column-indicators\" role=\"presentation\" [style.float]=\"float\">\r\n @for (kind of hfFirst ? ['funnel', 'sort'] : ['sort', 'funnel']; track kind) {\r\n @if (kind === 'funnel') {\r\n @if (hf) {\r\n <span\r\n class=\"nxe-grid-hf-icon\"\r\n [class.nxe-hf-active]=\"hasHeaderFilter(col)\"\r\n (click)=\"openHeaderFilter(col, $event)\"></span>\r\n }\r\n } @else if (sortSlot) {\r\n <span\r\n class=\"nxe-sort\"\r\n [class.nxe-sort-none]=\"!dir\"\r\n [class.nxe-sort-up]=\"dir === 'asc'\"\r\n [class.nxe-sort-down]=\"dir === 'desc'\"></span>\r\n }\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n<!-- The header-filter popup (#hfPanel) moved to nxe-header-filter-panel.component.html \u2014\r\n openHeaderFilter attaches it through openNxeHeaderFilterOverlay (pivot parity Phase 3a). -->\r\n\r\n<!-- Filter-row operation-chooser dropdown (dx filter menu) -->\r\n<ng-template #frOpMenu>\r\n <div class=\"nxe-fr-op-menu nxe-datagrid\">\r\n @for (item of frMenuOps(); track item.op) {\r\n <div\r\n class=\"nxe-menu-item\"\r\n [class.nxe-menu-item-selected]=\"frMenuColumn() && frOp(frMenuColumn()!) === item.op\"\r\n (click)=\"selectFilterOp(item.op)\">\r\n <span class=\"nxe-icon nxe-icon-filter-operation-{{ opIconSlug(item.op) }}\"></span>\r\n <span class=\"nxe-menu-item-text\">{{ item.label }}</span>\r\n </div>\r\n }\r\n <!-- dx draws Reset with the chooser's own magnifier. -->\r\n <div class=\"nxe-menu-item nxe-fr-reset\" (click)=\"resetFilterRow()\">\r\n <span class=\"nxe-icon nxe-icon-filter-operation-default\"></span>\r\n <span class=\"nxe-menu-item-text\">Reset</span>\r\n </div>\r\n </div>\r\n</ng-template>\r\n\r\n", styles: ["@charset \"UTF-8\";.nxe-data-grid{display:block}.nxe-data-grid .nxe-datagrid{height:100%;position:relative;white-space:normal}.nxe-data-grid .nxe-gridbase-container{display:flex;flex-direction:column;height:100%}.nxe-data-grid .nxe-datagrid-rowsview{position:relative;overflow:auto;flex:1 1 auto;min-height:0;background-color:#fff}.nxe-data-grid .nxe-datagrid-content{display:block;min-height:100%}.nxe-data-grid .nxe-datagrid-rowsview.nxe-empty{display:flex;flex-direction:column;height:auto}.nxe-data-grid .nxe-datagrid-rowsview.nxe-empty>.nxe-datagrid-content{flex:none;min-height:0}.nxe-data-grid .nxe-datagrid-rowsview.nxe-empty>.nxe-datagrid-empty-body{position:sticky;inset-inline-start:0;flex:1 1 var(--nxe-ui-grid-empty-height, 100px);min-height:0}.nxe-data-grid table.nxe-datagrid-table{display:table;width:100%;table-layout:fixed;border-collapse:collapse;margin:0;overflow-x:clip}.nxe-data-grid .nxe-datagrid-content .nxe-datagrid-table{max-width:none}.nxe-data-grid .nxe-datagrid-headers{display:table-header-group;position:sticky;top:0;z-index:1;color:#757575;font-weight:400;background:#fff;border-bottom:0}.nxe-data-grid tbody{display:table-row-group}.nxe-data-grid .nxe-row{display:table-row}.nxe-data-grid .nxe-datagrid-rowsview tbody>.nxe-row{border-top:var(--nxe-ui-grid-line-width, 1px) solid transparent;border-bottom:var(--nxe-ui-grid-line-width, 1px) solid transparent}.nxe-data-grid .nxe-datagrid-headers>.nxe-row{border-top:0;border-bottom:0}.nxe-data-grid .nxe-datagrid-headers:before{content:none}.nxe-data-grid .nxe-datagrid-headers>.nxe-row>td{border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-header-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview.nxe-datagrid-after-headers{border-top:0}.nxe-data-grid .nxe-datagrid .nxe-row-lines>td{border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-row-lines:first-child,.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-row-lines:first-child>td{border-top:none}.nxe-data-grid .nxe-datagrid .nxe-column-lines>td{border-left:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-column-line-color, #ddd);border-right:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-column-line-color, #ddd)}.nxe-data-grid .nxe-datagrid .nxe-column-lines>td:first-child{border-left:none}.nxe-data-grid .nxe-datagrid .nxe-column-lines>td:last-child{border-right:none}.nxe-data-grid .nxe-datagrid-content .nxe-datagrid-table .nxe-row>td:first-child{border-left:0}.nxe-data-grid .nxe-datagrid-content .nxe-datagrid-table .nxe-row>td.nxe-datagrid-group-space{border-right:none}.nxe-data-grid .nxe-datagrid-content .nxe-datagrid-table .nxe-row>td.nxe-datagrid-group-space+td{border-left:none}.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-group-row>td{border-top:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd);border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-group-row:first-child,.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-group-row:first-child>td{border-top:none}.nxe-data-grid .nxe-datagrid-rowsview .nxe-group-row.nxe-row>td{border-left-color:transparent;border-right-color:transparent}.nxe-data-grid .nxe-row.nxe-datagrid-group-footer>td{border-top:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd);background-color:#fff;border-left-width:0;border-right-width:0}.nxe-data-grid .nxe-row.nxe-datagrid-group-footer.nxe-column-lines{border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-edit-row>td{border-top:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd);border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview .nxe-row.nxe-edit-row:first-child>td{border-top-width:0;border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused):not(.nxe-row-lines)>td,.nxe-data-grid .nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused):hover:not(.nxe-row-lines)>td{border-top:var(--nxe-ui-grid-line-width, 1px) solid #e6e6e6;border-bottom:var(--nxe-ui-grid-line-width, 1px) solid #e6e6e6}.nxe-data-grid .nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused).nxe-column-lines>td,.nxe-data-grid .nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused):hover.nxe-column-lines>td{border-left-color:var(--nxe-ui-grid-column-line-color, #ddd);border-right-color:var(--nxe-ui-grid-column-line-color, #ddd)}.nxe-data-grid .nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused).nxe-row-lines>td,.nxe-data-grid .nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused):hover.nxe-row-lines>td{border-bottom-color:var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-data-grid .nxe-datagrid .nxe-row-alt>td{background-color:var(--nxe-ui-grid-alt-row-bg, #f5f5f5)}.nxe-data-grid .nxe-datagrid .nxe-row-alt.nxe-row:not(.nxe-row-removed){border-bottom-color:transparent}.nxe-data-grid .nxe-datagrid-borders>.nxe-gridbase-container>.nxe-datagrid-rowsview{border:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-border-color, #ddd)}.nxe-data-grid .nxe-datagrid-borders>.nxe-gridbase-container>.nxe-datagrid-header-panel{border-bottom:0}.nxe-data-grid .nxe-datagrid-borders>.nxe-gridbase-container>.nxe-grid-pager{margin-top:-1px}.nxe-data-grid .nxe-datagrid-borders>.nxe-gridbase-container>.nxe-datagrid-rowsview.nxe-last-row-border>.nxe-datagrid-content>.nxe-datagrid-table>tbody>.nxe-data-row:nth-last-child(2),.nxe-data-grid .nxe-datagrid-borders>.nxe-gridbase-container>.nxe-datagrid-rowsview.nxe-last-row-border>.nxe-datagrid-content>.nxe-datagrid-table>tbody>.nxe-data-row:nth-last-child(2)>td{border-bottom-width:0}.nxe-data-grid .nxe-datagrid-nodata{position:absolute;top:50%;left:50%;width:100%;text-align:center;transform:translate(-50%,-50%);color:#333;font-size:var(--nxe-ui-grid-nodata-font-size, 17px);opacity:.7;pointer-events:none}.nxe-data-grid .nxe-datagrid-loadpanel{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#ffffff80;z-index:2}.nxe-data-grid .nxe-grid-spinner{width:32px;height:32px;border:3px solid rgba(48,55,76,.2);border-top-color:#30374c;border-radius:50%;animation:nxe-grid-spin .8s linear infinite}.nxe-data-grid .nxe-header-row>td{position:relative;cursor:default;-webkit-user-select:none;user-select:none;white-space:nowrap;overflow:clip;overflow-clip-margin:4px}.nxe-data-grid .nxe-header-row>td.nxe-datagrid-action{cursor:pointer}.nxe-data-grid .nxe-header-row>td>.nxe-datagrid-text-content{display:inline-block;vertical-align:top;white-space:normal;overflow:hidden;text-overflow:ellipsis}.nxe-data-grid .nxe-datagrid-nowrap .nxe-header-row>td>.nxe-datagrid-text-content{white-space:nowrap}.nxe-data-grid .nxe-header-row>td>.nxe-datagrid-text-content:not(.nxe-sort-indicator):not(.nxe-header-filter-indicator){max-width:100%}.nxe-data-grid .nxe-header-row .nxe-sort-indicator,.nxe-data-grid .nxe-header-row .nxe-header-filter-indicator{max-width:calc(100% - 17px)}.nxe-data-grid .nxe-header-row .nxe-sort-indicator.nxe-text-content-alignment-left,.nxe-data-grid .nxe-header-row .nxe-header-filter-indicator.nxe-text-content-alignment-left{margin-right:3px}.nxe-data-grid .nxe-header-row .nxe-sort-indicator.nxe-text-content-alignment-right,.nxe-data-grid .nxe-header-row .nxe-header-filter-indicator.nxe-text-content-alignment-right{margin-left:3px}.nxe-data-grid .nxe-header-row .nxe-sort-indicator.nxe-text-content-alignment-left.nxe-text-content-alignment-right,.nxe-data-grid .nxe-header-row .nxe-header-filter-indicator.nxe-text-content-alignment-left.nxe-text-content-alignment-right{max-width:calc(100% - 34px)}.nxe-data-grid .nxe-header-row .nxe-sort-indicator.nxe-header-filter-indicator{max-width:calc(100% - 31px)}.nxe-data-grid .nxe-header-row .nxe-sort-indicator.nxe-header-filter-indicator.nxe-text-content-alignment-left.nxe-text-content-alignment-right{max-width:calc(100% - 62px)}.nxe-data-grid .nxe-column-indicators{display:inline-block;vertical-align:top;white-space:nowrap}.nxe-data-grid .nxe-column-indicators.nxe-visibility-hidden{visibility:hidden}.nxe-data-grid .nxe-column-indicator-spacer{display:inline-block;width:14px;height:14px;vertical-align:top}.nxe-data-grid .nxe-datagrid-table-fixed .nxe-column-indicators .nxe-sort.nxe-sort-none,.nxe-data-grid .nxe-datagrid-table-fixed .nxe-column-indicators .nxe-column-indicator-spacer-none{display:none}.nxe-data-grid .nxe-datagrid-best-fit .nxe-column-indicators{float:none!important}.nxe-data-grid .nxe-datagrid-best-fit .nxe-column-indicators>span{width:14px}.nxe-data-grid .nxe-datagrid-best-fit .nxe-column-indicators .nxe-sort.nxe-sort-none,.nxe-data-grid .nxe-datagrid-best-fit .nxe-column-indicators .nxe-column-indicator-spacer-none{display:inline-block!important}.nxe-data-grid .nxe-datagrid-best-fit .nxe-header-row .nxe-datagrid-text-content{max-width:none!important}.nxe-data-grid .nxe-datagrid-best-fit .nxe-text-content-alignment-left{margin-right:3px}.nxe-data-grid .nxe-datagrid-best-fit .nxe-text-content-alignment-right{margin-left:3px}.nxe-data-grid .nxe-sort{display:inline-block;position:relative;width:14px;height:14px;vertical-align:top}.nxe-data-grid .nxe-sort:after{content:\"\";position:absolute;left:0;top:0;width:14px;height:14px;background-color:currentColor}@media(forced-colors:active){.nxe-data-grid .nxe-sort:after{forced-color-adjust:none;background-color:CanvasText}}.nxe-data-grid .nxe-sort.nxe-sort-up:after{clip-path:polygon(6.5px 0,9px 3px,7px 3px,7px 14px,6px 14px,6px 3px,4px 3px)}.nxe-data-grid .nxe-sort.nxe-sort-down:after{clip-path:polygon(6px 0,7px 0,7px 11px,9px 11px,6.5px 14px,4px 11px,6px 11px)}.nxe-data-grid .nxe-sort.nxe-sort-none:after{content:none}.nxe-data-grid .nxe-datagrid-group-panel .nxe-sort{color:#757575}.nxe-data-grid .nxe-datagrid-group-panel .nxe-group-panel-item .nxe-sort{margin-left:6px}.nxe-data-grid .nxe-grid-hf-icon{display:inline-block;position:relative;width:14px;height:14px;cursor:pointer;vertical-align:top;opacity:.5}.nxe-data-grid .nxe-grid-hf-icon:before{content:\"\";position:absolute;inset:0;background-color:#757575;clip-path:polygon(0 0,14px 0,14px 3px,9px 8px,9px 14px,5px 14px,5px 8px,0 3px)}@media(forced-colors:active){.nxe-data-grid .nxe-grid-hf-icon:before{forced-color-adjust:none;background-color:CanvasText}}.nxe-data-grid .nxe-grid-hf-icon:after{content:\"\";position:absolute;inset:0;margin:-7px}.nxe-data-grid .nxe-grid-hf-icon.nxe-hf-active{opacity:1}.nxe-data-grid .nxe-grid-pager{display:block;flex:none;overflow:hidden;width:100%;padding:9px 0;border-top:1px solid var(--nxe-ui-grid-border-color, #ddd);color:#333;line-height:normal;-webkit-user-select:none;user-select:none}.nxe-data-grid .nxe-grid-pager .nxe-page-sizes{float:left}.nxe-data-grid .nxe-grid-pager .nxe-pages{float:right;white-space:nowrap}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-info{display:inline-block;margin-right:9px;opacity:.7}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-page-indexes{display:inline-block}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-navigate-button,.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-page,.nxe-data-grid .nxe-grid-pager .nxe-page-sizes .nxe-page-size{appearance:none;margin:0;background:transparent;color:inherit;font:inherit;text-align:center;outline:0;cursor:pointer}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-navigate-button:focus-visible,.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-page:focus-visible,.nxe-data-grid .nxe-grid-pager .nxe-page-sizes .nxe-page-size:focus-visible{outline:2px solid #5c95c5;outline-offset:-2px}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-page{display:inline-block;margin:0 1px 0 5px;padding:7px 9px 8px;border:1px solid transparent;border-radius:3px}.nxe-data-grid .nxe-grid-pager .nxe-page-sizes .nxe-page-size{display:inline-block;margin:0 1px 0 4px;padding:7px 9px 8px 10px;border:1px solid transparent;border-radius:3px}.nxe-data-grid .nxe-grid-pager .nxe-page-sizes .nxe-page-size.nxe-first-child,.nxe-data-grid .nxe-grid-pager .nxe-page-sizes .nxe-page-size:first-child{margin-left:1px}.nxe-data-grid .nxe-grid-pager .nxe-page-sizes .nxe-selection,.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-selection{cursor:inherit;text-shadow:none;color:#333;border-color:transparent;background-color:#d4d4d4}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-separator{display:inline-block;padding:0 8px}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-navigate-button{position:relative;display:inline-block;box-sizing:content-box;width:9px;height:17px;padding:9px 13px;border:0;vertical-align:top}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-navigate-button:before{content:\"\";position:absolute;display:block;box-sizing:border-box;top:50%;left:50%;width:21px;height:21px;margin:-10.5px 0 0 -10.5px;background-color:currentColor}@media(forced-colors:active){.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-navigate-button:before{forced-color-adjust:none;background-color:CanvasText}}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-navigate-button.nxe-button-disable{opacity:.3;cursor:inherit}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-prev-button:before{clip-path:polygon(5.75px 10.25px,13.75px 2.25px,15.25px 3.75px,8.75px 10.25px,15.25px 16.75px,13.75px 18.25px)}.nxe-data-grid .nxe-grid-pager .nxe-pages .nxe-next-button:before{clip-path:polygon(15.25px 10.25px,7.25px 2.25px,5.75px 3.75px,12.25px 10.25px,5.75px 16.75px,7.25px 18.25px)}.nxe-data-grid .nxe-grid-pager .nxe-pager-size{padding:2px 6px;border:1px solid #ddd;border-radius:3px;font:inherit}.nxe-data-grid .nxe-datagrid-header-panel{padding:.5rem .75rem}.nxe-data-grid .nxe-datagrid-group-panel{align-items:center;min-height:36px}.nxe-data-grid .nxe-tree-icons{display:inline-block;float:left;white-space:nowrap;vertical-align:top;line-height:1}.nxe-data-grid .nxe-tree-expander,.nxe-data-grid .nxe-tree-expander-stub{display:inline-block;position:relative;width:14px;height:14px;vertical-align:top}.nxe-data-grid .nxe-tree-expander{height:18px;margin-right:4px;color:#959595;cursor:pointer}.nxe-data-grid .nxe-tree-expander:before{content:\"\";position:absolute;left:0;top:8px;width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid currentColor}.nxe-data-grid .nxe-tree-expander.nxe-collapsed:before{left:1px;top:6px;border-top:4px solid transparent;border-right:0;border-bottom:4px solid transparent;border-left:4px solid currentColor}.nxe-data-grid .nxe-group-row{font-weight:700;color:var(--nxe-ui-grid-group-row-color, #222)}.nxe-data-grid .nxe-group-row>td{background-color:#f7f7f7}.nxe-data-grid .nxe-group-row>td.nxe-group-cell{font-weight:700}.nxe-data-grid .nxe-group-row>td.nxe-datagrid-summary-item,.nxe-data-grid .nxe-group-footer-row>td.nxe-datagrid-summary-item{font-weight:700;color:var(--nxe-ui-grid-summary-color, rgba(51, 51, 51, .7))}.nxe-data-grid .nxe-datagrid-group-opened,.nxe-data-grid .nxe-datagrid-group-closed{display:block;position:relative;width:100%;height:18px;padding:0;color:#959595;cursor:pointer}.nxe-data-grid .nxe-datagrid-group-opened:before,.nxe-data-grid .nxe-datagrid-group-closed:before{content:\"\"!important;position:absolute;left:50%;right:auto;width:0;height:0}.nxe-data-grid .nxe-datagrid-group-opened:before{top:7px;margin-left:-4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid currentColor}.nxe-data-grid .nxe-datagrid-group-closed:before{top:5px;margin-left:-2px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid currentColor}.nxe-data-grid .nxe-datagrid-total-footer>tr>td,.nxe-data-grid tfoot.nxe-datagrid-total-footer td{border-top:1px solid #ddd;padding:6px 4px;font-weight:700;color:#959595}.nxe-data-grid .nxe-datagrid-filter-row>td{padding:2px 4px}.nxe-data-grid .nxe-grid-filter-input{width:100%;box-sizing:border-box;padding:4px 6px;border:1px solid #ddd;border-radius:2px;font:inherit;color:#333;outline:none}.nxe-data-grid .nxe-grid-filter-input:focus{border-color:#30374c}.nxe-data-grid .nxe-datagrid-filter-row select.nxe-grid-filter-input{appearance:none;background:#fff url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 4'%3E%3Cpath d='M0 0h8L4 4z' fill='%23333'/%3E%3C/svg%3E\") no-repeat right 12px center/8px 4px;padding-right:34px!important}.nxe-data-grid .nxe-command-select{width:40px;min-width:40px;max-width:40px;text-align:center;vertical-align:middle}.nxe-data-grid .nxe-grid-checkbox{display:block;width:16px;margin:0 auto;padding-bottom:2px;line-height:0;cursor:pointer}.nxe-data-grid .nxe-grid-checkbox.nxe-grid-checkbox-left{margin-left:0}.nxe-data-grid .nxe-grid-checkbox.nxe-grid-checkbox-right{margin-right:0}.nxe-data-grid .nxe-grid-checkbox:focus{outline:none}.nxe-data-grid .nxe-grid-checkbox:focus .nxe-grid-checkbox-box{border-color:var(--nxe-ui-accent, #337ab7)}.nxe-data-grid .nxe-grid-checkbox .nxe-grid-checkbox-box{display:block;position:relative;width:16px;height:16px;box-sizing:border-box;border:1px solid #ddd;border-radius:2px;background:#fff}.nxe-data-grid .nxe-grid-checkbox:not(.nxe-state-readonly):hover .nxe-grid-checkbox-box{border-color:var(--nxe-ui-checkbox-hover-border, #265a87)}.nxe-data-grid .nxe-grid-checkbox.nxe-checked .nxe-grid-checkbox-box:after{content:\"\";position:absolute;inset:-1px;background-color:var(--nxe-ui-accent, #337ab7);clip-path:polygon(6.75px 12px,2.5px 7.75px,3.5px 6.75px,6.75px 10px,12.75px 4px,13.75px 5px)}@media(forced-colors:active){.nxe-data-grid .nxe-grid-checkbox.nxe-checked .nxe-grid-checkbox-box:after{forced-color-adjust:none;background-color:CanvasText}}.nxe-data-grid .nxe-grid-checkbox.nxe-indeterminate .nxe-grid-checkbox-box:after{content:\"\";position:absolute;left:4px;top:4px;width:6px;height:6px;background-color:var(--nxe-ui-accent, #337ab7)}@media(forced-colors:active){.nxe-data-grid .nxe-grid-checkbox.nxe-indeterminate .nxe-grid-checkbox-box:after{forced-color-adjust:none;background-color:CanvasText}}.nxe-data-grid .nxe-grid-checkbox.nxe-state-readonly{cursor:default}.nxe-data-grid .nxe-grid-checkbox.nxe-state-readonly .nxe-grid-checkbox-box{border-color:#f4f4f4;background-color:#fff}@keyframes nxe-grid-spin{to{transform:rotate(360deg)}}.nxe-datagrid-rowsview .nxe-datagrid-table .nxe-row.nxe-freespace-row{border-top:0;border-bottom:0}.nxe-datagrid-rowsview .nxe-datagrid-table .nxe-row.nxe-freespace-row>td{padding-top:0;padding-bottom:0}.nxe-datagrid-nowrap{white-space:nowrap}.nxe-datagrid-rowsview :where(tbody)>.nxe-row>td{overflow:hidden;text-overflow:ellipsis}.nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused)>td,.nxe-datagrid-rowsview .nxe-selection.nxe-row:not(.nxe-row-focused):hover>td{background-color:#e6e6e6;color:#333}.nxe-data-grid .nxe-data-row>td.nxe-editor-cell input:not([type=hidden]){text-align:inherit}.nxe-datagrid-content .nxe-datagrid-table .nxe-row .nxe-command-expand{padding:0;width:30px;min-width:30px;max-width:30px;-webkit-user-select:none;user-select:none}.nxe-datagrid-table .nxe-row .nxe-command-expand.nxe-datagrid-group-space{padding-top:var(--nxe-ui-grid-expand-offset, 7px)}.nxe-datagrid-content .nxe-datagrid-table .nxe-row>td.nxe-datagrid-group-space{vertical-align:top}.nxe-datagrid-content .nxe-datagrid-table .nxe-row .nxe-command-select{padding:0}.nxe-datagrid-rowsview .nxe-master-detail-row>.nxe-master-detail-cell{padding:20px}.nxe-datagrid-rowsview .nxe-master-detail-row .nxe-master-detail-cell:not(.nxe-row-lines){border-top:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd);border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-grid-row-line-color, #ddd)}.nxe-datagrid-rowsview .nxe-master-detail-row:not(.nxe-datagrid-edit-form) .nxe-master-detail-cell{background-color:#fafafa}.nxe-grid-cell-editor{width:100%;box-sizing:border-box;border:1px solid #337ab7;padding:2px 4px;font:inherit;background:#fff;outline:none}.nxe-data-grid .nxe-command-edit{text-align:center;white-space:nowrap}.nxe-datagrid .nxe-link{text-decoration:underline;cursor:pointer;color:#337ab7}.nxe-datagrid-content .nxe-datagrid-table .nxe-row .nxe-command-edit .nxe-link{display:inline-block}.nxe-datagrid .nxe-datagrid-content .nxe-datagrid-table .nxe-row .nxe-command-edit .nxe-link:not(.nxe-link-icon){margin:0 2px;min-width:20px}.nxe-data-row.nxe-state-hover:not(.nxe-selection):not(.nxe-row-modified):not(.nxe-row-inserted):not(.nxe-row-removed):not(.nxe-edit-row)>td:not(.nxe-focused) .nxe-link,.nxe-datagrid-rowsview .nxe-selection.nxe-row:hover>td .nxe-link,.nxe-datagrid-rowsview .nxe-selection>td .nxe-link{color:inherit}.nxe-data-grid .nxe-header-row td.nxe-datagrid-drag-action,.nxe-data-grid .nxe-group-panel-item{touch-action:none}.nxe-data-grid .nxe-drag-source{opacity:.5}.nxe-data-grid .nxe-datagrid-filter-row .nxe-menu ul{list-style:none;margin:0;padding:0}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu{display:flex;position:relative}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu>.nxe-menu{position:absolute;top:0;left:0;bottom:0;height:auto;z-index:1;cursor:pointer}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu>.nxe-menu ul,.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu>.nxe-menu li{height:100%}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu>.nxe-menu .nxe-menu-item{display:flex;align-items:center;box-sizing:border-box;height:100%;padding:1px 8px 0}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu>.nxe-menu .nxe-menu-item-content{display:flex;align-items:center;padding:0}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu .nxe-grid-filter-input{flex:1 1 0;min-width:0}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu>.nxe-grid-filter-input:not(.nxe-fr-second){padding-left:32px!important}.nxe-data-grid .nxe-datagrid-filter-row .nxe-editor-with-menu .nxe-fr-second{padding-left:4px!important;border-left:1px solid #ddd}.nxe-data-grid .nxe-datagrid-filter-row .nxe-menu-item-content .nxe-icon,.nxe-fr-op-menu .nxe-menu-item .nxe-icon{display:block;flex:0 0 14px;width:14px;height:14px;margin:0;padding:0;font-size:0;line-height:0;color:#898989;background-color:currentColor;-webkit-mask:var(--nxe-ui-grid-fr-op-glyph, linear-gradient(transparent, transparent)) center/14px 14px no-repeat;mask:var(--nxe-ui-grid-fr-op-glyph, linear-gradient(transparent, transparent)) center/14px 14px no-repeat}@media(forced-colors:active){.nxe-data-grid .nxe-datagrid-filter-row .nxe-menu-item-content .nxe-icon,.nxe-fr-op-menu .nxe-menu-item .nxe-icon{forced-color-adjust:none;background-color:CanvasText}}.nxe-data-grid .nxe-datagrid-filter-row .nxe-menu-item-content .nxe-icon:not(.nxe-icon-filter-operation-default),.nxe-fr-op-menu .nxe-menu-item .nxe-icon:not(.nxe-icon-filter-operation-default){position:relative;top:-1px}.nxe-data-grid .nxe-datagrid-filter-row .nxe-menu-item-content .nxe-icon:before,.nxe-fr-op-menu .nxe-menu-item .nxe-icon:before{content:none}.nxe-icon-filter-operation-default{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.5'%3E%3Ccircle cx='5.5' cy='5.5' r='4'/%3E%3Cpath d='M8.5 8.5l4 4'/%3E%3C/g%3E%3C/svg%3E\")}.nxe-icon-filter-operation-contains{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cmask id='m'%3E%3Crect width='14' height='14' fill='%23fff'/%3E%3Cg fill='none' stroke='%23000'%3E%3Ccircle cx='7' cy='9.25' r='1.75'/%3E%3Cpath d='M5.25 3.5v8'/%3E%3C/g%3E%3C/mask%3E%3Crect x='4.5' y='2.5' width='5' height='9.5' mask='url(%23m)'/%3E%3Cg fill='none' stroke='%23000'%3E%3Ccircle cx='2.25' cy='9.25' r='1.75'/%3E%3Cpath d='M4 7v4.5'/%3E%3Cpath d='M13.4 7.9A1.75 1.75 0 1 0 13.4 10.6'/%3E%3C/g%3E%3C/svg%3E\")}.nxe-icon-filter-operation-not-contains{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cmask id='m'%3E%3Crect width='14' height='14' fill='%23fff'/%3E%3Cg fill='none' stroke='%23000'%3E%3Ccircle cx='2.25' cy='9.25' r='1.75'/%3E%3Cpath d='M4 7v4.5'/%3E%3Ccircle cx='7' cy='9.25' r='1.75'/%3E%3Cpath d='M5.25 3.5v8'/%3E%3Cpath d='M13.4 7.9A1.75 1.75 0 1 0 13.4 10.6'/%3E%3C/g%3E%3Crect y='8.75' width='14' height='1'/%3E%3C/mask%3E%3Crect x='0' y='2.5' width='14' height='9.5' mask='url(%23m)'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-starts-with{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cmask id='m'%3E%3Crect width='14' height='14' fill='%23fff'/%3E%3Cg fill='none' stroke='%23000'%3E%3Ccircle cx='2.25' cy='9.25' r='1.75'/%3E%3Cpath d='M4 7v4.5'/%3E%3Ccircle cx='7' cy='9.25' r='1.75'/%3E%3Cpath d='M5.25 3.5v8'/%3E%3C/g%3E%3C/mask%3E%3Crect x='0' y='2.5' width='9.5' height='9.5' mask='url(%23m)'/%3E%3Cg fill='none' stroke='%23000'%3E%3Cpath d='M13.4 7.9A1.75 1.75 0 1 0 13.4 10.6'/%3E%3C/g%3E%3C/svg%3E\")}.nxe-icon-filter-operation-ends-with{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cg fill='none' stroke='%23000'%3E%3Ccircle cx='2.25' cy='9.25' r='1.75'/%3E%3Cpath d='M4 7v4.5'/%3E%3C/g%3E%3Cmask id='m'%3E%3Crect width='14' height='14' fill='%23fff'/%3E%3Cg fill='none' stroke='%23000'%3E%3Ccircle cx='7' cy='9.25' r='1.75'/%3E%3Cpath d='M5.25 3.5v8'/%3E%3Cpath d='M13.4 7.9A1.75 1.75 0 1 0 13.4 10.6'/%3E%3C/g%3E%3C/mask%3E%3Crect x='4.5' y='2.5' width='9.5' height='9.5' mask='url(%23m)'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-equals{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2 5h10v1H2zM2 8h10v1H2z'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-not-equals{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2 5h10v1H2zM2 8h10v1H2zM9.3 2h1.4l-6 10H3.3z'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-less{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M10.75 3.5 3.75 7l7 3.5' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-greater{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M3.25 3.5l7 3.5-7 3.5' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-less-equal{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M10.75 2 3.75 5.5l7 3.5M3.75 8.5l7 3.5' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-greater-equal{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M3.25 2l7 3.5-7 3.5M10.25 8.5l-7 3.5' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3C/svg%3E\")}.nxe-icon-filter-operation-between{--nxe-ui-grid-fr-op-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2 5h1v5H2zM11 5h1v5h-1zM3 7h8v1H3z'/%3E%3C/svg%3E\")}.nxe-fr-op-menu{background:#fff;border:1px solid #ddd;box-shadow:0 2px 6px #00000026;padding:4px 0;min-width:180px}.nxe-fr-op-menu .nxe-menu-item{display:flex;align-items:center;gap:5px;box-sizing:border-box;height:31px;padding:0 24px 0 5px;cursor:pointer;color:#333}.nxe-fr-op-menu .nxe-menu-item:hover{background:#f0f0f0}.nxe-fr-op-menu .nxe-menu-item.nxe-menu-item-selected{background:#e6e6e6}.nxe-data-grid .nxe-col-resize-handle{position:absolute;right:-4px;top:0;bottom:0;width:8px;cursor:col-resize;z-index:2;touch-action:none}body.nxe-grid-resizing{cursor:col-resize;user-select:none;-webkit-user-select:none}.nxe-col-drop-indicator{position:fixed;width:2px;background:#337ab7;z-index:10000;pointer-events:none}\n"] }]
5112
+ }], ctorParameters: () => [], propDecorators: { declaredColumns: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NxeColumnComponent), { ...{ descendants: false }, isSignal: true }] }], templates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NxeTemplateDirective), { isSignal: true }] }], selection: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeSelectionComponent), { isSignal: true }] }], headerFilter: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeHeaderFilterComponent), { isSignal: true }] }], grouping: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeGroupingComponent), { isSignal: true }] }], groupPanel: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeGroupPanelComponent), { isSignal: true }] }], summaryCmp: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeSummaryComponent), { isSignal: true }] }], pagingCmp: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxePagingComponent), { isSignal: true }] }], pagerCmp: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxePagerComponent), { isSignal: true }] }], editingCmp: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeEditingComponent), { isSignal: true }] }], filterRowCmp: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeFilterRowComponent), { isSignal: true }] }], masterDetail: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeMasterDetailComponent), { isSignal: true }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }], keyExpr: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyExpr", required: false }] }], hoverStateEnabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverStateEnabled", required: false }] }], showColumnLinesInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showColumnLines", required: false }] }], showRowLinesInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRowLines", required: false }] }], showBordersInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBorders", required: false }] }], wordWrapEnabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "wordWrapEnabled", required: false }] }], columnAutoWidthInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnAutoWidth", required: false }] }], rowAlternationEnabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowAlternationEnabled", required: false }] }], remoteOperations: [{ type: i0.Input, args: [{ isSignal: true, alias: "remoteOperations", required: false }] }], syncLookupFilterValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "syncLookupFilterValues", required: false }] }], columnMinWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnMinWidth", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], noDataTextInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "noDataText", required: false }] }], loadPanel: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadPanel", required: false }] }], paging: [{ type: i0.Input, args: [{ isSignal: true, alias: "paging", required: false }] }], filterRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterRow", required: false }] }], headerFilterOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerFilter", required: false }] }], groupingOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "grouping", required: false }] }], groupPanelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupPanel", required: false }] }], scrolling: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrolling", required: false }] }], sorting: [{ type: i0.Input, args: [{ isSignal: true, alias: "sorting", required: false }] }], allowColumnResizing: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowColumnResizing", required: false }] }], columnResizingMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnResizingMode", required: false }] }], allowColumnReordering: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowColumnReordering", required: false }] }], repaintChangesOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "repaintChangesOnly", required: false }] }], showColumnHeaders: [{ type: i0.Input, args: [{ isSignal: true, alias: "showColumnHeaders", required: false }] }], parentIdExpr: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentIdExpr", required: false }] }], dataStructure: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataStructure", required: false }] }], rootValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootValue", required: false }] }], autoExpandAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoExpandAll", required: false }] }], expandedRowKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedRowKeys", required: false }] }, { type: i0.Output, args: ["expandedRowKeysChange"] }], selectedRowKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedRowKeys", required: false }] }, { type: i0.Output, args: ["selectedRowKeysChange"] }], mergeColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "mergeColumns", required: false }] }], onSelectionChanged: [{ type: i0.Output, args: ["onSelectionChanged"] }], onRowClick: [{ type: i0.Output, args: ["onRowClick"] }], onContentReady: [{ type: i0.Output, args: ["onContentReady"] }], onOptionChanged: [{ type: i0.Output, args: ["onOptionChanged"] }], rowClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClass", required: false }] }], onCellPrepared: [{
4133
5113
  type: Output
4134
5114
  }], onRowPrepared: [{
4135
5115
  type: Output
@@ -4141,5 +5121,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
4141
5121
  * Generated bundle index. Do not edit.
4142
5122
  */
4143
5123
 
4144
- export { NXE_HF_BLANKS, NxeColumnComponent, NxeDataGridComponent, NxeDragGesture, NxeEditingComponent, NxeFilterRowComponent, NxeGestureClickSuppression, NxeGridDragController, NxeGroupItemComponent, NxeGroupPanelComponent, NxeGroupingComponent, NxeHeaderFilterComponent, NxeHeaderFilterModel, NxeHeaderFilterPanelComponent, NxeMasterDetailComponent, NxePagerComponent, NxePagingComponent, NxeScrollingComponent, NxeSelectionComponent, NxeSummaryComponent, NxeTotalItemComponent, exportDataGrid, exportNxeGridToExcel, nxeDatePattern, nxeDefaultFormat, nxeExcelNumFmt, nxeFormatNumberValue, nxeHfKey, nxeNumberMask, openNxeHeaderFilterOverlay };
5124
+ export { NXE_HF_BLANKS, NxeColumnComponent, NxeDataGridComponent, NxeDragGesture, NxeEditingComponent, NxeFilterRowComponent, NxeGestureClickSuppression, NxeGridDragController, NxeGroupItemComponent, NxeGroupPanelComponent, NxeGroupingComponent, NxeHeaderFilterComponent, NxeHeaderFilterModel, NxeHeaderFilterPanelComponent, NxeMasterDetailComponent, NxePagerComponent, NxePagingComponent, NxeScrollingComponent, NxeSelectionComponent, NxeSummaryComponent, NxeTotalItemComponent, exportDataGrid, exportNxeGridToExcel, nxeDatePattern, nxeDefaultFormat, nxeExcelNumFmt, nxeExcelNumberMask, nxeFormatNumberValue, nxeHfKey, nxeNumberMask, nxeNumberValue, nxeValueDataType, openNxeHeaderFilterOverlay };
4145
5125
  //# sourceMappingURL=nexterajs-ui-grid.mjs.map