@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.
- package/fesm2022/nexterajs-ui-core.mjs +189 -5
- package/fesm2022/nexterajs-ui-core.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-dropdowns.mjs +314 -50
- package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-file-uploader.mjs +6 -3
- package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-forms.mjs +729 -370
- package/fesm2022/nexterajs-ui-forms.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-grid.mjs +1188 -208
- package/fesm2022/nexterajs-ui-grid.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-intl.mjs +15 -5
- package/fesm2022/nexterajs-ui-intl.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-notify.mjs +351 -0
- package/fesm2022/nexterajs-ui-notify.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-pivot.mjs +146 -116
- package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-styling.mjs +568 -3
- package/fesm2022/nexterajs-ui-styling.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-tree-view.mjs +147 -18
- package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-validation.mjs +247 -92
- package/fesm2022/nexterajs-ui-validation.mjs.map +1 -1
- package/fesm2022/nexterajs-ui.mjs +1 -0
- package/fesm2022/nexterajs-ui.mjs.map +1 -1
- package/package.json +5 -1
- package/styles/_dropdown.scss +149 -0
- package/styles/_editor.scss +23 -4
- package/styles/_icons.scss +92 -0
- package/styles/_mixins.scss +104 -1
- package/styles/_scrollbar.scss +134 -0
- package/styles/_toast.scss +243 -0
- package/styles/_tokens.scss +48 -0
- package/styles/index.scss +4 -0
- package/types/nexterajs-ui-core.d.ts +142 -22
- package/types/nexterajs-ui-dropdowns.d.ts +145 -27
- package/types/nexterajs-ui-file-uploader.d.ts +4 -1
- package/types/nexterajs-ui-forms.d.ts +103 -19
- package/types/nexterajs-ui-grid.d.ts +445 -57
- package/types/nexterajs-ui-notify.d.ts +147 -0
- package/types/nexterajs-ui-pivot.d.ts +35 -22
- package/types/nexterajs-ui-styling.d.ts +163 -3
- package/types/nexterajs-ui-tree-view.d.ts +50 -1
- package/types/nexterajs-ui-validation.d.ts +94 -15
- 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,
|
|
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
|
|
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.
|
|
95
|
-
*
|
|
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
|
|
104
|
-
|
|
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
|
-
|
|
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('.
|
|
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
|
|
227
|
+
* The bottom `.nxe-header-row` of THIS grid.
|
|
126
228
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
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('.
|
|
134
|
-
const rows = thead?.querySelectorAll('.
|
|
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
|
|
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
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
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
|
|
145
|
-
|
|
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
|
|
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 (`.
|
|
160
|
-
* `.
|
|
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.
|
|
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.
|
|
168
|
-
* - chip drag highlights the header row with `
|
|
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
|
-
'
|
|
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('
|
|
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('
|
|
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('.
|
|
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 = '
|
|
632
|
+
a.separator.className = 'nxe-block-separator';
|
|
522
633
|
a.separator.textContent = ' '; // dx renders the separator as an 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('.
|
|
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
|
-
|
|
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 `
|
|
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
|
-
|
|
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 `
|
|
1345
|
-
* `<tr class="
|
|
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
|
-
|
|
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
|
|
1425
|
-
const mask = typeof col.format === 'string' ? col.format
|
|
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('.
|
|
1537
|
-
|
|
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
|
|
1543
|
-
//
|
|
1544
|
-
const headerCells =
|
|
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('
|
|
1922
|
+
if (c.contains('nxe-header-row')) {
|
|
1661
1923
|
return 'header';
|
|
1662
1924
|
}
|
|
1663
|
-
if (c.contains('
|
|
1925
|
+
if (c.contains('nxe-datagrid-filter-row')) {
|
|
1664
1926
|
return 'filter';
|
|
1665
1927
|
}
|
|
1666
|
-
if (c.contains('
|
|
1928
|
+
if (c.contains('nxe-data-row')) {
|
|
1667
1929
|
return 'data';
|
|
1668
1930
|
}
|
|
1669
|
-
if (c.contains('
|
|
1931
|
+
if (c.contains('nxe-group-footer-row')) {
|
|
1670
1932
|
return 'groupFooter';
|
|
1671
1933
|
}
|
|
1672
|
-
if (c.contains('
|
|
1934
|
+
if (c.contains('nxe-group-row')) {
|
|
1673
1935
|
return 'group';
|
|
1674
1936
|
}
|
|
1675
|
-
if (tr.parentElement?.classList.contains('
|
|
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>`
|
|
1697
|
-
* `
|
|
1698
|
-
*
|
|
1699
|
-
* (4px cell padding, #f8fafc header, #525252 header text, row/hover/alt colours)
|
|
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: () =>
|
|
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
|
-
|
|
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 ??
|
|
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 ??
|
|
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) =>
|
|
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
|
-
|
|
1948
|
-
|
|
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
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
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('.
|
|
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('.
|
|
2117
|
-
const rowsview = host.querySelector('.
|
|
2118
|
-
|
|
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
|
-
//
|
|
2125
|
-
// row and
|
|
2126
|
-
const cells =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
2413
|
-
|
|
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(
|
|
2918
|
+
footerSums.set(this.summaryTarget(item), text);
|
|
2539
2919
|
}
|
|
2540
2920
|
else if (item.alignByColumn()) {
|
|
2541
|
-
alignedSums.set(
|
|
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'
|
|
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.
|
|
3043
|
+
this.goToPage(this.currentPage() - 1);
|
|
2648
3044
|
}
|
|
2649
3045
|
nextPage() {
|
|
2650
|
-
this.goToPage(this.
|
|
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
|
-
|
|
2660
|
-
|
|
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
|
|
2961
|
-
|
|
2962
|
-
const
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
/**
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
return
|
|
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) =>
|
|
3542
|
-
return match ?
|
|
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(
|
|
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:
|
|
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('.
|
|
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
|
-
|
|
4438
|
+
const lead = this.leadCellCount();
|
|
3693
4439
|
for (const td of Array.from(tr.cells)) {
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
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:
|
|
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
|
-
/**
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
3949
|
-
|
|
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, {
|
|
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,
|
|
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 ||
|
|
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
|