@nexterajs/ui 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -34
- package/fesm2022/nexterajs-ui-charts.mjs +1096 -0
- package/fesm2022/nexterajs-ui-charts.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-core.mjs +652 -12
- package/fesm2022/nexterajs-ui-core.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-display.mjs +1142 -0
- package/fesm2022/nexterajs-ui-display.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-dropdowns.mjs +321 -33
- package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-file-uploader.mjs +572 -10
- package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-forms.mjs +2027 -136
- package/fesm2022/nexterajs-ui-forms.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-grid.mjs +1222 -188
- package/fesm2022/nexterajs-ui-grid.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-intl.mjs +103 -11
- package/fesm2022/nexterajs-ui-intl.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-navigation.mjs +1054 -0
- package/fesm2022/nexterajs-ui-navigation.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-notify.mjs +46 -44
- package/fesm2022/nexterajs-ui-notify.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-overlay.mjs +1671 -0
- package/fesm2022/nexterajs-ui-overlay.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-pivot.mjs +51 -26
- package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-shell.mjs +542 -0
- package/fesm2022/nexterajs-ui-shell.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-tree-view.mjs +5 -2
- package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-validation.mjs +42 -11
- package/fesm2022/nexterajs-ui-validation.mjs.map +1 -1
- package/fesm2022/nexterajs-ui.mjs +5 -0
- package/fesm2022/nexterajs-ui.mjs.map +1 -1
- package/package.json +33 -2
- package/styles/_dropdown.scss +5 -5
- package/styles/_editor.scss +52 -17
- package/styles/_mixins.scss +1 -1
- package/styles/_scrollbar.scss +5 -5
- package/styles/_status.scss +21 -0
- package/styles/_toast.scss +14 -14
- package/styles/_tokens.scss +420 -3
- package/styles/_widget.scss +14 -3
- package/styles/index.scss +6 -4
- package/types/nexterajs-ui-charts.d.ts +446 -0
- package/types/nexterajs-ui-core.d.ts +387 -22
- package/types/nexterajs-ui-display.d.ts +720 -0
- package/types/nexterajs-ui-dropdowns.d.ts +191 -48
- package/types/nexterajs-ui-file-uploader.d.ts +236 -7
- package/types/nexterajs-ui-forms.d.ts +675 -30
- package/types/nexterajs-ui-grid.d.ts +448 -93
- package/types/nexterajs-ui-intl.d.ts +39 -4
- package/types/nexterajs-ui-navigation.d.ts +531 -0
- package/types/nexterajs-ui-notify.d.ts +4 -2
- package/types/nexterajs-ui-overlay.d.ts +703 -0
- package/types/nexterajs-ui-pivot.d.ts +56 -48
- package/types/nexterajs-ui-shell.d.ts +294 -0
- package/types/nexterajs-ui-tree-view.d.ts +6 -3
- package/types/nexterajs-ui-validation.d.ts +20 -11
- package/types/nexterajs-ui.d.ts +5 -0
|
@@ -1,12 +1,15 @@
|
|
|
1
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, ViewEncapsulation, ChangeDetectionStrategy, Component, contentChildren, contentChild, computed, signal, output, viewChild, Directive, model,
|
|
3
|
+
import { input, ViewEncapsulation, ChangeDetectionStrategy, Component, contentChildren, contentChild, computed, signal, inject, output, viewChild, Directive, model, ElementRef, Injector, numberAttribute, booleanAttribute, linkedSignal, afterNextRender, ViewContainerRef, NgZone, DestroyRef, EventEmitter, untracked, afterRenderEffect, Output } from '@angular/core';
|
|
4
4
|
import { NgTemplateOutlet } from '@angular/common';
|
|
5
|
+
import { NxeMessageService, NXE_DEFAULT_OPTIONS, NxeTemplateDirective, nxeResolveExpr, nxeResolveText } from '@nexterajs/ui/core';
|
|
5
6
|
import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
|
|
6
7
|
import { nxePane } from '@nexterajs/ui/styling';
|
|
7
8
|
import { Workbook, nxeSaveAs } from '@nexterajs/ui/excel';
|
|
9
|
+
import { isObservable, debounce, of, timer } from 'rxjs';
|
|
8
10
|
import { Overlay } from '@angular/cdk/overlay';
|
|
9
|
-
import {
|
|
11
|
+
import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
12
|
+
import { DateTime, Info } from 'luxon';
|
|
10
13
|
|
|
11
14
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
12
15
|
// Shared value-formatting helpers for the nxe grid family — the single source of truth for how
|
|
@@ -196,7 +199,7 @@ function nxeNumberValue(raw) {
|
|
|
196
199
|
* JS number meets any valid `format`, whatever the column declared. So a number in an untyped
|
|
197
200
|
* or string column still takes its mask, and a number column honours object formats as well.
|
|
198
201
|
*/
|
|
199
|
-
function nxeDefaultFormat(dataType, format, raw, separators = NXE_EN_US_SEPARATORS) {
|
|
202
|
+
function nxeDefaultFormat(dataType, format, raw, separators = NXE_EN_US_SEPARATORS, locale) {
|
|
200
203
|
if (raw == null || raw === '') {
|
|
201
204
|
return '';
|
|
202
205
|
}
|
|
@@ -207,7 +210,7 @@ function nxeDefaultFormat(dataType, format, raw, separators = NXE_EN_US_SEPARATO
|
|
|
207
210
|
}
|
|
208
211
|
case 'date': {
|
|
209
212
|
const dt = parseDateValue(raw);
|
|
210
|
-
return dt ? formatDisplay(dt, nxeDatePattern(format)) : String(raw);
|
|
213
|
+
return dt ? formatDisplay(dt, nxeDatePattern(format), locale) : String(raw);
|
|
211
214
|
}
|
|
212
215
|
case 'boolean':
|
|
213
216
|
return raw ? '✓' : '';
|
|
@@ -898,12 +901,18 @@ function nxeHfKey(raw, isDate) {
|
|
|
898
901
|
}
|
|
899
902
|
return raw;
|
|
900
903
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
+
const NXE_HF_ENGLISH_LABELS = {
|
|
905
|
+
blanks: () => '(Blanks)',
|
|
906
|
+
yes: () => 'Yes',
|
|
907
|
+
no: () => 'No',
|
|
908
|
+
month: (month) => [
|
|
904
909
|
'January', 'February', 'March', 'April', 'May', 'June',
|
|
905
910
|
'July', 'August', 'September', 'October', 'November', 'December',
|
|
906
|
-
]
|
|
911
|
+
][month - 1],
|
|
912
|
+
};
|
|
913
|
+
class NxeHeaderFilterModel {
|
|
914
|
+
source;
|
|
915
|
+
labels;
|
|
907
916
|
/** The column whose popup is open (`null` = closed). */
|
|
908
917
|
column = signal(null, /* @ts-ignore */
|
|
909
918
|
...(ngDevMode ? [{ debugName: "column" }] : /* istanbul ignore next */ []));
|
|
@@ -914,8 +923,13 @@ class NxeHeaderFilterModel {
|
|
|
914
923
|
...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
|
|
915
924
|
expanded = signal(new Set(), /* @ts-ignore */
|
|
916
925
|
...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
917
|
-
constructor(source) {
|
|
926
|
+
constructor(source, labels = NXE_HF_ENGLISH_LABELS) {
|
|
918
927
|
this.source = source;
|
|
928
|
+
this.labels = labels;
|
|
929
|
+
}
|
|
930
|
+
/** The popup's wording for a boolean bucket. */
|
|
931
|
+
boolText(label) {
|
|
932
|
+
return label === null ? null : label === 'Yes' ? this.labels.yes() : this.labels.no();
|
|
919
933
|
}
|
|
920
934
|
/** This column's header-filter key for a raw value. */
|
|
921
935
|
key(column, raw) {
|
|
@@ -940,8 +954,8 @@ class NxeHeaderFilterModel {
|
|
|
940
954
|
key,
|
|
941
955
|
raw,
|
|
942
956
|
text: key === NXE_HF_BLANKS
|
|
943
|
-
?
|
|
944
|
-
: (this.source.boolLabel(col, raw) ?? this.source.displayText(col, row)),
|
|
957
|
+
? this.labels.blanks()
|
|
958
|
+
: (this.boolText(this.source.boolLabel(col, raw)) ?? this.source.displayText(col, row)),
|
|
945
959
|
});
|
|
946
960
|
}
|
|
947
961
|
}
|
|
@@ -999,7 +1013,7 @@ class NxeHeaderFilterModel {
|
|
|
999
1013
|
}
|
|
1000
1014
|
const out = [];
|
|
1001
1015
|
if (hasBlanks) {
|
|
1002
|
-
out.push({ label:
|
|
1016
|
+
out.push({ label: this.labels.blanks(), key: 'blanks', children: [], leafKeys: [NXE_HF_BLANKS] });
|
|
1003
1017
|
}
|
|
1004
1018
|
const byYear = new Map();
|
|
1005
1019
|
for (const day of [...days].sort()) {
|
|
@@ -1018,7 +1032,7 @@ class NxeHeaderFilterModel {
|
|
|
1018
1032
|
const monthNodes = [];
|
|
1019
1033
|
for (const [month, dayKeys] of months) {
|
|
1020
1034
|
monthNodes.push({
|
|
1021
|
-
label:
|
|
1035
|
+
label: this.labels.month(month),
|
|
1022
1036
|
key: `m:${year}-${month}`,
|
|
1023
1037
|
children: dayKeys.map((d) => ({
|
|
1024
1038
|
label: String(Number(d.slice(8, 10))),
|
|
@@ -1190,6 +1204,8 @@ class NxeHeaderFilterModel {
|
|
|
1190
1204
|
* the panel itself never touches the host's filter state.
|
|
1191
1205
|
*/
|
|
1192
1206
|
class NxeHeaderFilterPanelComponent {
|
|
1207
|
+
/** The library's runtime-switchable messages. */
|
|
1208
|
+
messages = inject(NxeMessageService);
|
|
1193
1209
|
model = input.required(/* @ts-ignore */
|
|
1194
1210
|
...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
|
|
1195
1211
|
/** Live predicate (re-read every CD, like the grid template did) — a mid-open config
|
|
@@ -1212,11 +1228,11 @@ class NxeHeaderFilterPanelComponent {
|
|
|
1212
1228
|
this.searchInput()?.nativeElement.focus();
|
|
1213
1229
|
}
|
|
1214
1230
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeHeaderFilterPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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 });
|
|
1231
|
+
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 [attr.placeholder]=\"messages.t('common.search')\"\r\n [attr.aria-label]=\"messages.t('common.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 [attr.aria-label]=\"messages.t('common.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>{{ messages.t('common.selectAll') }}</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()\">{{ messages.t('common.ok') }}</button>\r\n <button type=\"button\" class=\"nxe-hf-btn\" (click)=\"cancelled.emit()\">{{ messages.t('common.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:var(--nxe-ui-accent-faded, rgba(51, 122, 183, .4))}.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:var(--nxe-ui-hover-bg, rgba(0, 0, 0, .04))}.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 });
|
|
1216
1232
|
}
|
|
1217
1233
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeHeaderFilterPanelComponent, decorators: [{
|
|
1218
1234
|
type: Component,
|
|
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"] }]
|
|
1235
|
+
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 [attr.placeholder]=\"messages.t('common.search')\"\r\n [attr.aria-label]=\"messages.t('common.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 [attr.aria-label]=\"messages.t('common.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>{{ messages.t('common.selectAll') }}</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()\">{{ messages.t('common.ok') }}</button>\r\n <button type=\"button\" class=\"nxe-hf-btn\" (click)=\"cancelled.emit()\">{{ messages.t('common.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:var(--nxe-ui-accent-faded, rgba(51, 122, 183, .4))}.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:var(--nxe-ui-hover-bg, rgba(0, 0, 0, .04))}.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
1236
|
}], 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 }] }] } });
|
|
1221
1237
|
|
|
1222
1238
|
/** Create + attach the header-filter popup; the caller keeps the ref to detach/dispose. */
|
|
@@ -1427,7 +1443,7 @@ class NxePagerComponent {
|
|
|
1427
1443
|
infoText = input(undefined, /* @ts-ignore */
|
|
1428
1444
|
...(ngDevMode ? [{ debugName: "infoText" }] : /* istanbul ignore next */ []));
|
|
1429
1445
|
/** dx `pager.label`: the pager's accessible name. */
|
|
1430
|
-
label = input(
|
|
1446
|
+
label = input(undefined, /* @ts-ignore */
|
|
1431
1447
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1432
1448
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxePagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1433
1449
|
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 });
|
|
@@ -1672,6 +1688,476 @@ async function exportDataGrid(opts) {
|
|
|
1672
1688
|
}
|
|
1673
1689
|
}
|
|
1674
1690
|
|
|
1691
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1692
|
+
/** The default page size of a source (and of a remote grid with no `nxe-paging`). */
|
|
1693
|
+
const NXE_REMOTE_DEFAULT_PAGE_SIZE = 20;
|
|
1694
|
+
/** Structural equality of two queries; what keeps a re-stated, unchanged query from reloading. */
|
|
1695
|
+
function nxeSameQuery(a, b) {
|
|
1696
|
+
return (a.pageIndex === b.pageIndex &&
|
|
1697
|
+
a.pageSize === b.pageSize &&
|
|
1698
|
+
JSON.stringify(a.sort) === JSON.stringify(b.sort) &&
|
|
1699
|
+
JSON.stringify(a.filter) === JSON.stringify(b.filter));
|
|
1700
|
+
}
|
|
1701
|
+
/** Builds the load options for a query. */
|
|
1702
|
+
function nxeLoadOptions(query) {
|
|
1703
|
+
const pageSize = Math.max(1, Math.floor(query.pageSize));
|
|
1704
|
+
const pageIndex = Math.max(0, Math.floor(query.pageIndex));
|
|
1705
|
+
return {
|
|
1706
|
+
pageIndex,
|
|
1707
|
+
pageSize,
|
|
1708
|
+
skip: pageIndex * pageSize,
|
|
1709
|
+
take: pageSize,
|
|
1710
|
+
sort: query.sort.map((s) => ({ selector: s.selector, desc: s.desc })),
|
|
1711
|
+
filter: query.filter,
|
|
1712
|
+
requireTotalCount: true,
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
const FILTER_OPERATORS = new Set([
|
|
1716
|
+
'=',
|
|
1717
|
+
'<>',
|
|
1718
|
+
'<',
|
|
1719
|
+
'<=',
|
|
1720
|
+
'>',
|
|
1721
|
+
'>=',
|
|
1722
|
+
'contains',
|
|
1723
|
+
'notcontains',
|
|
1724
|
+
'startswith',
|
|
1725
|
+
'endswith',
|
|
1726
|
+
]);
|
|
1727
|
+
function isCondition(expr) {
|
|
1728
|
+
return (Array.isArray(expr) &&
|
|
1729
|
+
expr.length === 3 &&
|
|
1730
|
+
typeof expr[0] === 'string' &&
|
|
1731
|
+
typeof expr[1] === 'string' &&
|
|
1732
|
+
FILTER_OPERATORS.has(expr[1]));
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Flattens the AND-chain a grid builds into simple conditions, for endpoints that take a flat
|
|
1736
|
+
* parameter list (`status=sent,confirmed&supplierId=123`). An OR of `=` on one field becomes one
|
|
1737
|
+
* `in` condition. `exact` is `false` when part of the expression could not be flattened (a `!`, or
|
|
1738
|
+
* an OR across fields); those parts are left out of `conditions`, so an adapter that cannot take
|
|
1739
|
+
* `exact: false` should send the expression as JSON instead.
|
|
1740
|
+
*/
|
|
1741
|
+
function nxeFlattenFilter(filter) {
|
|
1742
|
+
const conditions = [];
|
|
1743
|
+
let exact = true;
|
|
1744
|
+
const walk = (expr) => {
|
|
1745
|
+
if (isCondition(expr)) {
|
|
1746
|
+
conditions.push({ field: expr[0], operator: expr[1], value: expr[2] });
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
if (Array.isArray(expr) && expr.length >= 3 && expr.length % 2 === 1) {
|
|
1750
|
+
const connectors = new Set(expr.filter((_, i) => i % 2 === 1));
|
|
1751
|
+
if (connectors.size === 1 && connectors.has('and')) {
|
|
1752
|
+
for (let i = 0; i < expr.length; i += 2) {
|
|
1753
|
+
walk(expr[i]);
|
|
1754
|
+
}
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (connectors.size === 1 && connectors.has('or')) {
|
|
1758
|
+
const parts = expr.filter((_, i) => i % 2 === 0);
|
|
1759
|
+
if (parts.every((p) => isCondition(p) && p[1] === '=' && p[0] === parts[0][0])) {
|
|
1760
|
+
conditions.push({
|
|
1761
|
+
field: parts[0][0],
|
|
1762
|
+
operator: 'in',
|
|
1763
|
+
value: parts.map((p) => p[2]),
|
|
1764
|
+
});
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
exact = false;
|
|
1770
|
+
};
|
|
1771
|
+
if (filter && filter.length) {
|
|
1772
|
+
walk(filter);
|
|
1773
|
+
}
|
|
1774
|
+
return { conditions, exact };
|
|
1775
|
+
}
|
|
1776
|
+
function nxeFilterJoin(connector, parts) {
|
|
1777
|
+
const present = parts.filter((p) => !!p && p.length > 0);
|
|
1778
|
+
if (present.length === 0) {
|
|
1779
|
+
return null;
|
|
1780
|
+
}
|
|
1781
|
+
if (present.length === 1) {
|
|
1782
|
+
return present[0];
|
|
1783
|
+
}
|
|
1784
|
+
return present.flatMap((p, i) => (i === 0 ? [p] : [connector, p]));
|
|
1785
|
+
}
|
|
1786
|
+
/** Combines expressions with AND; `null` and empty ones are dropped, a single one is returned as is. */
|
|
1787
|
+
function nxeFilterAnd(...parts) {
|
|
1788
|
+
return nxeFilterJoin('and', parts);
|
|
1789
|
+
}
|
|
1790
|
+
/** Combines expressions with OR, as {@link nxeFilterAnd} does with AND. */
|
|
1791
|
+
function nxeFilterOr(...parts) {
|
|
1792
|
+
return nxeFilterJoin('or', parts);
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* A server-backed list for `nxe-data-grid` and `nxe-paginator`. It holds one page at a time:
|
|
1796
|
+
* the grid (or the app) states a query, the source asks the server for exactly that page, and the
|
|
1797
|
+
* result lands in signals.
|
|
1798
|
+
*
|
|
1799
|
+
* ```ts
|
|
1800
|
+
* readonly orders = new NxeRemoteDataSource<Order>(
|
|
1801
|
+
* (o, { signal }) => this.http.get<OrderList>('/api/orders', { params: toParams(o) }).pipe(map(toResult)),
|
|
1802
|
+
* { key: 'id' },
|
|
1803
|
+
* );
|
|
1804
|
+
* ```
|
|
1805
|
+
*
|
|
1806
|
+
* A request still in flight is aborted (its `AbortSignal` fires, or its observable is
|
|
1807
|
+
* unsubscribed) when a newer one starts, and a late response that slips past is ignored, so the
|
|
1808
|
+
* rows on screen always belong to the latest query.
|
|
1809
|
+
*
|
|
1810
|
+
* The source is lazy: nothing loads until {@link setOptions} or {@link reload} is called, which a
|
|
1811
|
+
* grid bound to it does on its own.
|
|
1812
|
+
*/
|
|
1813
|
+
class NxeRemoteDataSource {
|
|
1814
|
+
loadFn;
|
|
1815
|
+
/** The row key field, when the source was given one. */
|
|
1816
|
+
key;
|
|
1817
|
+
/** Milliseconds a grid waits after the last filter-row keystroke before asking. */
|
|
1818
|
+
filterDebounce;
|
|
1819
|
+
itemsState = signal([], /* @ts-ignore */
|
|
1820
|
+
...(ngDevMode ? [{ debugName: "itemsState" }] : /* istanbul ignore next */ []));
|
|
1821
|
+
totalCountState = signal(0, /* @ts-ignore */
|
|
1822
|
+
...(ngDevMode ? [{ debugName: "totalCountState" }] : /* istanbul ignore next */ []));
|
|
1823
|
+
loadingState = signal(false, /* @ts-ignore */
|
|
1824
|
+
...(ngDevMode ? [{ debugName: "loadingState" }] : /* istanbul ignore next */ []));
|
|
1825
|
+
errorState = signal(null, /* @ts-ignore */
|
|
1826
|
+
...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
|
|
1827
|
+
loadedState = signal(false, /* @ts-ignore */
|
|
1828
|
+
...(ngDevMode ? [{ debugName: "loadedState" }] : /* istanbul ignore next */ []));
|
|
1829
|
+
queryState;
|
|
1830
|
+
optionsState;
|
|
1831
|
+
/** The rows of the current page. Kept (not cleared) while the next page loads. */
|
|
1832
|
+
items = this.itemsState.asReadonly();
|
|
1833
|
+
/** Rows matching the filter across every page, as the last successful response said. */
|
|
1834
|
+
totalCount = this.totalCountState.asReadonly();
|
|
1835
|
+
/** A request is in flight. */
|
|
1836
|
+
loading = this.loadingState.asReadonly();
|
|
1837
|
+
/** The last request's failure, or `null` after a success or while a retry runs. */
|
|
1838
|
+
error = this.errorState.asReadonly();
|
|
1839
|
+
/** A response has arrived at least once. */
|
|
1840
|
+
loaded = this.loadedState.asReadonly();
|
|
1841
|
+
/** The query and the `skip`/`take` of the page the next (or current) request uses. */
|
|
1842
|
+
options;
|
|
1843
|
+
started = false;
|
|
1844
|
+
seq = 0;
|
|
1845
|
+
controller = null;
|
|
1846
|
+
unsubscribe = null;
|
|
1847
|
+
constructor(loadFn, config = {}) {
|
|
1848
|
+
this.loadFn = loadFn;
|
|
1849
|
+
this.key = config.key;
|
|
1850
|
+
this.filterDebounce = config.filterDebounce ?? 300;
|
|
1851
|
+
const initial = {
|
|
1852
|
+
pageIndex: config.initial?.pageIndex ?? 0,
|
|
1853
|
+
pageSize: config.initial?.pageSize ?? NXE_REMOTE_DEFAULT_PAGE_SIZE,
|
|
1854
|
+
sort: config.initial?.sort ?? [],
|
|
1855
|
+
filter: config.initial?.filter ?? null,
|
|
1856
|
+
};
|
|
1857
|
+
this.queryState = signal(initial, /* @ts-ignore */
|
|
1858
|
+
...(ngDevMode ? [{ debugName: "queryState" }] : /* istanbul ignore next */ []));
|
|
1859
|
+
this.optionsState = signal(nxeLoadOptions(initial), /* @ts-ignore */
|
|
1860
|
+
...(ngDevMode ? [{ debugName: "optionsState" }] : /* istanbul ignore next */ []));
|
|
1861
|
+
this.options = this.optionsState.asReadonly();
|
|
1862
|
+
}
|
|
1863
|
+
/**
|
|
1864
|
+
* States a new query (any of its parts) and loads it. Nothing happens when it is the query
|
|
1865
|
+
* already stated, so a caller can re-state its inputs freely. The first call starts the source.
|
|
1866
|
+
*/
|
|
1867
|
+
setOptions(patch) {
|
|
1868
|
+
const current = this.queryState();
|
|
1869
|
+
const next = {
|
|
1870
|
+
pageIndex: patch.pageIndex ?? current.pageIndex,
|
|
1871
|
+
pageSize: patch.pageSize ?? current.pageSize,
|
|
1872
|
+
sort: patch.sort ?? current.sort,
|
|
1873
|
+
filter: patch.filter === undefined ? current.filter : patch.filter,
|
|
1874
|
+
};
|
|
1875
|
+
if (this.started && nxeSameQuery(current, next)) {
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
this.queryState.set(next);
|
|
1879
|
+
this.optionsState.set(nxeLoadOptions(next));
|
|
1880
|
+
this.run();
|
|
1881
|
+
}
|
|
1882
|
+
/** Loads the current query again (after an edit, or as a retry). */
|
|
1883
|
+
reload() {
|
|
1884
|
+
this.run();
|
|
1885
|
+
}
|
|
1886
|
+
/** Aborts the request in flight and ignores its response. */
|
|
1887
|
+
cancel() {
|
|
1888
|
+
this.seq++;
|
|
1889
|
+
this.abort();
|
|
1890
|
+
this.loadingState.set(false);
|
|
1891
|
+
}
|
|
1892
|
+
abort() {
|
|
1893
|
+
this.controller?.abort();
|
|
1894
|
+
this.controller = null;
|
|
1895
|
+
this.unsubscribe?.();
|
|
1896
|
+
this.unsubscribe = null;
|
|
1897
|
+
}
|
|
1898
|
+
run() {
|
|
1899
|
+
this.started = true;
|
|
1900
|
+
this.abort();
|
|
1901
|
+
const seq = ++this.seq;
|
|
1902
|
+
const controller = new AbortController();
|
|
1903
|
+
this.controller = controller;
|
|
1904
|
+
this.loadingState.set(true);
|
|
1905
|
+
this.errorState.set(null);
|
|
1906
|
+
const options = nxeLoadOptions(this.queryState());
|
|
1907
|
+
let pending;
|
|
1908
|
+
try {
|
|
1909
|
+
const result = this.loadFn(options, { signal: controller.signal });
|
|
1910
|
+
pending = isObservable(result) ? this.fromObservable(result, controller.signal) : Promise.resolve(result);
|
|
1911
|
+
}
|
|
1912
|
+
catch (error) {
|
|
1913
|
+
pending = Promise.reject(error);
|
|
1914
|
+
}
|
|
1915
|
+
pending.then((result) => {
|
|
1916
|
+
if (seq !== this.seq) {
|
|
1917
|
+
return; // superseded: a newer query owns the signals now
|
|
1918
|
+
}
|
|
1919
|
+
const page = Array.isArray(result)
|
|
1920
|
+
? { data: result, totalCount: result.length }
|
|
1921
|
+
: result;
|
|
1922
|
+
const data = page.data ?? [];
|
|
1923
|
+
this.itemsState.set(data);
|
|
1924
|
+
this.totalCountState.set(Number.isFinite(page.totalCount) ? page.totalCount : data.length);
|
|
1925
|
+
this.loadedState.set(true);
|
|
1926
|
+
this.loadingState.set(false);
|
|
1927
|
+
}, (error) => {
|
|
1928
|
+
if (seq !== this.seq) {
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
this.itemsState.set([]);
|
|
1932
|
+
this.errorState.set(error ?? new Error('Load failed'));
|
|
1933
|
+
this.loadingState.set(false);
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
/** The last value an observable emits; rejects when it errors or completes empty. */
|
|
1937
|
+
fromObservable(source, abortSignal) {
|
|
1938
|
+
return new Promise((resolve, reject) => {
|
|
1939
|
+
let last = null;
|
|
1940
|
+
const subscription = source.subscribe({
|
|
1941
|
+
next: (value) => (last = { value }),
|
|
1942
|
+
error: reject,
|
|
1943
|
+
complete: () => (last ? resolve(last.value) : reject(new Error('Load completed without a value'))),
|
|
1944
|
+
});
|
|
1945
|
+
this.unsubscribe = () => subscription.unsubscribe();
|
|
1946
|
+
abortSignal.addEventListener('abort', () => subscription.unsubscribe(), { once: true });
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
/** True for an object with a `load` function that is not an array or a {@link NxeRemoteDataSource}. */
|
|
1951
|
+
function nxeIsStoreLike(value) {
|
|
1952
|
+
return (!!value &&
|
|
1953
|
+
typeof value === 'object' &&
|
|
1954
|
+
!Array.isArray(value) &&
|
|
1955
|
+
!(value instanceof NxeRemoteDataSource) &&
|
|
1956
|
+
typeof value.load === 'function');
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
/**
|
|
1960
|
+
* The pager's page list and info text, as DevExtreme 24.2.3's pagination builds them.
|
|
1961
|
+
*
|
|
1962
|
+
* Pure functions, so the grid holds the only state (the sliding window, carried between renders
|
|
1963
|
+
* by a linkedSignal) and the rules can be specced without rendering a grid.
|
|
1964
|
+
*/
|
|
1965
|
+
/** dx `maxPagesCount` (grid_core/pager/m_pager.js): up to this many pages are all listed. */
|
|
1966
|
+
const NXE_PAGER_MAX_PAGES = 10;
|
|
1967
|
+
/** dx `PAGES_LIMITER` (pagination/pages/large.js): the pages kept around the current one. */
|
|
1968
|
+
const WINDOW = 4;
|
|
1969
|
+
/** dx's `dxPagination-infoText` message. */
|
|
1970
|
+
const NXE_PAGER_INFO_TEXT = 'Page {0} of {1} ({2} items)';
|
|
1971
|
+
const pageItem = (page) => ({ key: String(page), page, separator: false });
|
|
1972
|
+
const separatorItem = (key) => ({ key, page: -1, separator: true });
|
|
1973
|
+
/**
|
|
1974
|
+
* The page list for `pageIndex` of `pageCount`, given the previous render's window.
|
|
1975
|
+
*
|
|
1976
|
+
* dx `PagesLarge` (pagination/pages/large.js). Ten pages or fewer are all listed. Beyond that the
|
|
1977
|
+
* list is the first page, a window of four, the last page, and a `. . .` separator wherever pages
|
|
1978
|
+
* are skipped. The window stays put while the new page is one of its inner pages (so clicking
|
|
1979
|
+
* through it does not scroll the numbers under the pointer), and otherwise moves so the page
|
|
1980
|
+
* lands one in from its edge.
|
|
1981
|
+
*/
|
|
1982
|
+
function nxePagerWindow(pageCount, pageIndex, prev) {
|
|
1983
|
+
const slide = prev?.window ?? [];
|
|
1984
|
+
const reuse = prev?.reuse ?? [];
|
|
1985
|
+
if (pageCount <= NXE_PAGER_MAX_PAGES) {
|
|
1986
|
+
// dx leaves its window state untouched here, so it resumes if the count grows again.
|
|
1987
|
+
return { window: slide, reuse, items: Array.from({ length: pageCount }, (_, i) => pageItem(i)) };
|
|
1988
|
+
}
|
|
1989
|
+
const last = pageCount - 1;
|
|
1990
|
+
const reusable = reuse.length > 0 && reuse[reuse.length - 1] < last && reuse.includes(pageIndex);
|
|
1991
|
+
let next = slide;
|
|
1992
|
+
let nextReuse = reuse;
|
|
1993
|
+
if (!reusable) {
|
|
1994
|
+
let start;
|
|
1995
|
+
if (pageIndex === slide[0]) {
|
|
1996
|
+
start = pageIndex - 1;
|
|
1997
|
+
}
|
|
1998
|
+
else if (pageIndex === slide[slide.length - 1]) {
|
|
1999
|
+
start = pageIndex + 2 - WINDOW;
|
|
2000
|
+
}
|
|
2001
|
+
else if (pageIndex < WINDOW) {
|
|
2002
|
+
start = 1;
|
|
2003
|
+
}
|
|
2004
|
+
else if (pageIndex >= pageCount - WINDOW) {
|
|
2005
|
+
start = pageCount - WINDOW - 1;
|
|
2006
|
+
}
|
|
2007
|
+
else {
|
|
2008
|
+
start = pageIndex - 1;
|
|
2009
|
+
}
|
|
2010
|
+
// Only a window left over from a different page count can land outside 1..last-4; dx
|
|
2011
|
+
// would list pages that do not exist, so keep it inside the list.
|
|
2012
|
+
start = Math.min(Math.max(start, 1), pageCount - WINDOW - 1);
|
|
2013
|
+
next = Array.from({ length: WINDOW }, (_, i) => start + i);
|
|
2014
|
+
}
|
|
2015
|
+
const start = next[0];
|
|
2016
|
+
const kind = start === 1 ? 'high' : start + WINDOW === last ? 'low' : 'both';
|
|
2017
|
+
if (!reusable) {
|
|
2018
|
+
nextReuse = kind === 'both' ? next.slice(1, -1) : kind === 'high' ? next.slice(0, -1) : next.slice(1);
|
|
2019
|
+
}
|
|
2020
|
+
const items = [pageItem(0)];
|
|
2021
|
+
if (kind !== 'high') {
|
|
2022
|
+
items.push(separatorItem('low'));
|
|
2023
|
+
}
|
|
2024
|
+
items.push(...next.map(pageItem));
|
|
2025
|
+
if (kind !== 'low') {
|
|
2026
|
+
items.push(separatorItem('high'));
|
|
2027
|
+
}
|
|
2028
|
+
items.push(pageItem(last));
|
|
2029
|
+
return { window: next, reuse: nextReuse, items };
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* dx's info text: `{0}` is the 1-based page, `{1}` the page count and `{2}` the item count
|
|
2033
|
+
* (pagination/info.js, formatted by core/utils/string `format`).
|
|
2034
|
+
*/
|
|
2035
|
+
function nxePagerInfoText(template, pageIndex, pageCount, itemCount) {
|
|
2036
|
+
const values = [String(pageIndex + 1), String(pageCount), String(itemCount)];
|
|
2037
|
+
return template.replace(/\{(\d+)\}/g, (match, i) => values[Number(i)] ?? match);
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* dx `getPageSizes` (grid_core/pager/m_pager.js): an explicit list as given; `'auto'` offers half,
|
|
2041
|
+
* the same and double the configured page size, and nothing when that size is 1 or less.
|
|
2042
|
+
*/
|
|
2043
|
+
function nxePagerPageSizes(allowed, pageSize) {
|
|
2044
|
+
if (Array.isArray(allowed)) {
|
|
2045
|
+
return [...allowed];
|
|
2046
|
+
}
|
|
2047
|
+
return allowed === 'auto' && pageSize > 1 ? [Math.floor(pageSize / 2), pageSize, 2 * pageSize] : [];
|
|
2048
|
+
}
|
|
2049
|
+
/** A page or item count with the locale's group separator (`100,000`, or `100.000` under `id`). */
|
|
2050
|
+
function nxePagerCount(value, separators) {
|
|
2051
|
+
return formatNumber(value, parseNumberFormat('#,##0'), separators);
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
/**
|
|
2055
|
+
* A page navigator that stands on its own: page buttons with the grid pager's sliding window,
|
|
2056
|
+
* previous/next, a page-size selector and the total. Use it beside any list, or bind it to the same
|
|
2057
|
+
* state a remote grid pages with.
|
|
2058
|
+
*
|
|
2059
|
+
* ```html
|
|
2060
|
+
* <nxe-paginator [(pageIndex)]="page" [(pageSize)]="size" [totalCount]="orders.totalCount()" />
|
|
2061
|
+
* ```
|
|
2062
|
+
*
|
|
2063
|
+
* Text comes from `NxeMessageService`, so it follows the language. The nav is a labelled
|
|
2064
|
+
* `navigation` landmark; the current page carries `aria-current="page"`. With focus on a page
|
|
2065
|
+
* button, Left/Right move to the previous/next page and Home/End to the first/last.
|
|
2066
|
+
*/
|
|
2067
|
+
class NxePaginatorComponent {
|
|
2068
|
+
messages = inject(NxeMessageService);
|
|
2069
|
+
separators = inject(NXE_NUMBER_SEPARATORS);
|
|
2070
|
+
host = inject(ElementRef);
|
|
2071
|
+
injector = inject(Injector);
|
|
2072
|
+
/** The 0-based current page. Two-way. */
|
|
2073
|
+
pageIndex = model(0, /* @ts-ignore */
|
|
2074
|
+
...(ngDevMode ? [{ debugName: "pageIndex" }] : /* istanbul ignore next */ []));
|
|
2075
|
+
/** Rows per page. Two-way; changing it returns to the first page. */
|
|
2076
|
+
pageSize = model(20, /* @ts-ignore */
|
|
2077
|
+
...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
|
|
2078
|
+
/** Rows across every page. */
|
|
2079
|
+
totalCount = input(0, { ...(ngDevMode ? { debugName: "totalCount" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
2080
|
+
/** The sizes the selector offers. The current size is always offered too. */
|
|
2081
|
+
pageSizes = input([10, 20, 50, 100], /* @ts-ignore */
|
|
2082
|
+
...(ngDevMode ? [{ debugName: "pageSizes" }] : /* istanbul ignore next */ []));
|
|
2083
|
+
showPageSizeSelector = input(true, { ...(ngDevMode ? { debugName: "showPageSizeSelector" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
2084
|
+
showInfo = input(true, { ...(ngDevMode ? { debugName: "showInfo" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
2085
|
+
/** Greys the controls out and ignores clicks, e.g. while a page loads. */
|
|
2086
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
2087
|
+
/** The navigation landmark's accessible name; default is the `grid.pager.navigation` message. */
|
|
2088
|
+
label = input(undefined, /* @ts-ignore */
|
|
2089
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
2090
|
+
t(key, ...args) {
|
|
2091
|
+
return this.messages.t(key, ...args);
|
|
2092
|
+
}
|
|
2093
|
+
pageCount = computed(() => Math.max(1, Math.ceil(Math.max(0, this.totalCount()) / Math.max(1, this.pageSize()))), /* @ts-ignore */
|
|
2094
|
+
...(ngDevMode ? [{ debugName: "pageCount" }] : /* istanbul ignore next */ []));
|
|
2095
|
+
/** The page shown: `pageIndex` held inside the page count. */
|
|
2096
|
+
currentPage = computed(() => Math.max(0, Math.min(this.pageIndex(), this.pageCount() - 1)), /* @ts-ignore */
|
|
2097
|
+
...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
|
|
2098
|
+
window = linkedSignal({ ...(ngDevMode ? { debugName: "window" } : /* istanbul ignore next */ {}), source: () => ({ count: this.pageCount(), index: this.currentPage() }),
|
|
2099
|
+
computation: (src, prev) => nxePagerWindow(src.count, src.index, prev?.value) });
|
|
2100
|
+
items = computed(() => this.window().items, /* @ts-ignore */
|
|
2101
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
2102
|
+
sizes = computed(() => {
|
|
2103
|
+
const sizes = new Set(this.pageSizes());
|
|
2104
|
+
sizes.add(this.pageSize());
|
|
2105
|
+
return [...sizes].sort((a, b) => a - b);
|
|
2106
|
+
}, /* @ts-ignore */
|
|
2107
|
+
...(ngDevMode ? [{ debugName: "sizes" }] : /* istanbul ignore next */ []));
|
|
2108
|
+
info = computed(() => this.t('grid.pager.info', nxePagerCount(this.currentPage() + 1, this.separators), nxePagerCount(this.pageCount(), this.separators), nxePagerCount(this.totalCount(), this.separators)), /* @ts-ignore */
|
|
2109
|
+
...(ngDevMode ? [{ debugName: "info" }] : /* istanbul ignore next */ []));
|
|
2110
|
+
total = computed(() => this.t('grid.pager.total', nxePagerCount(this.totalCount(), this.separators)), /* @ts-ignore */
|
|
2111
|
+
...(ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []));
|
|
2112
|
+
go(index) {
|
|
2113
|
+
if (this.disabled()) {
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
const next = Math.max(0, Math.min(index, this.pageCount() - 1));
|
|
2117
|
+
if (next !== this.pageIndex()) {
|
|
2118
|
+
this.pageIndex.set(next);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
onSizeChange(event) {
|
|
2122
|
+
const size = Number(event.target.value);
|
|
2123
|
+
if (size > 0 && size !== this.pageSize()) {
|
|
2124
|
+
this.pageSize.set(size);
|
|
2125
|
+
this.pageIndex.set(0);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
onKeydown(event) {
|
|
2129
|
+
if (!event.target.classList.contains('nxe-paginator-page')) {
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
const last = this.pageCount() - 1;
|
|
2133
|
+
const current = this.currentPage();
|
|
2134
|
+
const target = event.key === 'ArrowLeft'
|
|
2135
|
+
? current - 1
|
|
2136
|
+
: event.key === 'ArrowRight'
|
|
2137
|
+
? current + 1
|
|
2138
|
+
: event.key === 'Home'
|
|
2139
|
+
? 0
|
|
2140
|
+
: event.key === 'End'
|
|
2141
|
+
? last
|
|
2142
|
+
: null;
|
|
2143
|
+
if (target === null) {
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
event.preventDefault();
|
|
2147
|
+
this.go(target);
|
|
2148
|
+
// The page buttons are re-rendered by key, so the old one keeps focus; move it to the new page.
|
|
2149
|
+
afterNextRender(() => this.host.nativeElement
|
|
2150
|
+
.querySelector('.nxe-paginator-page[aria-current="page"]')
|
|
2151
|
+
?.focus(), { injector: this.injector });
|
|
2152
|
+
}
|
|
2153
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxePaginatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2154
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxePaginatorComponent, isStandalone: true, selector: "nxe-paginator", inputs: { pageIndex: { classPropertyName: "pageIndex", publicName: "pageIndex", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, totalCount: { classPropertyName: "totalCount", publicName: "totalCount", isSignal: true, isRequired: false, transformFunction: null }, pageSizes: { classPropertyName: "pageSizes", publicName: "pageSizes", isSignal: true, isRequired: false, transformFunction: null }, showPageSizeSelector: { classPropertyName: "showPageSizeSelector", publicName: "showPageSizeSelector", isSignal: true, isRequired: false, transformFunction: null }, showInfo: { classPropertyName: "showInfo", publicName: "showInfo", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageIndex: "pageIndexChange", pageSize: "pageSizeChange" }, host: { listeners: { "keydown": "onKeydown($event)" }, classAttribute: "nxe-paginator" }, ngImport: i0, template: "<nav class=\"nxe-paginator-nav\" [attr.aria-label]=\"label() ?? t('grid.pager.navigation')\" [attr.aria-busy]=\"disabled() ? 'true' : null\">\r\n @if (showPageSizeSelector()) {\r\n <label class=\"nxe-paginator-sizes\">\r\n <span class=\"nxe-paginator-sizes-label\">{{ t('grid.pager.pageSize') }}</span>\r\n <select class=\"nxe-paginator-size\" [disabled]=\"disabled()\" (change)=\"onSizeChange($event)\">\r\n @for (size of sizes(); track size) {\r\n <option [value]=\"size\" [selected]=\"size === pageSize()\">{{ size }}</option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n <span class=\"nxe-paginator-spacer\"></span>\r\n <span class=\"nxe-paginator-info\" role=\"status\">{{ showInfo() ? info() : total() }}</span>\r\n <div class=\"nxe-paginator-pages\">\r\n <button\r\n type=\"button\"\r\n class=\"nxe-paginator-nav-button nxe-paginator-prev\"\r\n [attr.aria-label]=\"t('grid.pager.previous')\"\r\n [attr.aria-disabled]=\"disabled() || currentPage() === 0 ? 'true' : null\"\r\n (click)=\"go(currentPage() - 1)\"></button>\r\n @for (item of items(); track item.key) {\r\n @if (item.separator) {\r\n <span class=\"nxe-paginator-separator\" aria-hidden=\"true\">. . .</span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-paginator-page\"\r\n [class.nxe-paginator-current]=\"item.page === currentPage()\"\r\n [attr.aria-label]=\"t('grid.pager.page', item.page + 1)\"\r\n [attr.aria-current]=\"item.page === currentPage() ? 'page' : null\"\r\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\r\n (click)=\"go(item.page)\">{{ item.page + 1 }}</button>\r\n }\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"nxe-paginator-nav-button nxe-paginator-next\"\r\n [attr.aria-label]=\"t('grid.pager.next')\"\r\n [attr.aria-disabled]=\"disabled() || currentPage() >= pageCount() - 1 ? 'true' : null\"\r\n (click)=\"go(currentPage() + 1)\"></button>\r\n </div>\r\n</nav>\r\n", styles: ["nxe-paginator{display:block}nxe-paginator .nxe-paginator-nav{display:flex;flex-wrap:wrap;align-items:center;gap:var(--nxe-ui-space-2, 8px) var(--nxe-ui-space-3, 12px);color:var(--nxe-ui-text, #333);font-size:var(--nxe-ui-font-size-sm, 13px);line-height:var(--nxe-ui-line-height-sm, 18px)}nxe-paginator .nxe-paginator-spacer{flex:1 1 auto}nxe-paginator .nxe-paginator-sizes{display:inline-flex;align-items:center;gap:var(--nxe-ui-space-2, 8px)}nxe-paginator .nxe-paginator-size{height:32px;padding:0 var(--nxe-ui-space-2, 8px);border:1px solid var(--nxe-ui-border, #ddd);border-radius:var(--nxe-ui-radius, 4px);background:var(--nxe-ui-surface, #fff);color:inherit;font:inherit}nxe-paginator .nxe-paginator-info{color:var(--nxe-ui-text-muted, #999)}nxe-paginator .nxe-paginator-pages{display:inline-flex;align-items:center;gap:var(--nxe-ui-space-1, 4px)}nxe-paginator .nxe-paginator-page,nxe-paginator .nxe-paginator-nav-button{min-width:32px;height:32px;padding:0 var(--nxe-ui-space-2, 8px);border:1px solid transparent;border-radius:var(--nxe-ui-radius, 4px);background:transparent;color:inherit;font:inherit;cursor:pointer}nxe-paginator .nxe-paginator-page:hover,nxe-paginator .nxe-paginator-nav-button:hover{background:var(--nxe-ui-hover-bg, rgba(0, 0, 0, .04))}nxe-paginator .nxe-paginator-page:focus-visible,nxe-paginator .nxe-paginator-nav-button:focus-visible{outline:none;box-shadow:var(--nxe-ui-focus-ring, 0 0 0 2px rgba(0, 92, 170, .5))}nxe-paginator .nxe-paginator-page[aria-disabled=true],nxe-paginator .nxe-paginator-nav-button[aria-disabled=true]{opacity:var(--nxe-ui-disabled-opacity, .5);cursor:default;pointer-events:none}nxe-paginator .nxe-paginator-current{background:var(--nxe-ui-selected-bg, rgba(0, 0, 0, .1));cursor:default}nxe-paginator .nxe-paginator-nav-button{position:relative}nxe-paginator .nxe-paginator-nav-button:before{content:\"\";position:absolute;inset:0;margin:auto;width:8px;height:8px;border-style:solid;border-color:currentColor;border-width:0 0 2px 2px}nxe-paginator .nxe-paginator-prev:before{transform:translate(2px) rotate(45deg)}nxe-paginator .nxe-paginator-next:before{transform:translate(-2px) rotate(-135deg)}nxe-paginator .nxe-paginator-separator{padding:0 var(--nxe-ui-space-1, 4px);opacity:.7}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2155
|
+
}
|
|
2156
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxePaginatorComponent, decorators: [{
|
|
2157
|
+
type: Component,
|
|
2158
|
+
args: [{ selector: 'nxe-paginator', host: { class: 'nxe-paginator', '(keydown)': 'onKeydown($event)' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<nav class=\"nxe-paginator-nav\" [attr.aria-label]=\"label() ?? t('grid.pager.navigation')\" [attr.aria-busy]=\"disabled() ? 'true' : null\">\r\n @if (showPageSizeSelector()) {\r\n <label class=\"nxe-paginator-sizes\">\r\n <span class=\"nxe-paginator-sizes-label\">{{ t('grid.pager.pageSize') }}</span>\r\n <select class=\"nxe-paginator-size\" [disabled]=\"disabled()\" (change)=\"onSizeChange($event)\">\r\n @for (size of sizes(); track size) {\r\n <option [value]=\"size\" [selected]=\"size === pageSize()\">{{ size }}</option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n <span class=\"nxe-paginator-spacer\"></span>\r\n <span class=\"nxe-paginator-info\" role=\"status\">{{ showInfo() ? info() : total() }}</span>\r\n <div class=\"nxe-paginator-pages\">\r\n <button\r\n type=\"button\"\r\n class=\"nxe-paginator-nav-button nxe-paginator-prev\"\r\n [attr.aria-label]=\"t('grid.pager.previous')\"\r\n [attr.aria-disabled]=\"disabled() || currentPage() === 0 ? 'true' : null\"\r\n (click)=\"go(currentPage() - 1)\"></button>\r\n @for (item of items(); track item.key) {\r\n @if (item.separator) {\r\n <span class=\"nxe-paginator-separator\" aria-hidden=\"true\">. . .</span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-paginator-page\"\r\n [class.nxe-paginator-current]=\"item.page === currentPage()\"\r\n [attr.aria-label]=\"t('grid.pager.page', item.page + 1)\"\r\n [attr.aria-current]=\"item.page === currentPage() ? 'page' : null\"\r\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\r\n (click)=\"go(item.page)\">{{ item.page + 1 }}</button>\r\n }\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"nxe-paginator-nav-button nxe-paginator-next\"\r\n [attr.aria-label]=\"t('grid.pager.next')\"\r\n [attr.aria-disabled]=\"disabled() || currentPage() >= pageCount() - 1 ? 'true' : null\"\r\n (click)=\"go(currentPage() + 1)\"></button>\r\n </div>\r\n</nav>\r\n", styles: ["nxe-paginator{display:block}nxe-paginator .nxe-paginator-nav{display:flex;flex-wrap:wrap;align-items:center;gap:var(--nxe-ui-space-2, 8px) var(--nxe-ui-space-3, 12px);color:var(--nxe-ui-text, #333);font-size:var(--nxe-ui-font-size-sm, 13px);line-height:var(--nxe-ui-line-height-sm, 18px)}nxe-paginator .nxe-paginator-spacer{flex:1 1 auto}nxe-paginator .nxe-paginator-sizes{display:inline-flex;align-items:center;gap:var(--nxe-ui-space-2, 8px)}nxe-paginator .nxe-paginator-size{height:32px;padding:0 var(--nxe-ui-space-2, 8px);border:1px solid var(--nxe-ui-border, #ddd);border-radius:var(--nxe-ui-radius, 4px);background:var(--nxe-ui-surface, #fff);color:inherit;font:inherit}nxe-paginator .nxe-paginator-info{color:var(--nxe-ui-text-muted, #999)}nxe-paginator .nxe-paginator-pages{display:inline-flex;align-items:center;gap:var(--nxe-ui-space-1, 4px)}nxe-paginator .nxe-paginator-page,nxe-paginator .nxe-paginator-nav-button{min-width:32px;height:32px;padding:0 var(--nxe-ui-space-2, 8px);border:1px solid transparent;border-radius:var(--nxe-ui-radius, 4px);background:transparent;color:inherit;font:inherit;cursor:pointer}nxe-paginator .nxe-paginator-page:hover,nxe-paginator .nxe-paginator-nav-button:hover{background:var(--nxe-ui-hover-bg, rgba(0, 0, 0, .04))}nxe-paginator .nxe-paginator-page:focus-visible,nxe-paginator .nxe-paginator-nav-button:focus-visible{outline:none;box-shadow:var(--nxe-ui-focus-ring, 0 0 0 2px rgba(0, 92, 170, .5))}nxe-paginator .nxe-paginator-page[aria-disabled=true],nxe-paginator .nxe-paginator-nav-button[aria-disabled=true]{opacity:var(--nxe-ui-disabled-opacity, .5);cursor:default;pointer-events:none}nxe-paginator .nxe-paginator-current{background:var(--nxe-ui-selected-bg, rgba(0, 0, 0, .1));cursor:default}nxe-paginator .nxe-paginator-nav-button{position:relative}nxe-paginator .nxe-paginator-nav-button:before{content:\"\";position:absolute;inset:0;margin:auto;width:8px;height:8px;border-style:solid;border-color:currentColor;border-width:0 0 2px 2px}nxe-paginator .nxe-paginator-prev:before{transform:translate(2px) rotate(45deg)}nxe-paginator .nxe-paginator-next:before{transform:translate(-2px) rotate(-135deg)}nxe-paginator .nxe-paginator-separator{padding:0 var(--nxe-ui-space-1, 4px);opacity:.7}\n"] }]
|
|
2159
|
+
}], propDecorators: { pageIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageIndex", required: false }] }, { type: i0.Output, args: ["pageIndexChange"] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }, { type: i0.Output, args: ["pageSizeChange"] }], totalCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalCount", required: false }] }], pageSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizes", required: false }] }], showPageSizeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageSizeSelector", required: false }] }], showInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "showInfo", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
2160
|
+
|
|
1675
2161
|
class NxeGridResizeController {
|
|
1676
2162
|
hooks;
|
|
1677
2163
|
active = null;
|
|
@@ -1720,6 +2206,17 @@ class NxeGridResizeController {
|
|
|
1720
2206
|
}
|
|
1721
2207
|
const nextColEl = nextField != null ? cols[lead + visibleIndex + 1] ?? null : null;
|
|
1722
2208
|
const nextCell = nextField != null ? headerCells[visibleIndex + 1] : null;
|
|
2209
|
+
const trailingColEl = cols[lead + headerCells.length - 1];
|
|
2210
|
+
const trailingCell = headerCells[headerCells.length - 1];
|
|
2211
|
+
const rowsView = table.closest('.nxe-datagrid-rowsview');
|
|
2212
|
+
const fill = this.hooks.mode() === 'widget' && this.hooks.fillsGrid() && trailingColEl && trailingCell && rowsView
|
|
2213
|
+
? {
|
|
2214
|
+
colEl: trailingColEl,
|
|
2215
|
+
startW: trailingCell.getBoundingClientRect().width,
|
|
2216
|
+
prevStyle: trailingColEl.style.width,
|
|
2217
|
+
roomW: rowsView.clientWidth,
|
|
2218
|
+
}
|
|
2219
|
+
: null;
|
|
1723
2220
|
this.active = {
|
|
1724
2221
|
field,
|
|
1725
2222
|
nextField,
|
|
@@ -1736,6 +2233,7 @@ class NxeGridResizeController {
|
|
|
1736
2233
|
prevTableStyle: table.style.width,
|
|
1737
2234
|
lastW: cell.getBoundingClientRect().width,
|
|
1738
2235
|
lastNextW: nextCell ? nextCell.getBoundingClientRect().width : 0,
|
|
2236
|
+
fill,
|
|
1739
2237
|
};
|
|
1740
2238
|
this.hooks.onStart();
|
|
1741
2239
|
document.body.classList.add('nxe-grid-resizing');
|
|
@@ -1762,9 +2260,22 @@ class NxeGridResizeController {
|
|
|
1762
2260
|
const min = this.hooks.minWidth();
|
|
1763
2261
|
const delta = e.clientX - a.startX;
|
|
1764
2262
|
if (this.hooks.mode() === 'widget' || !a.nextColEl) {
|
|
1765
|
-
|
|
2263
|
+
let w = Math.max(min, a.startW + delta);
|
|
2264
|
+
let tableW = a.startTableW + (w - a.startW);
|
|
2265
|
+
// fillsGrid: hand what the table comes out short of the rows view to the trailing
|
|
2266
|
+
// column now, as the commit will, so the release lands where the drag already is.
|
|
2267
|
+
if (a.fill) {
|
|
2268
|
+
const slack = Math.max(0, a.fill.roomW - tableW);
|
|
2269
|
+
if (a.fill.colEl === a.colEl) {
|
|
2270
|
+
w += slack;
|
|
2271
|
+
}
|
|
2272
|
+
else {
|
|
2273
|
+
a.fill.colEl.style.width = `${a.fill.startW + slack}px`;
|
|
2274
|
+
}
|
|
2275
|
+
tableW += slack;
|
|
2276
|
+
}
|
|
1766
2277
|
a.colEl.style.width = `${w}px`;
|
|
1767
|
-
a.tableEl.style.width = `${
|
|
2278
|
+
a.tableEl.style.width = `${tableW}px`;
|
|
1768
2279
|
a.lastW = w;
|
|
1769
2280
|
return;
|
|
1770
2281
|
}
|
|
@@ -1798,7 +2309,25 @@ class NxeGridResizeController {
|
|
|
1798
2309
|
if (a.nextField && a.lastNextW !== a.startNextW) {
|
|
1799
2310
|
changes.push({ field: a.nextField, from: a.startNextW, to: a.lastNextW });
|
|
1800
2311
|
}
|
|
1801
|
-
this.hooks.zone.run(() =>
|
|
2312
|
+
this.hooks.zone.run(() => {
|
|
2313
|
+
// The commit measures the cells as the drag left them, so it goes first.
|
|
2314
|
+
this.hooks.onCommit(changes);
|
|
2315
|
+
// Then the DOM goes back to what the [style.width] bindings last wrote. Angular only
|
|
2316
|
+
// rewrites a binding whose value changed, and a second resize often commits the same
|
|
2317
|
+
// value (the trailing column's width, a table at 100%): the drag's leftovers would stay
|
|
2318
|
+
// on screen. Restored, the next render lands the committed layout either way.
|
|
2319
|
+
this.restore(a);
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
restore(a) {
|
|
2323
|
+
a.colEl.style.width = a.prevColStyle;
|
|
2324
|
+
if (a.nextColEl) {
|
|
2325
|
+
a.nextColEl.style.width = a.prevNextStyle;
|
|
2326
|
+
}
|
|
2327
|
+
if (a.fill && a.fill.colEl !== a.colEl) {
|
|
2328
|
+
a.fill.colEl.style.width = a.fill.prevStyle;
|
|
2329
|
+
}
|
|
2330
|
+
a.tableEl.style.width = a.prevTableStyle;
|
|
1802
2331
|
}
|
|
1803
2332
|
cancel() {
|
|
1804
2333
|
const a = this.active;
|
|
@@ -1807,11 +2336,7 @@ class NxeGridResizeController {
|
|
|
1807
2336
|
return;
|
|
1808
2337
|
}
|
|
1809
2338
|
this.suppression.arm();
|
|
1810
|
-
|
|
1811
|
-
if (a.nextColEl) {
|
|
1812
|
-
a.nextColEl.style.width = a.prevNextStyle;
|
|
1813
|
-
}
|
|
1814
|
-
a.tableEl.style.width = a.prevTableStyle;
|
|
2339
|
+
this.restore(a);
|
|
1815
2340
|
this.hooks.zone.run(() => this.hooks.onCancel());
|
|
1816
2341
|
}
|
|
1817
2342
|
teardown() {
|
|
@@ -1824,98 +2349,38 @@ class NxeGridResizeController {
|
|
|
1824
2349
|
}
|
|
1825
2350
|
}
|
|
1826
2351
|
|
|
2352
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2353
|
+
/** The day after a `yyyy-MM-dd` day key. */
|
|
2354
|
+
function nxeNextDay(day) {
|
|
2355
|
+
return DateTime.fromISO(day).plus({ days: 1 }).toFormat('yyyy-MM-dd');
|
|
2356
|
+
}
|
|
1827
2357
|
/**
|
|
1828
|
-
*
|
|
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.
|
|
2358
|
+
* Recursive tree selection state per node key: `all` when the node's key or an ancestor's is in
|
|
2359
|
+
* `keys`, or when every child is `all`; `some` when only part of its subtree is; absent otherwise.
|
|
1849
2360
|
*/
|
|
1850
|
-
function
|
|
1851
|
-
const
|
|
1852
|
-
const
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
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;
|
|
2361
|
+
function nxeTreeCheckState(roots, keys) {
|
|
2362
|
+
const out = new Map();
|
|
2363
|
+
const walk = (node, forced) => {
|
|
2364
|
+
const self = forced || keys.has(node.key);
|
|
2365
|
+
let state = self ? 'all' : 'none';
|
|
2366
|
+
if (node.children.length) {
|
|
2367
|
+
let every = true;
|
|
2368
|
+
let any = false;
|
|
2369
|
+
for (const child of node.children) {
|
|
2370
|
+
const s = walk(child, self);
|
|
2371
|
+
every &&= s === 'all';
|
|
2372
|
+
any ||= s !== 'none';
|
|
2373
|
+
}
|
|
2374
|
+
state = every ? 'all' : any ? 'some' : 'none';
|
|
1874
2375
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
2376
|
+
if (state !== 'none') {
|
|
2377
|
+
out.set(node.key, state);
|
|
1877
2378
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
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] : [];
|
|
2379
|
+
return state;
|
|
2380
|
+
};
|
|
2381
|
+
roots.forEach((r) => walk(r, false));
|
|
2382
|
+
return out;
|
|
1916
2383
|
}
|
|
1917
|
-
|
|
1918
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1919
2384
|
/** Classify a rendered `<tr>` for dx's `rowType`, or null for rows dx does not report. */
|
|
1920
2385
|
function nxeRowTypeOf(tr) {
|
|
1921
2386
|
const c = tr.classList;
|
|
@@ -1993,6 +2458,11 @@ class NxeDataGridComponent {
|
|
|
1993
2458
|
defaults = inject(NXE_DEFAULT_OPTIONS);
|
|
1994
2459
|
/** Locale separators for every number this grid renders — cells, summaries and group text. */
|
|
1995
2460
|
separators = inject(NXE_NUMBER_SEPARATORS);
|
|
2461
|
+
messages = inject(NxeMessageService);
|
|
2462
|
+
/** One library message for the active language; reading it in a template re-renders on a switch. */
|
|
2463
|
+
t(key, ...args) {
|
|
2464
|
+
return this.messages.t(key, ...args);
|
|
2465
|
+
}
|
|
1996
2466
|
/** Pointer drag engine: drag-to-group, chip drag-out (ungroup), chip/column reorder. */
|
|
1997
2467
|
drag = new NxeGridDragController({
|
|
1998
2468
|
zone: this.ngZone,
|
|
@@ -2009,6 +2479,7 @@ class NxeDataGridComponent {
|
|
|
2009
2479
|
zone: this.ngZone,
|
|
2010
2480
|
host: () => this.elementRef.nativeElement,
|
|
2011
2481
|
mode: () => this.columnResizingMode(),
|
|
2482
|
+
fillsGrid: () => this.resizeFillsGrid(),
|
|
2012
2483
|
minWidth: () => this.columnMinWidth() ?? 50,
|
|
2013
2484
|
onStart: () => (this.resizeActive = true),
|
|
2014
2485
|
onCancel: () => (this.resizeActive = false),
|
|
@@ -2055,6 +2526,11 @@ class NxeDataGridComponent {
|
|
|
2055
2526
|
masterDetail = contentChild(NxeMasterDetailComponent, /* @ts-ignore */
|
|
2056
2527
|
...(ngDevMode ? [{ debugName: "masterDetail" }] : /* istanbul ignore next */ []));
|
|
2057
2528
|
// --- Inputs (mirror dx-data-grid; object-literal inputs accepted as-is for drop-in parity) ---
|
|
2529
|
+
/**
|
|
2530
|
+
* The rows: a plain array (paged, sorted and filtered here), a {@link NxeRemoteDataSource}
|
|
2531
|
+
* (paged, sorted and filtered by the server; the grid holds one page), or, with
|
|
2532
|
+
* `remoteOperations` on, a `CustomStore`-shaped `{ load, key? }`.
|
|
2533
|
+
*/
|
|
2058
2534
|
dataSource = input([], /* @ts-ignore */
|
|
2059
2535
|
...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
|
|
2060
2536
|
keyExpr = input(undefined, /* @ts-ignore */
|
|
@@ -2073,11 +2549,19 @@ class NxeDataGridComponent {
|
|
|
2073
2549
|
wordWrapEnabledInput = input(undefined, { ...(ngDevMode ? { debugName: "wordWrapEnabledInput" } : /* istanbul ignore next */ {}), alias: 'wordWrapEnabled' });
|
|
2074
2550
|
columnAutoWidthInput = input(undefined, { ...(ngDevMode ? { debugName: "columnAutoWidthInput" } : /* istanbul ignore next */ {}), alias: 'columnAutoWidth' });
|
|
2075
2551
|
rowAlternationEnabledInput = input(undefined, { ...(ngDevMode ? { debugName: "rowAlternationEnabledInput" } : /* istanbul ignore next */ {}), alias: 'rowAlternationEnabled' });
|
|
2552
|
+
resizeFillsGridInput = input(undefined, { ...(ngDevMode ? { debugName: "resizeFillsGridInput" } : /* istanbul ignore next */ {}), alias: 'resizeFillsGrid' });
|
|
2076
2553
|
/** dx resolves `nxe-tree-list` against the `treeList` slice, not `dataGrid`. */
|
|
2077
2554
|
widgetDefaults = computed(() => (this.treeMode ? this.defaults.treeList : this.defaults.dataGrid) ?? {}, /* @ts-ignore */
|
|
2078
2555
|
...(ngDevMode ? [{ debugName: "widgetDefaults" }] : /* istanbul ignore next */ []));
|
|
2079
2556
|
hoverStateEnabled = computed(() => this.hoverStateEnabledInput() ?? this.widgetDefaults().hoverStateEnabled ?? false, /* @ts-ignore */
|
|
2080
2557
|
...(ngDevMode ? [{ debugName: "hoverStateEnabled" }] : /* istanbul ignore next */ []));
|
|
2558
|
+
/**
|
|
2559
|
+
* Widget-mode resizing that never leaves a blank strip: the trailing column takes whatever a
|
|
2560
|
+
* narrowed layout leaves of the grid's width. Not dx - dx lets the table end short - so off
|
|
2561
|
+
* unless bound or set app-wide through `NXE_DEFAULT_OPTIONS`.
|
|
2562
|
+
*/
|
|
2563
|
+
resizeFillsGrid = computed(() => this.resizeFillsGridInput() ?? this.widgetDefaults().resizeFillsGrid ?? false, /* @ts-ignore */
|
|
2564
|
+
...(ngDevMode ? [{ debugName: "resizeFillsGrid" }] : /* istanbul ignore next */ []));
|
|
2081
2565
|
// The literals are DevExtreme 24.2.3's generic defaults: showColumnLines true, showRowLines
|
|
2082
2566
|
// false and wordWrapEnabled false (m_rows_view.js:1180-1182), showBorders false
|
|
2083
2567
|
// (m_grid_view.js:743). dxTreeList shares them and changes them only for the Material themes
|
|
@@ -2096,8 +2580,37 @@ class NxeDataGridComponent {
|
|
|
2096
2580
|
...(ngDevMode ? [{ debugName: "columnAutoWidth" }] : /* istanbul ignore next */ []));
|
|
2097
2581
|
rowAlternationEnabled = computed(() => this.rowAlternationEnabledInput() ?? this.widgetDefaults().rowAlternationEnabled ?? false, /* @ts-ignore */
|
|
2098
2582
|
...(ngDevMode ? [{ debugName: "rowAlternationEnabled" }] : /* istanbul ignore next */ []));
|
|
2583
|
+
/**
|
|
2584
|
+
* dx `remoteOperations`. With a `{ load }` store as the `dataSource`, any truthy value makes the
|
|
2585
|
+
* grid page, sort and filter on the server (`{ paging, sorting, filtering }` is read as all or
|
|
2586
|
+
* nothing). A {@link NxeRemoteDataSource} is remote whatever this says.
|
|
2587
|
+
*/
|
|
2099
2588
|
remoteOperations = input(false, /* @ts-ignore */
|
|
2100
2589
|
...(ngDevMode ? [{ debugName: "remoteOperations" }] : /* istanbul ignore next */ []));
|
|
2590
|
+
// Controlled state, for binding to URL query params. `undefined` = not controlled: the grid
|
|
2591
|
+
// keeps its own state. A remote grid always writes them back, so `[(pageIndex)]` follows the
|
|
2592
|
+
// user; a client-side grid writes back only the ones that are bound.
|
|
2593
|
+
/** The 0-based page. Two-way. */
|
|
2594
|
+
pageIndexModel = model(undefined, { ...(ngDevMode ? { debugName: "pageIndexModel" } : /* istanbul ignore next */ {}), alias: 'pageIndex' });
|
|
2595
|
+
/** Rows per page. Two-way. */
|
|
2596
|
+
pageSizeModel = model(undefined, { ...(ngDevMode ? { debugName: "pageSizeModel" } : /* istanbul ignore next */ {}), alias: 'pageSize' });
|
|
2597
|
+
/** The sort, `[{ selector, desc }]`. Two-way; a header click writes its single level here. */
|
|
2598
|
+
sortModel = model(undefined, { ...(ngDevMode ? { debugName: "sortModel" } : /* istanbul ignore next */ {}), alias: 'sort' });
|
|
2599
|
+
/**
|
|
2600
|
+
* A filter the application owns (a filter bar, a status tab), in {@link NxeFilterExpression}
|
|
2601
|
+
* form. A remote grid ANDs it with the filter row and header filter and sends the lot to the
|
|
2602
|
+
* server. Changing it returns the grid to the first page.
|
|
2603
|
+
*/
|
|
2604
|
+
// The annotation is explicit on purpose: the library compiles without strictNullChecks, which erases
|
|
2605
|
+
// `| null | undefined` from an inferred type, so the built .d.ts would otherwise say non-null.
|
|
2606
|
+
filter = input(undefined, /* @ts-ignore */
|
|
2607
|
+
...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
|
|
2608
|
+
/**
|
|
2609
|
+
* The options of every request a remote grid hands its source: page, size, sort and the
|
|
2610
|
+
* combined filter. One emission per distinct request, after filter-row typing settles. This is
|
|
2611
|
+
* where an application mirrors the grid's state into the URL.
|
|
2612
|
+
*/
|
|
2613
|
+
optionsChange = output();
|
|
2101
2614
|
/**
|
|
2102
2615
|
* dx `syncLookupFilterValues` — accepted, and deliberately inert. It asks dx to narrow a
|
|
2103
2616
|
* lookup column's header-filter list to the values still reachable under the other filters.
|
|
@@ -2121,7 +2634,7 @@ class NxeDataGridComponent {
|
|
|
2121
2634
|
}, /* @ts-ignore */
|
|
2122
2635
|
...(ngDevMode ? [{ debugName: "heightStyle" }] : /* istanbul ignore next */ []));
|
|
2123
2636
|
noDataTextInput = input(undefined, { ...(ngDevMode ? { debugName: "noDataTextInput" } : /* istanbul ignore next */ {}), alias: 'noDataText' });
|
|
2124
|
-
noDataText = computed(() => this.noDataTextInput() ?? this.widgetDefaults().noDataText ?? '
|
|
2637
|
+
noDataText = computed(() => this.noDataTextInput() ?? this.widgetDefaults().noDataText ?? this.messages.t('grid.noData'), /* @ts-ignore */
|
|
2125
2638
|
...(ngDevMode ? [{ debugName: "noDataText" }] : /* istanbul ignore next */ []));
|
|
2126
2639
|
loadPanel = input(undefined, /* @ts-ignore */
|
|
2127
2640
|
...(ngDevMode ? [{ debugName: "loadPanel" }] : /* istanbul ignore next */ []));
|
|
@@ -2251,6 +2764,11 @@ class NxeDataGridComponent {
|
|
|
2251
2764
|
sortByDisplayText: (col) => !!col.lookup(),
|
|
2252
2765
|
boolLabel: (col, raw) => this.hfBoolLabel(col, raw),
|
|
2253
2766
|
compare: (a, b) => this.compare(a, b),
|
|
2767
|
+
}, {
|
|
2768
|
+
blanks: () => this.messages.t('common.blanks'),
|
|
2769
|
+
yes: () => this.messages.t('common.yes'),
|
|
2770
|
+
no: () => this.messages.t('common.no'),
|
|
2771
|
+
month: (month) => Info.months('long', { locale: this.messages.locale() })[month - 1],
|
|
2254
2772
|
});
|
|
2255
2773
|
hfColumn = this.hf.column;
|
|
2256
2774
|
hfDraft = this.hf.draft;
|
|
@@ -2263,15 +2781,25 @@ class NxeDataGridComponent {
|
|
|
2263
2781
|
* input without this would satisfy an audit while doing nothing — the option would resolve
|
|
2264
2782
|
* and be ignored, which is worse than not accepting it.
|
|
2265
2783
|
*/
|
|
2266
|
-
pageIndex = linkedSignal(() =>
|
|
2784
|
+
pageIndex = linkedSignal(() => {
|
|
2785
|
+
const remote = this.remote();
|
|
2786
|
+
return (this.pageIndexModel() ??
|
|
2787
|
+
this.pagingCmp()?.pageIndex() ??
|
|
2788
|
+
(remote ? untracked(() => remote.options().pageIndex) : 0));
|
|
2789
|
+
}, /* @ts-ignore */
|
|
2267
2790
|
...(ngDevMode ? [{ debugName: "pageIndex" }] : /* istanbul ignore next */ []));
|
|
2268
|
-
userPageSize =
|
|
2791
|
+
userPageSize = linkedSignal(() => this.pageSizeModel() ?? null, /* @ts-ignore */
|
|
2269
2792
|
...(ngDevMode ? [{ debugName: "userPageSize" }] : /* istanbul ignore next */ []));
|
|
2270
2793
|
/** DevExtreme `.instance` facade — `#gridAll.instance.<method>()`. */
|
|
2271
2794
|
facade = {
|
|
2272
2795
|
getSelectedRowsData: () => this.selectedRowsData(),
|
|
2273
|
-
getSelectedRowKeys: () => this.rowKeys(this.selectedRowsData()),
|
|
2274
|
-
|
|
2796
|
+
getSelectedRowKeys: () => (this.remote() ? this.selectedRowKeys() : this.rowKeys(this.selectedRowsData())),
|
|
2797
|
+
clearSelection: () => this.deselectRows(null),
|
|
2798
|
+
deselectAll: () => this.deselectRows(this.treeMode ? null : this.displayRows()),
|
|
2799
|
+
refresh: () => {
|
|
2800
|
+
this.refreshTick.update((v) => v + 1);
|
|
2801
|
+
this.remote()?.reload();
|
|
2802
|
+
},
|
|
2275
2803
|
clearFilter: (filterName) => {
|
|
2276
2804
|
if (!filterName || filterName === 'row') {
|
|
2277
2805
|
this.filterRowState.set(new Map());
|
|
@@ -2304,7 +2832,7 @@ class NxeDataGridComponent {
|
|
|
2304
2832
|
};
|
|
2305
2833
|
}),
|
|
2306
2834
|
getVisibleRows: () => this.displayRows(),
|
|
2307
|
-
totalCount: () => this.displayRows().length,
|
|
2835
|
+
totalCount: () => this.remote()?.totalCount() ?? this.displayRows().length,
|
|
2308
2836
|
getRootNode: () => ({
|
|
2309
2837
|
key: undefined,
|
|
2310
2838
|
data: undefined,
|
|
@@ -2320,7 +2848,7 @@ class NxeDataGridComponent {
|
|
|
2320
2848
|
if (this.treeMode) {
|
|
2321
2849
|
const keyF = this.keyExpr() ?? 'id';
|
|
2322
2850
|
const all = new Map();
|
|
2323
|
-
for (const r of
|
|
2851
|
+
for (const r of this.sourceRows()) {
|
|
2324
2852
|
all.set(r[keyF], true);
|
|
2325
2853
|
}
|
|
2326
2854
|
this.expandOverride.set(all);
|
|
@@ -2330,7 +2858,7 @@ class NxeDataGridComponent {
|
|
|
2330
2858
|
const editing = this.editingCmp();
|
|
2331
2859
|
const key = editing?.editRowKey();
|
|
2332
2860
|
if (editing && key != null) {
|
|
2333
|
-
const row =
|
|
2861
|
+
const row = this.sourceRows().find((r) => this.rowKey(r) === key);
|
|
2334
2862
|
if (row) {
|
|
2335
2863
|
this.saveRowEdit(row, new Event('save'));
|
|
2336
2864
|
}
|
|
@@ -2371,6 +2899,18 @@ class NxeDataGridComponent {
|
|
|
2371
2899
|
/** Guards against a handler that schedules another render re-entering the walk. */
|
|
2372
2900
|
preparedDepth = 0;
|
|
2373
2901
|
constructor() {
|
|
2902
|
+
// Server-side data: the request is a computed over exactly the state it is made of, and
|
|
2903
|
+
// this stream (explicitly those signals, nothing a callee reads) hands each distinct one to
|
|
2904
|
+
// the source, waiting out filter-row typing first.
|
|
2905
|
+
toObservable(this.remoteRequest)
|
|
2906
|
+
.pipe(debounce(() => {
|
|
2907
|
+
if (!this.remoteTyping) {
|
|
2908
|
+
return of(0);
|
|
2909
|
+
}
|
|
2910
|
+
this.remoteTyping = false;
|
|
2911
|
+
return timer(this.remote()?.filterDebounce ?? 0);
|
|
2912
|
+
}), takeUntilDestroyed(this.destroyRef))
|
|
2913
|
+
.subscribe((request) => request && this.dispatchRemote(request));
|
|
2374
2914
|
// Registered before onContentReady so cells are prepared by the time it fires, as in dx.
|
|
2375
2915
|
afterRenderEffect(() => {
|
|
2376
2916
|
this.renderRows();
|
|
@@ -2465,7 +3005,7 @@ class NxeDataGridComponent {
|
|
|
2465
3005
|
return;
|
|
2466
3006
|
}
|
|
2467
3007
|
const cols = this.visibleColumns();
|
|
2468
|
-
const lead = this.groupLevelCount() + (this.masterDetailEnabled() ? 1 : 0) + (this.
|
|
3008
|
+
const lead = this.groupLevelCount() + (this.masterDetailEnabled() ? 1 : 0) + (this.selectColumnVisible() ? 1 : 0);
|
|
2469
3009
|
// One cell per visible column, gathered across the header rows: with bands a top-level
|
|
2470
3010
|
// column sits in the first row and spans down, so no single row holds them all.
|
|
2471
3011
|
const cells = nxeHeaderLeafCells(host);
|
|
@@ -2507,7 +3047,7 @@ class NxeDataGridComponent {
|
|
|
2507
3047
|
});
|
|
2508
3048
|
const specialSum = 30 * this.groupLevelCount() +
|
|
2509
3049
|
(this.masterDetailEnabled() ? 30 : 0) +
|
|
2510
|
-
(this.
|
|
3050
|
+
(this.selectColumnVisible() ? 70 : 0) +
|
|
2511
3051
|
(this.rowEditCommandVisible() ? 100 : 0);
|
|
2512
3052
|
const fixedSum = explicit.reduce((s, w) => s + (w ?? 0), 0) + specialSum;
|
|
2513
3053
|
const freeIdx = explicit.map((w, i) => (w == null ? i : -1)).filter((i) => i >= 0);
|
|
@@ -2562,24 +3102,51 @@ class NxeDataGridComponent {
|
|
|
2562
3102
|
if (aw && this.columnAutoWidth()) {
|
|
2563
3103
|
return aw.widths[idx] ?? this.colWidth(col);
|
|
2564
3104
|
}
|
|
3105
|
+
const slack = this.frozenSlack();
|
|
3106
|
+
if (slack > 0 && idx === this.visibleColumns().length - 1) {
|
|
3107
|
+
return `${Math.round(this.frozenColWidth(col) + slack)}px`;
|
|
3108
|
+
}
|
|
3109
|
+
// A manual resize froze every visible column at a measured width, and that width - not the
|
|
3110
|
+
// declared one - is what the column now has. (A declared width used to win here, and only a
|
|
3111
|
+
// stale inline style left by the drag kept a resized column at its new width.)
|
|
3112
|
+
if (this.widthOverride().has(col.dataField())) {
|
|
3113
|
+
return `${this.frozenColWidth(col)}px`;
|
|
3114
|
+
}
|
|
2565
3115
|
const w = this.colWidth(col);
|
|
2566
3116
|
if (w == null && this.widthOverride().size) {
|
|
2567
3117
|
return `${this.frozenColWidth(col)}px`;
|
|
2568
3118
|
}
|
|
2569
3119
|
return w;
|
|
2570
3120
|
}
|
|
3121
|
+
/** What a frozen layout's columns add up to, the command columns included. */
|
|
3122
|
+
frozenTableWidth() {
|
|
3123
|
+
let total = 30 * this.groupLevelCount() +
|
|
3124
|
+
(this.masterDetailEnabled() ? 30 : 0) +
|
|
3125
|
+
(this.selectColumnVisible() ? 40 : 0) +
|
|
3126
|
+
(this.rowEditCommandVisible() ? 100 : 0);
|
|
3127
|
+
for (const col of this.visibleColumns()) {
|
|
3128
|
+
total += this.frozenColWidth(col);
|
|
3129
|
+
}
|
|
3130
|
+
return total;
|
|
3131
|
+
}
|
|
3132
|
+
/**
|
|
3133
|
+
* With `resizeFillsGrid`, the room a frozen widget-mode layout leaves on the right, which the
|
|
3134
|
+
* trailing column takes. 0 when the columns fill or overflow the grid - that table scrolls.
|
|
3135
|
+
*/
|
|
3136
|
+
frozenSlack = computed(() => {
|
|
3137
|
+
if (!this.resizeFillsGrid() || !this.widthOverride().size || this.columnResizingMode() !== 'widget') {
|
|
3138
|
+
return 0;
|
|
3139
|
+
}
|
|
3140
|
+
const container = this.containerW();
|
|
3141
|
+
return container > 0 ? Math.max(0, Math.round(container - this.frozenTableWidth())) : 0;
|
|
3142
|
+
}, /* @ts-ignore */
|
|
3143
|
+
...(ngDevMode ? [{ debugName: "frozenSlack" }] : /* istanbul ignore next */ []));
|
|
2571
3144
|
tableWidthStyle() {
|
|
2572
3145
|
// After a manual resize the layout is frozen: widget mode sizes the table to the sum of
|
|
2573
3146
|
// its columns (dx grows/shrinks the table); nextColumn keeps filling the container.
|
|
2574
3147
|
if (this.widthOverride().size && this.columnResizingMode() === 'widget') {
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
(this.showCheckboxes() ? 40 : 0) +
|
|
2578
|
-
(this.rowEditCommandVisible() ? 100 : 0);
|
|
2579
|
-
for (const col of this.visibleColumns()) {
|
|
2580
|
-
total += this.frozenColWidth(col);
|
|
2581
|
-
}
|
|
2582
|
-
return `${Math.round(total)}px`;
|
|
3148
|
+
// The trailing column already holds the slack, so the columns add up to the grid.
|
|
3149
|
+
return this.frozenSlack() > 0 ? '100%' : `${Math.round(this.frozenTableWidth())}px`;
|
|
2583
3150
|
}
|
|
2584
3151
|
if (this.widthOverride().size) {
|
|
2585
3152
|
return '100%';
|
|
@@ -2604,21 +3171,76 @@ class NxeDataGridComponent {
|
|
|
2604
3171
|
(this.treeMode && this.selectionMode() === 'multiple'));
|
|
2605
3172
|
}, /* @ts-ignore */
|
|
2606
3173
|
...(ngDevMode ? [{ debugName: "showCheckboxes" }] : /* istanbul ignore next */ []));
|
|
3174
|
+
/**
|
|
3175
|
+
* The detached checkbox column. A tree list has none: dx draws its row checkbox in the first
|
|
3176
|
+
* cell, after the expander, and its select-all in the first column's header.
|
|
3177
|
+
*/
|
|
3178
|
+
selectColumnVisible = computed(() => this.showCheckboxes() && !this.treeMode, /* @ts-ignore */
|
|
3179
|
+
...(ngDevMode ? [{ debugName: "selectColumnVisible" }] : /* istanbul ignore next */ []));
|
|
3180
|
+
treeSelectAll = computed(() => this.treeMode && this.showCheckboxes() && this.selectionMode() === 'multiple', /* @ts-ignore */
|
|
3181
|
+
...(ngDevMode ? [{ debugName: "treeSelectAll" }] : /* istanbul ignore next */ []));
|
|
3182
|
+
recursiveTreeSelection = computed(() => this.treeMode && this.selectionMode() === 'multiple' && (this.selection()?.recursive() ?? false), /* @ts-ignore */
|
|
3183
|
+
...(ngDevMode ? [{ debugName: "recursiveTreeSelection" }] : /* istanbul ignore next */ []));
|
|
3184
|
+
/**
|
|
3185
|
+
* Each node's recursive selection state as dx draws it: `all` when its key is selected or an
|
|
3186
|
+
* ancestor's is, or when every child is; `some` when only some descendants are; absent for none.
|
|
3187
|
+
* Keys hold the selection, so the hierarchy - not the key list - decides what a parent shows.
|
|
3188
|
+
*/
|
|
3189
|
+
treeCheckState = computed(() => this.recursiveTreeSelection()
|
|
3190
|
+
? nxeTreeCheckState(this.treeRoots(), new Set(this.selectedRowKeys()))
|
|
3191
|
+
: new Map(), /* @ts-ignore */
|
|
3192
|
+
...(ngDevMode ? [{ debugName: "treeCheckState" }] : /* istanbul ignore next */ []));
|
|
3193
|
+
/** Parent of each tree node, for settling ancestors after a recursive toggle. */
|
|
3194
|
+
treeParents = computed(() => {
|
|
3195
|
+
const parents = new Map();
|
|
3196
|
+
const walk = (nodes) => {
|
|
3197
|
+
for (const n of nodes) {
|
|
3198
|
+
n.children.forEach((c) => parents.set(c.key, n));
|
|
3199
|
+
walk(n.children);
|
|
3200
|
+
}
|
|
3201
|
+
};
|
|
3202
|
+
walk(this.treeRoots());
|
|
3203
|
+
return parents;
|
|
3204
|
+
}, /* @ts-ignore */
|
|
3205
|
+
...(ngDevMode ? [{ debugName: "treeParents" }] : /* istanbul ignore next */ []));
|
|
2607
3206
|
/** Selected rows in dataSource order (stale refs from old data naturally filtered out). */
|
|
2608
3207
|
selectedRowsData = computed(() => {
|
|
3208
|
+
if (this.remote()) {
|
|
3209
|
+
// Keyed, and across pages: the row object is the page's when it is on screen, else the
|
|
3210
|
+
// one remembered from when it was selected.
|
|
3211
|
+
const page = new Map(this.sourceRows().map((r) => [this.rowKey(r), r]));
|
|
3212
|
+
return this.selectedRowKeys().flatMap((key) => {
|
|
3213
|
+
const row = page.get(key) ?? this.remoteRowCache.get(key);
|
|
3214
|
+
if (row === undefined) {
|
|
3215
|
+
return [];
|
|
3216
|
+
}
|
|
3217
|
+
this.remoteRowCache.set(key, row);
|
|
3218
|
+
return [row];
|
|
3219
|
+
});
|
|
3220
|
+
}
|
|
2609
3221
|
if (this.treeMode) {
|
|
2610
3222
|
const keySet = new Set(this.selectedRowKeys());
|
|
2611
3223
|
return keySet.size === 0
|
|
2612
3224
|
? []
|
|
2613
|
-
:
|
|
3225
|
+
: this.sourceRows().filter((r) => keySet.has(this.rowKey(r)));
|
|
2614
3226
|
}
|
|
2615
3227
|
const set = this.selectedSet();
|
|
2616
|
-
return set.size === 0 ? [] :
|
|
3228
|
+
return set.size === 0 ? [] : this.sourceRows().filter((r) => set.has(r));
|
|
2617
3229
|
}, /* @ts-ignore */
|
|
2618
3230
|
...(ngDevMode ? [{ debugName: "selectedRowsData" }] : /* istanbul ignore next */ []));
|
|
2619
3231
|
allSelected = computed(() => {
|
|
3232
|
+
if (this.remote()) {
|
|
3233
|
+
const rows = this.displayRows();
|
|
3234
|
+
const keys = this.selectedKeySet();
|
|
3235
|
+
return rows.length > 0 && rows.every((r) => keys.has(this.rowKey(r)));
|
|
3236
|
+
}
|
|
3237
|
+
if (this.recursiveTreeSelection()) {
|
|
3238
|
+
const roots = this.treeRoots();
|
|
3239
|
+
const state = this.treeCheckState();
|
|
3240
|
+
return roots.length > 0 && roots.every((n) => state.get(n.key) === 'all');
|
|
3241
|
+
}
|
|
2620
3242
|
if (this.treeMode) {
|
|
2621
|
-
const rows =
|
|
3243
|
+
const rows = this.sourceRows();
|
|
2622
3244
|
const keys = this.selectedRowKeys();
|
|
2623
3245
|
return rows.length > 0 && rows.every((r) => keys.includes(this.rowKey(r)));
|
|
2624
3246
|
}
|
|
@@ -2627,12 +3249,19 @@ class NxeDataGridComponent {
|
|
|
2627
3249
|
return rows.length > 0 && rows.every((r) => set.has(r));
|
|
2628
3250
|
}, /* @ts-ignore */
|
|
2629
3251
|
...(ngDevMode ? [{ debugName: "allSelected" }] : /* istanbul ignore next */ []));
|
|
2630
|
-
someSelected = computed(() => this.
|
|
2631
|
-
? this.
|
|
2632
|
-
: this.
|
|
3252
|
+
someSelected = computed(() => this.remote()
|
|
3253
|
+
? this.displayRows().some((r) => this.selectedKeySet().has(this.rowKey(r)))
|
|
3254
|
+
: this.recursiveTreeSelection()
|
|
3255
|
+
? this.treeCheckState().size > 0
|
|
3256
|
+
: this.treeMode
|
|
3257
|
+
? this.selectedRowKeys().length > 0
|
|
3258
|
+
: this.displayRows().some((r) => this.selectedSet().has(r)), /* @ts-ignore */
|
|
2633
3259
|
...(ngDevMode ? [{ debugName: "someSelected" }] : /* istanbul ignore next */ []));
|
|
3260
|
+
/** The key field: the bound `keyExpr`, else the remote source's own `key`. */
|
|
3261
|
+
keyField = computed(() => this.keyExpr() ?? this.remote()?.key, /* @ts-ignore */
|
|
3262
|
+
...(ngDevMode ? [{ debugName: "keyField" }] : /* istanbul ignore next */ []));
|
|
2634
3263
|
rowKey(row) {
|
|
2635
|
-
const k = this.
|
|
3264
|
+
const k = this.keyField();
|
|
2636
3265
|
return k ? row?.[k] : row;
|
|
2637
3266
|
}
|
|
2638
3267
|
treeDescendantKeys(row) {
|
|
@@ -2672,20 +3301,53 @@ class NxeDataGridComponent {
|
|
|
2672
3301
|
this.emitTreeSelectionChanged([key], added, removed);
|
|
2673
3302
|
return;
|
|
2674
3303
|
}
|
|
2675
|
-
|
|
3304
|
+
if (this.recursiveTreeSelection()) {
|
|
3305
|
+
this.toggleRecursiveTreeSelection(row, key);
|
|
3306
|
+
return;
|
|
3307
|
+
}
|
|
2676
3308
|
const keys = new Set(this.selectedRowKeys());
|
|
2677
3309
|
const selecting = !keys.has(key);
|
|
2678
|
-
|
|
2679
|
-
for (const k of affected) {
|
|
2680
|
-
selecting ? keys.add(k) : keys.delete(k);
|
|
2681
|
-
}
|
|
3310
|
+
selecting ? keys.add(key) : keys.delete(key);
|
|
2682
3311
|
const arr = [...keys];
|
|
2683
3312
|
this.selectedRowKeys.set(arr);
|
|
2684
|
-
this.emitTreeSelectionChanged(arr, selecting ?
|
|
3313
|
+
this.emitTreeSelectionChanged(arr, selecting ? [key] : [], selecting ? [] : [key]);
|
|
3314
|
+
}
|
|
3315
|
+
/**
|
|
3316
|
+
* dx recursive selection. Clicking a node that is not fully checked (unchecked, or a parent
|
|
3317
|
+
* showing indeterminate) selects its whole subtree; clicking a checked one clears it. Then its
|
|
3318
|
+
* ancestors follow their children, checked once all of them are and cleared as soon as one is
|
|
3319
|
+
* not, so deselecting one child leaves its parent indeterminate rather than still ticked.
|
|
3320
|
+
*
|
|
3321
|
+
* The key list is rewritten as every checked node, which dx's `getSelectedRowKeys('all')`
|
|
3322
|
+
* also reports. The event's current keys are what changed on screen.
|
|
3323
|
+
*/
|
|
3324
|
+
toggleRecursiveTreeSelection(row, key) {
|
|
3325
|
+
const before = this.treeCheckState();
|
|
3326
|
+
const checked = new Set();
|
|
3327
|
+
before.forEach((state, k) => state === 'all' && checked.add(k));
|
|
3328
|
+
const selecting = before.get(key) !== 'all';
|
|
3329
|
+
for (const k of this.treeDescendantKeys(row)) {
|
|
3330
|
+
selecting ? checked.add(k) : checked.delete(k);
|
|
3331
|
+
}
|
|
3332
|
+
const parents = this.treeParents();
|
|
3333
|
+
for (let parent = parents.get(key); parent; parent = parents.get(parent.key)) {
|
|
3334
|
+
parent.children.every((c) => checked.has(c.key)) ? checked.add(parent.key) : checked.delete(parent.key);
|
|
3335
|
+
}
|
|
3336
|
+
const previous = this.selectedRowKeys();
|
|
3337
|
+
const kept = previous.filter((k) => checked.has(k));
|
|
3338
|
+
const keptSet = new Set(kept);
|
|
3339
|
+
const next = [...kept, ...[...checked].filter((k) => !keptSet.has(k))];
|
|
3340
|
+
const wasChecked = (k) => before.get(k) === 'all';
|
|
3341
|
+
this.selectedRowKeys.set(next);
|
|
3342
|
+
this.emitTreeSelectionChanged(next, next.filter((k) => !wasChecked(k)), [...before.keys()].filter((k) => wasChecked(k) && !checked.has(k)));
|
|
3343
|
+
}
|
|
3344
|
+
/** A recursive tree list's parent with only some descendants selected (dx's indeterminate box). */
|
|
3345
|
+
isTreeIndeterminate(row) {
|
|
3346
|
+
return this.treeCheckState().get(this.rowKey(row)) === 'some';
|
|
2685
3347
|
}
|
|
2686
3348
|
emitTreeSelectionChanged(allKeys, added, removed) {
|
|
2687
3349
|
const keySet = new Set(allKeys);
|
|
2688
|
-
const data =
|
|
3350
|
+
const data = this.sourceRows().filter((r) => keySet.has(this.rowKey(r)));
|
|
2689
3351
|
this.onSelectionChanged.emit({
|
|
2690
3352
|
selectedRowsData: data,
|
|
2691
3353
|
selectedRowKeys: allKeys,
|
|
@@ -2731,24 +3393,308 @@ class NxeDataGridComponent {
|
|
|
2731
3393
|
return map;
|
|
2732
3394
|
}, /* @ts-ignore */
|
|
2733
3395
|
...(ngDevMode ? [{ debugName: "totalSums" }] : /* istanbul ignore next */ []));
|
|
3396
|
+
// --- Server-side data (NxeRemoteDataSource) -------------------------------------------------
|
|
3397
|
+
/** A `{ load }` store handed to `dataSource` with `remoteOperations`, wrapped once per store. */
|
|
3398
|
+
storeWrappers = new WeakMap();
|
|
3399
|
+
wrapped = null;
|
|
3400
|
+
remoteWarnings = new Set();
|
|
3401
|
+
/** Rows picked on earlier pages, by key, so `selectedRowsData` still has them. */
|
|
3402
|
+
remoteRowCache = new Map();
|
|
3403
|
+
/** Set by filter-row typing; the next request waits `filterDebounce` before it is sent. */
|
|
3404
|
+
remoteTyping = false;
|
|
3405
|
+
/** What the last request was for, to tell a changed filter, sort or size from a changed page. */
|
|
3406
|
+
remoteSignature = null;
|
|
3407
|
+
warnOnce(key, message) {
|
|
3408
|
+
if (!this.remoteWarnings.has(key)) {
|
|
3409
|
+
this.remoteWarnings.add(key);
|
|
3410
|
+
console.warn(`[nxe-data-grid] ${message}`);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
/**
|
|
3414
|
+
* The source the grid pages through, or `null` for a client-side array. An
|
|
3415
|
+
* {@link NxeRemoteDataSource} is used as it is; a `{ load }` store becomes one when
|
|
3416
|
+
* `remoteOperations` is on.
|
|
3417
|
+
*/
|
|
3418
|
+
remote = computed(() => {
|
|
3419
|
+
const source = this.dataSource();
|
|
3420
|
+
if (this.treeMode) {
|
|
3421
|
+
if (source instanceof NxeRemoteDataSource || nxeIsStoreLike(source)) {
|
|
3422
|
+
this.warnOnce('tree', 'nxe-tree-list does not support server-side data; the source is ignored.');
|
|
3423
|
+
}
|
|
3424
|
+
return null;
|
|
3425
|
+
}
|
|
3426
|
+
if (source instanceof NxeRemoteDataSource) {
|
|
3427
|
+
return source;
|
|
3428
|
+
}
|
|
3429
|
+
if (!nxeIsStoreLike(source)) {
|
|
3430
|
+
return null;
|
|
3431
|
+
}
|
|
3432
|
+
if (!this.remoteOperations()) {
|
|
3433
|
+
this.warnOnce('store', 'A { load } dataSource needs [remoteOperations]="true"; without it the grid shows no rows.');
|
|
3434
|
+
return null;
|
|
3435
|
+
}
|
|
3436
|
+
let wrapper = this.storeWrappers.get(source);
|
|
3437
|
+
if (!wrapper) {
|
|
3438
|
+
wrapper = new NxeRemoteDataSource((options, context) => source.load(options, context), {
|
|
3439
|
+
key: source.key,
|
|
3440
|
+
});
|
|
3441
|
+
this.storeWrappers.set(source, wrapper);
|
|
3442
|
+
}
|
|
3443
|
+
this.wrapped = wrapper;
|
|
3444
|
+
return wrapper;
|
|
3445
|
+
}, /* @ts-ignore */
|
|
3446
|
+
...(ngDevMode ? [{ debugName: "remote" }] : /* istanbul ignore next */ []));
|
|
3447
|
+
/** The rows every client-side computed reads: the array, or the remote source's current page. */
|
|
3448
|
+
sourceRows = computed(() => {
|
|
3449
|
+
const remote = this.remote();
|
|
3450
|
+
if (remote) {
|
|
3451
|
+
return remote.items();
|
|
3452
|
+
}
|
|
3453
|
+
const source = this.dataSource();
|
|
3454
|
+
return Array.isArray(source) ? source : [];
|
|
3455
|
+
}, /* @ts-ignore */
|
|
3456
|
+
...(ngDevMode ? [{ debugName: "sourceRows" }] : /* istanbul ignore next */ []));
|
|
3457
|
+
selectedKeySet = computed(() => new Set(this.selectedRowKeys()), /* @ts-ignore */
|
|
3458
|
+
...(ngDevMode ? [{ debugName: "selectedKeySet" }] : /* istanbul ignore next */ []));
|
|
3459
|
+
/** The sort a request carries: the controlled `sort`, else the header sort. */
|
|
3460
|
+
remoteSort = computed(() => {
|
|
3461
|
+
const bound = this.sortModel();
|
|
3462
|
+
if (bound !== undefined) {
|
|
3463
|
+
return bound.map((s) => ({ selector: s.selector, desc: s.desc }));
|
|
3464
|
+
}
|
|
3465
|
+
const sort = this.effectiveSort();
|
|
3466
|
+
return sort ? [{ selector: sort.field, desc: sort.dir === 'desc' }] : [];
|
|
3467
|
+
}, /* @ts-ignore */
|
|
3468
|
+
...(ngDevMode ? [{ debugName: "remoteSort" }] : /* istanbul ignore next */ []));
|
|
3469
|
+
/** The filter a request carries: filter row AND header filters AND the application's `filter`. */
|
|
3470
|
+
remoteFilter = computed(() => {
|
|
3471
|
+
if (!this.remote()) {
|
|
3472
|
+
return null;
|
|
3473
|
+
}
|
|
3474
|
+
return nxeFilterAnd(this.remoteFilterRowExpression(), this.remoteHeaderFilterExpression(), this.filter());
|
|
3475
|
+
}, /* @ts-ignore */
|
|
3476
|
+
...(ngDevMode ? [{ debugName: "remoteFilter" }] : /* istanbul ignore next */ []));
|
|
3477
|
+
/** The request the remote source should be showing, or `null` for a client-side grid. */
|
|
3478
|
+
remoteRequest = computed(() => {
|
|
3479
|
+
const source = this.remote();
|
|
3480
|
+
if (!source) {
|
|
3481
|
+
return null;
|
|
3482
|
+
}
|
|
3483
|
+
return {
|
|
3484
|
+
source,
|
|
3485
|
+
query: {
|
|
3486
|
+
pageIndex: this.currentPage(),
|
|
3487
|
+
pageSize: this.effectivePageSize(),
|
|
3488
|
+
sort: this.remoteSort(),
|
|
3489
|
+
filter: this.remoteFilter(),
|
|
3490
|
+
},
|
|
3491
|
+
};
|
|
3492
|
+
}, { ...(ngDevMode ? { debugName: "remoteRequest" } : /* istanbul ignore next */ {}),
|
|
3493
|
+
// The page index is clamped by the source's total, so every response re-derives the
|
|
3494
|
+
// request; an unchanged one must not be a new value, or it would reload forever.
|
|
3495
|
+
equal: (a, b) => a === b || (!!a && !!b && a.source === b.source && nxeSameQuery(a.query, b.query)) });
|
|
3496
|
+
dispatchRemote(request) {
|
|
3497
|
+
const { source, query } = request;
|
|
3498
|
+
const key = JSON.stringify([query.pageSize, query.sort, query.filter]);
|
|
3499
|
+
const last = this.remoteSignature;
|
|
3500
|
+
this.remoteSignature = { source, key };
|
|
3501
|
+
if (last && last.source === source && last.key !== key && query.pageIndex !== 0) {
|
|
3502
|
+
// A new filter, sort or page size means a new result set: back to its first page. The
|
|
3503
|
+
// move restates the request, which is what loads.
|
|
3504
|
+
this.goToPage(0);
|
|
3505
|
+
return;
|
|
3506
|
+
}
|
|
3507
|
+
if (this.groupColumns().length) {
|
|
3508
|
+
this.warnOnce('group', 'Grouping is not supported with server-side data: rows are grouped within the current page only.');
|
|
3509
|
+
}
|
|
3510
|
+
if (this.summaryCmp()) {
|
|
3511
|
+
this.warnOnce('summary', 'Summaries are not supported with server-side data: they are computed over the current page only.');
|
|
3512
|
+
}
|
|
3513
|
+
const before = source.options();
|
|
3514
|
+
source.setOptions(query);
|
|
3515
|
+
if (source.options() !== before) {
|
|
3516
|
+
this.optionsChange.emit(source.options());
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
// Filter row -> expression. The matcher in `buildFilterRowMatcher` is the client-side reading
|
|
3520
|
+
// of the same state; keep the two in step.
|
|
3521
|
+
remoteFilterRowExpression() {
|
|
3522
|
+
const byField = this.columnByField();
|
|
3523
|
+
const parts = [];
|
|
3524
|
+
for (const [field, entry] of this.filterRowState()) {
|
|
3525
|
+
const col = byField.get(field);
|
|
3526
|
+
const expr = col ? this.filterRowCondition(col, entry) : null;
|
|
3527
|
+
if (expr) {
|
|
3528
|
+
parts.push(expr);
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
return nxeFilterAnd(...parts);
|
|
3532
|
+
}
|
|
3533
|
+
filterRowCondition(col, entry) {
|
|
3534
|
+
const field = col.dataField();
|
|
3535
|
+
const kind = this.frEditorKind(col);
|
|
3536
|
+
const t1 = entry.text.trim();
|
|
3537
|
+
const t2 = (entry.text2 ?? '').trim();
|
|
3538
|
+
if (!t1 && !t2) {
|
|
3539
|
+
return null;
|
|
3540
|
+
}
|
|
3541
|
+
if (kind === 'boolean') {
|
|
3542
|
+
return [field, '=', t1 === 'true'];
|
|
3543
|
+
}
|
|
3544
|
+
if (kind === 'lookup') {
|
|
3545
|
+
const lookup = col.lookup();
|
|
3546
|
+
const valueOf = (i) => nxeResolveExpr(lookup.valueExpr ?? 'lovCode', i);
|
|
3547
|
+
const item = (lookup.dataSource ?? []).find((i) => String(valueOf(i) ?? '') === t1);
|
|
3548
|
+
return [field, '=', item ? valueOf(item) : t1];
|
|
3549
|
+
}
|
|
3550
|
+
const between = (lo, hi, hiOp) => nxeFilterAnd(lo == null ? null : [field, '>=', lo], hi == null ? null : [field, hiOp, hi]);
|
|
3551
|
+
if (kind === 'number') {
|
|
3552
|
+
const v1 = t1 ? Number(t1.replace(/,/g, '')) : null;
|
|
3553
|
+
const v2 = t2 ? Number(t2.replace(/,/g, '')) : null;
|
|
3554
|
+
if ((t1 && Number.isNaN(v1)) || (t2 && Number.isNaN(v2))) {
|
|
3555
|
+
return null; // nothing applies until the editor holds a valid number
|
|
3556
|
+
}
|
|
3557
|
+
if (entry.op === 'between') {
|
|
3558
|
+
return between(v1, v2, '<=');
|
|
3559
|
+
}
|
|
3560
|
+
const op = ['<>', '<', '<=', '>', '>='].find((o) => o === entry.op) ?? '=';
|
|
3561
|
+
return v1 == null ? null : [field, op, v1];
|
|
3562
|
+
}
|
|
3563
|
+
if (kind === 'date') {
|
|
3564
|
+
const d1 = t1 ? this.dayKey(t1) : null;
|
|
3565
|
+
const d2 = t2 ? this.dayKey(t2) : null;
|
|
3566
|
+
if ((t1 && !d1) || (t2 && !d2)) {
|
|
3567
|
+
return null;
|
|
3568
|
+
}
|
|
3569
|
+
// A date is a whole day, as in dx: '=' is [day, next day), '<=' ends before the next day.
|
|
3570
|
+
const next = (day) => nxeNextDay(day);
|
|
3571
|
+
switch (entry.op) {
|
|
3572
|
+
case 'between':
|
|
3573
|
+
return between(d1, d2 ? next(d2) : null, '<');
|
|
3574
|
+
case '<>':
|
|
3575
|
+
return d1 ? nxeFilterOr([field, '<', d1], [field, '>=', next(d1)]) : null;
|
|
3576
|
+
case '<':
|
|
3577
|
+
return d1 ? [field, '<', d1] : null;
|
|
3578
|
+
case '<=':
|
|
3579
|
+
return d1 ? [field, '<', next(d1)] : null;
|
|
3580
|
+
case '>':
|
|
3581
|
+
return d1 ? [field, '>=', next(d1)] : null;
|
|
3582
|
+
case '>=':
|
|
3583
|
+
return d1 ? [field, '>=', d1] : null;
|
|
3584
|
+
default:
|
|
3585
|
+
return d1 ? between(d1, next(d1), '<') : null;
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
// Text: the operation as chosen. Case is the server's business; the client matcher ignores it.
|
|
3589
|
+
const textOps = ['notcontains', 'startswith', 'endswith', '=', '<>'];
|
|
3590
|
+
return [field, textOps.find((o) => o === entry.op) ?? 'contains', t1];
|
|
3591
|
+
}
|
|
3592
|
+
remoteHeaderFilterExpression() {
|
|
3593
|
+
const byField = this.columnByField();
|
|
3594
|
+
const parts = [];
|
|
3595
|
+
for (const [field, keys] of this.effectiveHeaderFilters()) {
|
|
3596
|
+
const isDate = (byField.get(field) ? this.colType(byField.get(field)) : undefined) === 'date';
|
|
3597
|
+
const values = [...keys].map((key) => {
|
|
3598
|
+
if (key === NXE_HF_BLANKS) {
|
|
3599
|
+
return [field, '=', null];
|
|
3600
|
+
}
|
|
3601
|
+
if (isDate && typeof key === 'string') {
|
|
3602
|
+
return [[field, '>=', key], 'and', [field, '<', nxeNextDay(key)]];
|
|
3603
|
+
}
|
|
3604
|
+
return [field, '=', key];
|
|
3605
|
+
});
|
|
3606
|
+
// An empty selection is an empty grid: a condition nothing satisfies.
|
|
3607
|
+
parts.push(nxeFilterOr(...values) ?? [[field, '=', null], 'and', [field, '<>', null]]);
|
|
3608
|
+
}
|
|
3609
|
+
return nxeFilterAnd(...parts);
|
|
3610
|
+
}
|
|
3611
|
+
toggleRemoteSelection(row) {
|
|
3612
|
+
const key = this.rowKey(row);
|
|
3613
|
+
const previous = this.selectedRowKeys();
|
|
3614
|
+
let next;
|
|
3615
|
+
let added = [];
|
|
3616
|
+
let removed = [];
|
|
3617
|
+
if (this.selectionMode() !== 'multiple') {
|
|
3618
|
+
added = previous.includes(key) ? [] : [key];
|
|
3619
|
+
removed = previous.filter((k) => k !== key);
|
|
3620
|
+
next = [key];
|
|
3621
|
+
}
|
|
3622
|
+
else if (previous.includes(key)) {
|
|
3623
|
+
next = previous.filter((k) => k !== key);
|
|
3624
|
+
removed = [key];
|
|
3625
|
+
}
|
|
3626
|
+
else {
|
|
3627
|
+
next = [...previous, key];
|
|
3628
|
+
added = [key];
|
|
3629
|
+
}
|
|
3630
|
+
this.remoteRowCache.set(key, row);
|
|
3631
|
+
this.selectedRowKeys.set(next);
|
|
3632
|
+
this.emitRemoteSelectionChanged(next, added, removed);
|
|
3633
|
+
}
|
|
3634
|
+
toggleRemoteSelectAll() {
|
|
3635
|
+
const pageKeys = this.displayRows().map((r) => this.rowKey(r));
|
|
3636
|
+
for (const row of this.displayRows()) {
|
|
3637
|
+
this.remoteRowCache.set(this.rowKey(row), row);
|
|
3638
|
+
}
|
|
3639
|
+
const previous = this.selectedRowKeys();
|
|
3640
|
+
if (this.allSelected()) {
|
|
3641
|
+
const drop = new Set(pageKeys);
|
|
3642
|
+
const next = previous.filter((k) => !drop.has(k));
|
|
3643
|
+
this.selectedRowKeys.set(next);
|
|
3644
|
+
this.emitRemoteSelectionChanged(next, [], pageKeys);
|
|
3645
|
+
}
|
|
3646
|
+
else {
|
|
3647
|
+
const have = new Set(previous);
|
|
3648
|
+
const added = pageKeys.filter((k) => !have.has(k));
|
|
3649
|
+
const next = [...previous, ...added];
|
|
3650
|
+
this.selectedRowKeys.set(next);
|
|
3651
|
+
this.emitRemoteSelectionChanged(next, added, []);
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
emitRemoteSelectionChanged(keys, added, removed) {
|
|
3655
|
+
this.onSelectionChanged.emit({
|
|
3656
|
+
selectedRowsData: this.selectedRowsData(),
|
|
3657
|
+
selectedRowKeys: keys,
|
|
3658
|
+
currentSelectedRowKeys: added,
|
|
3659
|
+
currentDeselectedRowKeys: removed,
|
|
3660
|
+
component: this.facade,
|
|
3661
|
+
element: this.elementRef.nativeElement,
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
2734
3664
|
// --- Internal paging (dxo-paging) -----------------------------------------------------------
|
|
2735
|
-
pagingEnabled = computed(() => !!this.pagingCmp() && this.pagingCmp().enabled(), /* @ts-ignore */
|
|
3665
|
+
pagingEnabled = computed(() => !!this.remote() || (!!this.pagingCmp() && this.pagingCmp().enabled()), /* @ts-ignore */
|
|
2736
3666
|
...(ngDevMode ? [{ debugName: "pagingEnabled" }] : /* istanbul ignore next */ []));
|
|
2737
|
-
effectivePageSize = computed(() => this.userPageSize() ??
|
|
3667
|
+
effectivePageSize = computed(() => this.userPageSize() ??
|
|
3668
|
+
this.pagingCmp()?.pageSize() ??
|
|
3669
|
+
this.remote()?.options().pageSize ??
|
|
3670
|
+
NXE_REMOTE_DEFAULT_PAGE_SIZE, /* @ts-ignore */
|
|
2738
3671
|
...(ngDevMode ? [{ debugName: "effectivePageSize" }] : /* istanbul ignore next */ []));
|
|
2739
|
-
pageCount = computed(() =>
|
|
3672
|
+
pageCount = computed(() => {
|
|
3673
|
+
const remote = this.remote();
|
|
3674
|
+
if (remote) {
|
|
3675
|
+
return Math.max(1, Math.ceil(remote.totalCount() / this.effectivePageSize()));
|
|
3676
|
+
}
|
|
3677
|
+
return this.pagingEnabled() ? Math.max(1, Math.ceil(this.displayRows().length / this.effectivePageSize())) : 1;
|
|
3678
|
+
}, /* @ts-ignore */
|
|
2740
3679
|
...(ngDevMode ? [{ debugName: "pageCount" }] : /* istanbul ignore next */ []));
|
|
2741
3680
|
/**
|
|
2742
3681
|
* The page shown: `pageIndex` held inside the page count. A filter can leave the bound index
|
|
2743
3682
|
* past the last page; dx moves it back (m_data_source_adapter.js:454), and the rows, the
|
|
2744
3683
|
* pager's selected button, its info text and prev/next all read this one value.
|
|
2745
3684
|
*/
|
|
2746
|
-
currentPage = computed(() =>
|
|
3685
|
+
currentPage = computed(() => {
|
|
3686
|
+
const remote = this.remote();
|
|
3687
|
+
// Until the first response there is no total to hold the index inside.
|
|
3688
|
+
if (remote && !remote.loaded()) {
|
|
3689
|
+
return Math.max(0, this.pageIndex());
|
|
3690
|
+
}
|
|
3691
|
+
return Math.max(0, Math.min(this.pageIndex(), this.pageCount() - 1));
|
|
3692
|
+
}, /* @ts-ignore */
|
|
2747
3693
|
...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
|
|
2748
3694
|
/** The filtered/sorted rows visible on the current page (all rows when paging is off). */
|
|
2749
3695
|
pagedRows = computed(() => {
|
|
2750
3696
|
const rows = this.displayRows();
|
|
2751
|
-
if (!this.pagingEnabled()) {
|
|
3697
|
+
if (!this.pagingEnabled() || this.remote()) {
|
|
2752
3698
|
return rows;
|
|
2753
3699
|
}
|
|
2754
3700
|
const ps = this.effectivePageSize();
|
|
@@ -2801,7 +3747,7 @@ class NxeDataGridComponent {
|
|
|
2801
3747
|
if (!this.treeMode) {
|
|
2802
3748
|
return [];
|
|
2803
3749
|
}
|
|
2804
|
-
const rows =
|
|
3750
|
+
const rows = this.sourceRows();
|
|
2805
3751
|
const keyF = this.keyExpr() ?? 'id';
|
|
2806
3752
|
const parentF = this.parentIdExpr() ?? 'parentId';
|
|
2807
3753
|
const root = this.rootValue();
|
|
@@ -3036,6 +3982,9 @@ class NxeDataGridComponent {
|
|
|
3036
3982
|
this.pageIndex.set(next);
|
|
3037
3983
|
// Write back so `[(pageIndex)]` reflects a page the grid moved to itself.
|
|
3038
3984
|
this.pagingCmp()?.pageIndex.set(next);
|
|
3985
|
+
if (this.pageIndexModel() !== undefined || this.remote()) {
|
|
3986
|
+
this.pageIndexModel.set(next);
|
|
3987
|
+
}
|
|
3039
3988
|
this.onOptionChanged.emit({ name: 'paging', fullName: 'paging.pageIndex', value: next });
|
|
3040
3989
|
}
|
|
3041
3990
|
}
|
|
@@ -3048,12 +3997,16 @@ class NxeDataGridComponent {
|
|
|
3048
3997
|
changePageSize(size) {
|
|
3049
3998
|
this.userPageSize.set(size);
|
|
3050
3999
|
this.pageIndex.set(0);
|
|
4000
|
+
if (this.pageSizeModel() !== undefined || this.remote()) {
|
|
4001
|
+
this.pageSizeModel.set(size);
|
|
4002
|
+
this.pageIndexModel.set(0);
|
|
4003
|
+
}
|
|
3051
4004
|
}
|
|
3052
4005
|
onPageSizeSelect(event) {
|
|
3053
4006
|
this.changePageSize(Number(event.target.value));
|
|
3054
4007
|
}
|
|
3055
4008
|
/** dx's info text, `Page {0} of {1} ({2} items)` unless `infoText` says otherwise (pagination/info.js). */
|
|
3056
|
-
pageInfo = computed(() => nxePagerInfoText(this.pagerCmp()?.infoText() ??
|
|
4009
|
+
pageInfo = computed(() => nxePagerInfoText(this.pagerCmp()?.infoText() ?? this.messages.t('grid.pager.info'), this.currentPage(), this.remote() ? nxePagerCount(this.pageCount(), this.separators) : this.pageCount(), this.remote() ? nxePagerCount(this.remote().totalCount(), this.separators) : this.displayRows().length), /* @ts-ignore */
|
|
3057
4010
|
...(ngDevMode ? [{ debugName: "pageInfo" }] : /* istanbul ignore next */ []));
|
|
3058
4011
|
// --- Runtime group state (drag-to-group backbone) --------------------------------------------
|
|
3059
4012
|
static fieldArrayEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
|
|
@@ -3123,7 +4076,7 @@ class NxeDataGridComponent {
|
|
|
3123
4076
|
this.groupedFields.update((fields) => [...fields, field]);
|
|
3124
4077
|
if (current?.field === field) {
|
|
3125
4078
|
this.groupSortDir.update((m) => new Map(m).set(field, current.dir));
|
|
3126
|
-
this.
|
|
4079
|
+
this.setSort(null);
|
|
3127
4080
|
}
|
|
3128
4081
|
this.emitGroupIndexChange(this.columns().indexOf(col), null, level);
|
|
3129
4082
|
}
|
|
@@ -3140,8 +4093,7 @@ class NxeDataGridComponent {
|
|
|
3140
4093
|
const dir = col ? this.groupDir(col) : 'asc';
|
|
3141
4094
|
this.groupedFields.set(fields.filter((f) => f !== field));
|
|
3142
4095
|
if (!this.effectiveSort()) {
|
|
3143
|
-
this.
|
|
3144
|
-
this.sort.set({ field, dir });
|
|
4096
|
+
this.setSort({ field, dir });
|
|
3145
4097
|
}
|
|
3146
4098
|
this.emitGroupIndexChange(this.columns().findIndex((c) => c.dataField() === field), level, null);
|
|
3147
4099
|
}
|
|
@@ -3347,7 +4299,7 @@ class NxeDataGridComponent {
|
|
|
3347
4299
|
leadCellCount() {
|
|
3348
4300
|
return (this.groupLevelCount() +
|
|
3349
4301
|
(this.masterDetailEnabled() ? 1 : 0) +
|
|
3350
|
-
(this.
|
|
4302
|
+
(this.selectColumnVisible() ? 1 : 0));
|
|
3351
4303
|
}
|
|
3352
4304
|
// dx numbers every cell of a rows-view row, command cells included (`_setCellAriaAttributes`,
|
|
3353
4305
|
// m_columns_view.js:681-693): the group indents are 1..n, then the master-detail expander, the
|
|
@@ -3378,6 +4330,11 @@ class NxeDataGridComponent {
|
|
|
3378
4330
|
}
|
|
3379
4331
|
this.resizeActive = false;
|
|
3380
4332
|
this.widthOverride.set(map);
|
|
4333
|
+
// dx stops redistributing after a manual resize. The auto-width pass would drop its result
|
|
4334
|
+
// on its next run, but nothing that run tracks has changed, so drop it now - left in place,
|
|
4335
|
+
// it outranks the frozen widths and the column snaps back to its auto width on release.
|
|
4336
|
+
this.autoWidths.set(null);
|
|
4337
|
+
this.lastAutoWidthKey = '';
|
|
3381
4338
|
for (const ch of changes) {
|
|
3382
4339
|
this.onOptionChanged.emit({
|
|
3383
4340
|
name: 'columns',
|
|
@@ -3453,7 +4410,7 @@ class NxeDataGridComponent {
|
|
|
3453
4410
|
/** Total column count including command columns (indents/checkbox/expand/edit) for colspans. */
|
|
3454
4411
|
allColCount = computed(() => (this.groupLevelCount() +
|
|
3455
4412
|
this.visibleColumns().length +
|
|
3456
|
-
(this.
|
|
4413
|
+
(this.selectColumnVisible() ? 1 : 0) +
|
|
3457
4414
|
(this.masterDetailEnabled() ? 1 : 0) +
|
|
3458
4415
|
(this.rowEditCommandVisible() ? 1 : 0)) ||
|
|
3459
4416
|
1, /* @ts-ignore */
|
|
@@ -3876,7 +4833,10 @@ class NxeDataGridComponent {
|
|
|
3876
4833
|
/** dataSource → filter-row filters → header filters (sorting happens in `displayRows`). */
|
|
3877
4834
|
filteredRows = computed(() => {
|
|
3878
4835
|
this.refreshTick();
|
|
3879
|
-
let rows =
|
|
4836
|
+
let rows = this.sourceRows();
|
|
4837
|
+
if (this.remote()) {
|
|
4838
|
+
return rows; // the server has filtered
|
|
4839
|
+
}
|
|
3880
4840
|
const rowFilter = this.filterRowPredicate();
|
|
3881
4841
|
if (rowFilter) {
|
|
3882
4842
|
rows = rows.filter(rowFilter);
|
|
@@ -3901,7 +4861,7 @@ class NxeDataGridComponent {
|
|
|
3901
4861
|
chain.push({ field: col.dataField(), dir: this.groupDir(col) });
|
|
3902
4862
|
}
|
|
3903
4863
|
}
|
|
3904
|
-
const user = this.effectiveSort();
|
|
4864
|
+
const user = this.remote() ? null : this.effectiveSort(); // a remote source arrives sorted
|
|
3905
4865
|
if (user && !chain.some((s) => s.field === user.field)) {
|
|
3906
4866
|
chain.push(user);
|
|
3907
4867
|
}
|
|
@@ -3919,8 +4879,17 @@ class NxeDataGridComponent {
|
|
|
3919
4879
|
});
|
|
3920
4880
|
}, /* @ts-ignore */
|
|
3921
4881
|
...(ngDevMode ? [{ debugName: "displayRows" }] : /* istanbul ignore next */ []));
|
|
3922
|
-
loading = computed(() => this.dataSource() == null
|
|
4882
|
+
loading = computed(() => (this.dataSource() == null || this.remote()?.loading() === true) &&
|
|
4883
|
+
this.loadPanel()?.enabled !== false, /* @ts-ignore */
|
|
3923
4884
|
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
4885
|
+
/** A remote request is in flight over rows already on screen: the overlay, not the empty-body spinner. */
|
|
4886
|
+
remoteBusy = computed(() => !!this.remote() && this.loading() && !this.isEmpty(), /* @ts-ignore */
|
|
4887
|
+
...(ngDevMode ? [{ debugName: "remoteBusy" }] : /* istanbul ignore next */ []));
|
|
4888
|
+
remoteFailed = computed(() => this.remote()?.error() != null, /* @ts-ignore */
|
|
4889
|
+
...(ngDevMode ? [{ debugName: "remoteFailed" }] : /* istanbul ignore next */ []));
|
|
4890
|
+
retryRemote() {
|
|
4891
|
+
this.remote()?.reload();
|
|
4892
|
+
}
|
|
3924
4893
|
/**
|
|
3925
4894
|
* dx `isEmpty()` = `!items().length` (m_data_controller.js:1044-1046), toggled as `dx-empty`
|
|
3926
4895
|
* on the rows view in `_renderCore` (m_rows_view.js:710) whether loading or not.
|
|
@@ -4078,7 +5047,7 @@ class NxeDataGridComponent {
|
|
|
4078
5047
|
*/
|
|
4079
5048
|
inferredTypes = linkedSignal({ ...(ngDevMode ? { debugName: "inferredTypes" } : /* istanbul ignore next */ {}), source: () => {
|
|
4080
5049
|
this.refreshTick();
|
|
4081
|
-
return { rows: this.
|
|
5050
|
+
return { rows: this.sourceRows(), cols: this.columns() };
|
|
4082
5051
|
},
|
|
4083
5052
|
computation: ({ rows, cols }, previous) => {
|
|
4084
5053
|
const out = new Map();
|
|
@@ -4148,11 +5117,29 @@ class NxeDataGridComponent {
|
|
|
4148
5117
|
}
|
|
4149
5118
|
/** The active sort: an explicit user click, else the first column declaring `sortOrder`. */
|
|
4150
5119
|
effectiveSort() {
|
|
5120
|
+
const bound = this.sortModel();
|
|
5121
|
+
if (bound !== undefined) {
|
|
5122
|
+
const first = bound[0];
|
|
5123
|
+
return first ? { field: first.selector, dir: first.desc ? 'desc' : 'asc' } : null;
|
|
5124
|
+
}
|
|
4151
5125
|
if (this.sortInit()) {
|
|
4152
5126
|
return this.sort();
|
|
4153
5127
|
}
|
|
4154
5128
|
const sorted = this.columns().find((c) => c.sortOrder());
|
|
4155
|
-
|
|
5129
|
+
if (sorted) {
|
|
5130
|
+
return { field: sorted.dataField(), dir: sorted.sortOrder() };
|
|
5131
|
+
}
|
|
5132
|
+
const remote = this.remote();
|
|
5133
|
+
const initial = remote ? untracked(() => remote.options().sort[0]) : undefined;
|
|
5134
|
+
return initial ? { field: initial.selector, dir: initial.desc ? 'desc' : 'asc' } : null;
|
|
5135
|
+
}
|
|
5136
|
+
/** A user sort change: the grid's own state, and the controlled `sort` when it is in play. */
|
|
5137
|
+
setSort(next) {
|
|
5138
|
+
this.sortInit.set(true);
|
|
5139
|
+
this.sort.set(next);
|
|
5140
|
+
if (this.sortModel() !== undefined || this.remote()) {
|
|
5141
|
+
this.sortModel.set(next ? [{ selector: next.field, desc: next.dir === 'desc' }] : []);
|
|
5142
|
+
}
|
|
4156
5143
|
}
|
|
4157
5144
|
// --- Rendering helpers -----------------------------------------------------------------------
|
|
4158
5145
|
/**
|
|
@@ -4288,7 +5275,8 @@ class NxeDataGridComponent {
|
|
|
4288
5275
|
return match ? nxeResolveText(displayExpr, match) || String(raw) : String(raw);
|
|
4289
5276
|
}
|
|
4290
5277
|
defaultFormat(col, raw) {
|
|
4291
|
-
|
|
5278
|
+
// The locale is read here so a language switch re-renders date cells (month names).
|
|
5279
|
+
return nxeDefaultFormat(this.colType(col), col.format(), raw, this.separators, this.messages.locale());
|
|
4292
5280
|
}
|
|
4293
5281
|
templateFor(name) {
|
|
4294
5282
|
if (!name) {
|
|
@@ -4702,16 +5690,15 @@ class NxeDataGridComponent {
|
|
|
4702
5690
|
if (!field) {
|
|
4703
5691
|
return;
|
|
4704
5692
|
}
|
|
4705
|
-
this.sortInit.set(true);
|
|
4706
5693
|
const current = this.effectiveSort();
|
|
4707
5694
|
if (!current || current.field !== field) {
|
|
4708
|
-
this.
|
|
5695
|
+
this.setSort({ field, dir: 'asc' });
|
|
4709
5696
|
}
|
|
4710
5697
|
else if (current.dir === 'asc') {
|
|
4711
|
-
this.
|
|
5698
|
+
this.setSort({ field, dir: 'desc' });
|
|
4712
5699
|
}
|
|
4713
5700
|
else {
|
|
4714
|
-
this.
|
|
5701
|
+
this.setSort(null);
|
|
4715
5702
|
}
|
|
4716
5703
|
}
|
|
4717
5704
|
sortDir(col) {
|
|
@@ -4768,6 +5755,12 @@ class NxeDataGridComponent {
|
|
|
4768
5755
|
return this.hoveredIndex() === index;
|
|
4769
5756
|
}
|
|
4770
5757
|
isSelected(row) {
|
|
5758
|
+
if (this.remote()) {
|
|
5759
|
+
return this.selectedKeySet().has(this.rowKey(row));
|
|
5760
|
+
}
|
|
5761
|
+
if (this.recursiveTreeSelection()) {
|
|
5762
|
+
return this.treeCheckState().get(this.rowKey(row)) === 'all';
|
|
5763
|
+
}
|
|
4771
5764
|
return this.treeMode
|
|
4772
5765
|
? this.selectedRowKeys().includes(this.rowKey(row))
|
|
4773
5766
|
: this.selectedSet().has(row);
|
|
@@ -4790,6 +5783,10 @@ class NxeDataGridComponent {
|
|
|
4790
5783
|
this.toggleRow(row);
|
|
4791
5784
|
}
|
|
4792
5785
|
toggleRow(row) {
|
|
5786
|
+
if (this.remote()) {
|
|
5787
|
+
this.toggleRemoteSelection(row);
|
|
5788
|
+
return;
|
|
5789
|
+
}
|
|
4793
5790
|
if (this.treeMode) {
|
|
4794
5791
|
this.toggleTreeSelection(row);
|
|
4795
5792
|
return;
|
|
@@ -4816,8 +5813,12 @@ class NxeDataGridComponent {
|
|
|
4816
5813
|
}
|
|
4817
5814
|
toggleSelectAll(event) {
|
|
4818
5815
|
event.stopPropagation();
|
|
5816
|
+
if (this.remote()) {
|
|
5817
|
+
this.toggleRemoteSelectAll();
|
|
5818
|
+
return;
|
|
5819
|
+
}
|
|
4819
5820
|
if (this.treeMode) {
|
|
4820
|
-
const allKeys =
|
|
5821
|
+
const allKeys = this.sourceRows().map((r) => this.rowKey(r));
|
|
4821
5822
|
if (this.allSelected()) {
|
|
4822
5823
|
this.selectedRowKeys.set([]);
|
|
4823
5824
|
this.emitTreeSelectionChanged([], [], allKeys);
|
|
@@ -4838,6 +5839,39 @@ class NxeDataGridComponent {
|
|
|
4838
5839
|
this.emitSelectionChanged(rows, []);
|
|
4839
5840
|
}
|
|
4840
5841
|
}
|
|
5842
|
+
/**
|
|
5843
|
+
* `clearSelection()` (`scope` null: every selected row) and `deselectAll()` (`scope`: the rows
|
|
5844
|
+
* on show). One `onSelectionChanged` with the dropped keys as deselected, none when nothing
|
|
5845
|
+
* was selected.
|
|
5846
|
+
*/
|
|
5847
|
+
deselectRows(scope) {
|
|
5848
|
+
if (this.remote() || this.treeMode) {
|
|
5849
|
+
const previous = this.selectedRowKeys();
|
|
5850
|
+
const drop = scope ? new Set(scope.map((r) => this.rowKey(r))) : null;
|
|
5851
|
+
const removed = drop ? previous.filter((k) => drop.has(k)) : previous;
|
|
5852
|
+
if (!removed.length) {
|
|
5853
|
+
return;
|
|
5854
|
+
}
|
|
5855
|
+
const next = drop ? previous.filter((k) => !drop.has(k)) : [];
|
|
5856
|
+
this.selectedRowKeys.set(next);
|
|
5857
|
+
if (this.remote()) {
|
|
5858
|
+
this.emitRemoteSelectionChanged(next, [], removed);
|
|
5859
|
+
}
|
|
5860
|
+
else {
|
|
5861
|
+
this.emitTreeSelectionChanged(next, [], removed);
|
|
5862
|
+
}
|
|
5863
|
+
return;
|
|
5864
|
+
}
|
|
5865
|
+
const previous = this.selectedSet();
|
|
5866
|
+
const removed = scope ? scope.filter((r) => previous.has(r)) : [...previous];
|
|
5867
|
+
if (!removed.length) {
|
|
5868
|
+
return;
|
|
5869
|
+
}
|
|
5870
|
+
const next = new Set(previous);
|
|
5871
|
+
removed.forEach((r) => next.delete(r));
|
|
5872
|
+
this.selectedSet.set(next);
|
|
5873
|
+
this.emitSelectionChanged([], removed);
|
|
5874
|
+
}
|
|
4841
5875
|
emitSelectionChanged(currentSelected, currentDeselected) {
|
|
4842
5876
|
const data = this.selectedRowsData();
|
|
4843
5877
|
this.onSelectionChanged.emit({
|
|
@@ -4850,26 +5884,26 @@ class NxeDataGridComponent {
|
|
|
4850
5884
|
});
|
|
4851
5885
|
}
|
|
4852
5886
|
rowKeys(rows) {
|
|
4853
|
-
const key = this.
|
|
5887
|
+
const key = this.keyField();
|
|
4854
5888
|
return key ? rows.map((r) => r?.[key]) : rows;
|
|
4855
5889
|
}
|
|
4856
5890
|
// --- Filter row (dxo-filter-row): typed editors + dx operation-chooser menu ------------------
|
|
4857
5891
|
static STRING_OPS = [
|
|
4858
|
-
{ op: 'contains', label: '
|
|
4859
|
-
{ op: 'notcontains', label: '
|
|
4860
|
-
{ op: 'startswith', label: '
|
|
4861
|
-
{ op: 'endswith', label: '
|
|
4862
|
-
{ op: '=', label: '
|
|
4863
|
-
{ op: '<>', label: '
|
|
5892
|
+
{ op: 'contains', label: 'grid.op.contains' },
|
|
5893
|
+
{ op: 'notcontains', label: 'grid.op.notContains' },
|
|
5894
|
+
{ op: 'startswith', label: 'grid.op.startsWith' },
|
|
5895
|
+
{ op: 'endswith', label: 'grid.op.endsWith' },
|
|
5896
|
+
{ op: '=', label: 'grid.op.equals' },
|
|
5897
|
+
{ op: '<>', label: 'grid.op.notEquals' },
|
|
4864
5898
|
];
|
|
4865
5899
|
static SCALAR_OPS = [
|
|
4866
|
-
{ op: '=', label: '
|
|
4867
|
-
{ op: '<>', label: '
|
|
4868
|
-
{ op: '<', label: '
|
|
4869
|
-
{ op: '>', label: '
|
|
4870
|
-
{ op: '<=', label: '
|
|
4871
|
-
{ op: '>=', label: '
|
|
4872
|
-
{ op: 'between', label: '
|
|
5900
|
+
{ op: '=', label: 'grid.op.equals' },
|
|
5901
|
+
{ op: '<>', label: 'grid.op.notEquals' },
|
|
5902
|
+
{ op: '<', label: 'grid.op.lessThan' },
|
|
5903
|
+
{ op: '>', label: 'grid.op.greaterThan' },
|
|
5904
|
+
{ op: '<=', label: 'grid.op.lessThanOrEqual' },
|
|
5905
|
+
{ op: '>=', label: 'grid.op.greaterThanOrEqual' },
|
|
5906
|
+
{ op: 'between', label: 'grid.op.between' },
|
|
4873
5907
|
];
|
|
4874
5908
|
static OP_ICON_SLUGS = {
|
|
4875
5909
|
contains: 'contains',
|
|
@@ -4927,6 +5961,7 @@ class NxeDataGridComponent {
|
|
|
4927
5961
|
return NxeDataGridComponent.OP_ICON_SLUGS[op];
|
|
4928
5962
|
}
|
|
4929
5963
|
setFilterRowText(col, text, second = false) {
|
|
5964
|
+
this.remoteTyping = true;
|
|
4930
5965
|
const field = col.dataField();
|
|
4931
5966
|
const next = new Map(this.filterRowState());
|
|
4932
5967
|
const prev = next.get(field) ?? { op: this.frDefaultOp(col), text: '' };
|
|
@@ -4962,9 +5997,7 @@ class NxeDataGridComponent {
|
|
|
4962
5997
|
if (!col) {
|
|
4963
5998
|
return [];
|
|
4964
5999
|
}
|
|
4965
|
-
return this.frEditorKind(col) === 'text'
|
|
4966
|
-
? NxeDataGridComponent.STRING_OPS
|
|
4967
|
-
: NxeDataGridComponent.SCALAR_OPS;
|
|
6000
|
+
return (this.frEditorKind(col) === 'text' ? NxeDataGridComponent.STRING_OPS : NxeDataGridComponent.SCALAR_OPS).map(({ op, label }) => ({ op, label: this.messages.t(label) }));
|
|
4968
6001
|
}
|
|
4969
6002
|
selectFilterOp(op) {
|
|
4970
6003
|
const col = this.frMenuColumn();
|
|
@@ -5017,7 +6050,7 @@ class NxeDataGridComponent {
|
|
|
5017
6050
|
}
|
|
5018
6051
|
/** dx does NOT cascade header-filter lists for client arrays (bright audit V6). */
|
|
5019
6052
|
hfBaseRows() {
|
|
5020
|
-
return
|
|
6053
|
+
return this.sourceRows();
|
|
5021
6054
|
}
|
|
5022
6055
|
hfBoolLabel(col, raw) {
|
|
5023
6056
|
if (col.lookup()) {
|
|
@@ -5102,14 +6135,15 @@ class NxeDataGridComponent {
|
|
|
5102
6135
|
this.hfOverlayRef?.dispose();
|
|
5103
6136
|
this.frMenuOverlayRef?.dispose();
|
|
5104
6137
|
this.resizeObserver?.disconnect();
|
|
6138
|
+
this.wrapped?.cancel();
|
|
5105
6139
|
}
|
|
5106
6140
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDataGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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 });
|
|
6141
|
+
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 }, resizeFillsGridInput: { classPropertyName: "resizeFillsGridInput", publicName: "resizeFillsGrid", isSignal: true, isRequired: false, transformFunction: null }, remoteOperations: { classPropertyName: "remoteOperations", publicName: "remoteOperations", isSignal: true, isRequired: false, transformFunction: null }, pageIndexModel: { classPropertyName: "pageIndexModel", publicName: "pageIndex", isSignal: true, isRequired: false, transformFunction: null }, pageSizeModel: { classPropertyName: "pageSizeModel", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, sortModel: { classPropertyName: "sortModel", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", 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: { pageIndexModel: "pageIndexChange", pageSizeModel: "pageSizeChange", sortModel: "sortChange", optionsChange: "optionsChange", 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\">{{ t('grid.groupPanel') }}</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-remote-busy]=\"remoteBusy()\"\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-busy]=\"remoteBusy() ? 'true' : null\"\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 (selectColumnVisible()) {\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 (selectColumnVisible()) {\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 [class.nxe-tree-select-all]=\"treeSelectAll() && cell.leafIndex === 0\"\r\n (pointerdown)=\"onHeaderPointerDown(cell.column, cell.leafIndex, $event)\"\r\n (click)=\"onHeaderClick(cell.column)\">\r\n @if (treeSelectAll() && cell.leafIndex === 0) {\r\n <!-- dx's tree list puts select-all in the first column's header. -->\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-label]=\"t('common.selectAll')\"\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 (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"toggleSelectAll($event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n }\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 (selectColumnVisible()) {\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=\"\">{{ t('common.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=\"\">{{ t('common.all') }}</option>\r\n <option value=\"true\">{{ t('grid.filterRow.true') }}</option>\r\n <option value=\"false\">{{ t('grid.filterRow.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 [attr.title]=\"t('grid.filterRow.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 ? t('grid.collapsedRow') : t('grid.expandedRow')\">\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 ? t('grid.expand') : t('grid.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 (selectColumnVisible()) {\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) ? t('grid.collapse') : t('grid.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 (selectColumnVisible()) {\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 ? t('grid.editableCell')\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\" [class.nxe-tree-icons-select]=\"showCheckboxes()\">\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 <!-- dx draws a tree list's row checkbox here, in the icon\r\n container after the toggle, not in a command column. -->\r\n @if (showCheckboxes()) {\r\n <span\r\n class=\"nxe-select-checkbox nxe-grid-checkbox nxe-checkbox nxe-datagrid-checkbox-size nxe-tree-checkbox\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"isTreeIndeterminate(item.row) ? 'mixed' : isSelected(item.row)\"\r\n [class.nxe-checked]=\"isSelected(item.row)\"\r\n [class.nxe-indeterminate]=\"isTreeIndeterminate(item.row)\"\r\n (click)=\"onCheckboxClick(item.row, $event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </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)\">{{ t('grid.save') }}</a>\r\n <a class=\"nxe-link nxe-link-cancel\" (click)=\"cancelRowEdit($event)\">{{ t('grid.cancel') }}</a>\r\n } @else {\r\n <a class=\"nxe-link nxe-link-edit\" (click)=\"startRowEdit(item.row, $event)\">{{ t('grid.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 (selectColumnVisible()) {\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 (selectColumnVisible()) {\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 (remoteBusy()) {\r\n <!-- Server-side data: a request is in flight over the rows already on screen. -->\r\n <div class=\"nxe-datagrid-loadpanel nxe-datagrid-remote-busy\" role=\"status\" [attr.aria-label]=\"t('grid.remote.loading')\">\r\n <span class=\"nxe-grid-spinner\"></span>\r\n </div>\r\n }\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 (remoteFailed()) {\r\n <div class=\"nxe-datagrid-remote-error\" role=\"alert\">\r\n <span class=\"nxe-datagrid-remote-error-text\">{{ t('grid.remote.error') }}</span>\r\n <button type=\"button\" class=\"nxe-datagrid-remote-retry\" (click)=\"retryRemote()\">{{ t('grid.remote.retry') }}</button>\r\n </div>\r\n } @else 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() ?? t('grid.pager.navigation')\">\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\" [attr.aria-label]=\"t('grid.pager.pageSize')\" (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]=\"t('grid.pager.itemsPerPage', 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 [attr.aria-label]=\"t('grid.pager.previous')\"\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]=\"t('grid.pager.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 [attr.aria-label]=\"t('grid.pager.next')\"\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\">{{ t('grid.filterRow.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;cursor:default;color:var(--nxe-ui-text, #333)}.nxe-data-grid .nxe-gridbase-container{display:flex;flex-direction:column;height:100%;line-height:inherit;min-height:inherit;max-height:inherit}.nxe-data-grid .nxe-gridbase-container>*{flex:0 0 auto}.nxe-data-grid .nxe-datagrid-rowsview{position:relative;overflow:auto;flex:1 1 auto;min-height:0;background-color:var(--nxe-ui-surface, #fff)}.nxe-data-grid .nxe-datagrid-content{display:block;position:relative;min-height:100%}.nxe-data-grid .nxe-datagrid-rowsview .nxe-datagrid-content{overflow-anchor:none}.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:var(--nxe-ui-neutral-600, #757575);font-weight:400;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none;touch-action:pinch-zoom;background:var(--nxe-ui-surface, #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:var(--nxe-ui-surface, #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 var(--nxe-ui-neutral-150, #e6e6e6);border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-neutral-150, #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:var(--nxe-ui-text, #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:var(--nxe-ui-scrim, rgba(255, 255, 255, .5));z-index:2}.nxe-data-grid .nxe-datagrid-remote-error{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--nxe-ui-space-2, 8px);color:var(--nxe-ui-invalid, #d9534f)}.nxe-data-grid .nxe-datagrid-remote-retry{padding:var(--nxe-ui-space-1, 4px) var(--nxe-ui-space-3, 12px);border:1px solid var(--nxe-ui-border, #ddd);border-radius:var(--nxe-ui-radius, 4px);background:var(--nxe-ui-surface, #fff);color:var(--nxe-ui-text, #333);font:inherit;cursor:pointer}.nxe-data-grid .nxe-grid-spinner{width:32px;height:32px;border:3px solid var(--nxe-ui-spinner-track, rgba(48, 55, 76, .2));border-top-color:var(--nxe-ui-ink-strong, #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;scale:var(--nxe-ui-grid-indicator-scale, 1);padding-top:var(--nxe-ui-grid-indicator-offset, 0px)}.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:var(--nxe-ui-neutral-600, #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:var(--nxe-ui-neutral-600, #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:var(--nxe-ui-text, #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-width:2px;outline-style:solid;outline-color:var(--nxe-ui-focus-ring-color, #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:var(--nxe-ui-text, #333);border-color:transparent;background-color:var(--nxe-ui-neutral-230, #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 var(--nxe-ui-border, #ddd);border-radius:3px;font:inherit}.nxe-data-grid .nxe-datagrid-header-panel{padding:.5rem .75rem;text-align:left;border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-border, #ddd)}.nxe-data-grid .nxe-datagrid-group-panel{display:flex;align-items:center;width:100%;min-height:36px;white-space:nowrap;font-size:var(--nxe-ui-font-size, 14px);touch-action:pinch-zoom}.nxe-data-grid .nxe-datagrid-group-panel .nxe-group-panel-message{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nxe-ui-neutral-600, #757575);font-weight:400;padding:7px;border-top:1px solid transparent;border-bottom:1px solid transparent;line-height:1.2858}.nxe-data-grid .nxe-datagrid-group-panel .nxe-group-panel-item{display:inline-block;min-width:30px;white-space:nowrap;margin-right:10px;color:var(--nxe-ui-neutral-600, #757575);font-weight:400;border:1px solid var(--nxe-ui-border, #ddd);padding:7px}.nxe-data-grid .nxe-datagrid-group-panel .nxe-block-separator{position:relative;margin-right:10px;border-radius:3px;background-color:var(--nxe-ui-accent, #337ab7)}.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-icons-select{position:relative;padding-right:23px}.nxe-data-grid .nxe-tree-icons .nxe-tree-checkbox{position:absolute;top:50%;right:7px;margin:-8px 0 0}.nxe-data-grid .nxe-header-row>.nxe-tree-select-all{position:relative;padding-left:44px}.nxe-data-grid .nxe-header-row>.nxe-tree-select-all>.nxe-grid-checkbox{position:absolute;left:21px;margin:1px 0 0}.nxe-data-grid .nxe-tree-expander{height:18px;margin-right:4px;color:var(--nxe-ui-neutral-450, #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:var(--nxe-ui-neutral-60, #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:var(--nxe-ui-neutral-450, #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{position:relative}.nxe-data-grid .nxe-datagrid-total-footer>tr>td,.nxe-data-grid tfoot.nxe-datagrid-total-footer td{border-top:1px solid var(--nxe-ui-border, #ddd);padding:6px 4px;font-weight:700;color:var(--nxe-ui-neutral-450, rgb(149, 149, 149))}.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 var(--nxe-ui-border, #ddd);border-radius:2px;font:inherit;color:var(--nxe-ui-text, #333);outline:none}.nxe-data-grid .nxe-grid-filter-input:focus{border-color:var(--nxe-ui-ink-strong, #30374c)}.nxe-data-grid .nxe-datagrid-filter-row select.nxe-grid-filter-input{appearance:none;background:var(--nxe-ui-surface, #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 var(--nxe-ui-border, #ddd);border-radius:2px;background:var(--nxe-ui-surface, #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:var(--nxe-ui-neutral-80, #f4f4f4);background-color:var(--nxe-ui-surface, #fff)}@keyframes nxe-grid-spin{to{transform:rotate(360deg)}}:where(.nxe-data-grid .nxe-row)>td{padding:var(--nxe-ui-grid-cell-padding, 7px);vertical-align:var(--nxe-ui-grid-cell-vertical-align, middle)}:where(.nxe-data-grid .nxe-row)>td:first-child{padding-left:var(--nxe-ui-grid-edge-padding, var(--nxe-ui-grid-cell-padding, 7px))}:where(.nxe-data-grid .nxe-row)>td:last-child{padding-right:var(--nxe-ui-grid-edge-padding, var(--nxe-ui-grid-cell-padding, 7px))}:where(.nxe-data-grid) .nxe-header-row{background-color:var(--nxe-ui-grid-header-bg, transparent);color:var(--nxe-ui-grid-header-color, inherit)}.nxe-datagrid-rowsview .nxe-row{border-top:1px solid transparent;border-bottom:1px solid transparent}.nxe-datagrid-rowsview .nxe-datagrid-table .nxe-row.nxe-freespace-row,.nxe-datagrid-rowsview .nxe-datagrid-table .nxe-row.nxe-virtual-row{border-top:0;border-bottom:0}.nxe-datagrid-rowsview .nxe-datagrid-table .nxe-row.nxe-freespace-row>td,.nxe-datagrid-rowsview .nxe-datagrid-table .nxe-row.nxe-virtual-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:var(--nxe-ui-neutral-150, #e6e6e6);color:var(--nxe-ui-text, #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:var(--nxe-ui-neutral-50, #fafafa)}.nxe-grid-cell-editor{width:100%;box-sizing:border-box;border:1px solid var(--nxe-ui-accent, #337ab7);padding:2px 4px;font:inherit;background:var(--nxe-ui-surface, #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:var(--nxe-ui-accent, #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 var(--nxe-ui-border, #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:var(--nxe-ui-neutral-500, #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:var(--nxe-ui-surface, #fff);border:1px solid var(--nxe-ui-border, #ddd);box-shadow:var(--nxe-ui-shadow-sm, 0 2px 6px rgba(0, 0, 0, .15));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:var(--nxe-ui-text, #333)}.nxe-fr-op-menu .nxe-menu-item:hover{background:var(--nxe-ui-neutral-100, #f0f0f0)}.nxe-fr-op-menu .nxe-menu-item.nxe-menu-item-selected{background:var(--nxe-ui-neutral-150, #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:var(--nxe-ui-accent, #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 });
|
|
5108
6142
|
}
|
|
5109
6143
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDataGridComponent, decorators: [{
|
|
5110
6144
|
type: Component,
|
|
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: [{
|
|
6145
|
+
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\">{{ t('grid.groupPanel') }}</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-remote-busy]=\"remoteBusy()\"\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-busy]=\"remoteBusy() ? 'true' : null\"\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 (selectColumnVisible()) {\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 (selectColumnVisible()) {\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 [class.nxe-tree-select-all]=\"treeSelectAll() && cell.leafIndex === 0\"\r\n (pointerdown)=\"onHeaderPointerDown(cell.column, cell.leafIndex, $event)\"\r\n (click)=\"onHeaderClick(cell.column)\">\r\n @if (treeSelectAll() && cell.leafIndex === 0) {\r\n <!-- dx's tree list puts select-all in the first column's header. -->\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-label]=\"t('common.selectAll')\"\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 (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"toggleSelectAll($event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </span>\r\n }\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 (selectColumnVisible()) {\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=\"\">{{ t('common.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=\"\">{{ t('common.all') }}</option>\r\n <option value=\"true\">{{ t('grid.filterRow.true') }}</option>\r\n <option value=\"false\">{{ t('grid.filterRow.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 [attr.title]=\"t('grid.filterRow.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 ? t('grid.collapsedRow') : t('grid.expandedRow')\">\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 ? t('grid.expand') : t('grid.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 (selectColumnVisible()) {\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) ? t('grid.collapse') : t('grid.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 (selectColumnVisible()) {\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 ? t('grid.editableCell')\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\" [class.nxe-tree-icons-select]=\"showCheckboxes()\">\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 <!-- dx draws a tree list's row checkbox here, in the icon\r\n container after the toggle, not in a command column. -->\r\n @if (showCheckboxes()) {\r\n <span\r\n class=\"nxe-select-checkbox nxe-grid-checkbox nxe-checkbox nxe-datagrid-checkbox-size nxe-tree-checkbox\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"isTreeIndeterminate(item.row) ? 'mixed' : isSelected(item.row)\"\r\n [class.nxe-checked]=\"isSelected(item.row)\"\r\n [class.nxe-indeterminate]=\"isTreeIndeterminate(item.row)\"\r\n (click)=\"onCheckboxClick(item.row, $event)\">\r\n <span class=\"nxe-grid-checkbox-box\"></span>\r\n </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)\">{{ t('grid.save') }}</a>\r\n <a class=\"nxe-link nxe-link-cancel\" (click)=\"cancelRowEdit($event)\">{{ t('grid.cancel') }}</a>\r\n } @else {\r\n <a class=\"nxe-link nxe-link-edit\" (click)=\"startRowEdit(item.row, $event)\">{{ t('grid.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 (selectColumnVisible()) {\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 (selectColumnVisible()) {\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 (remoteBusy()) {\r\n <!-- Server-side data: a request is in flight over the rows already on screen. -->\r\n <div class=\"nxe-datagrid-loadpanel nxe-datagrid-remote-busy\" role=\"status\" [attr.aria-label]=\"t('grid.remote.loading')\">\r\n <span class=\"nxe-grid-spinner\"></span>\r\n </div>\r\n }\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 (remoteFailed()) {\r\n <div class=\"nxe-datagrid-remote-error\" role=\"alert\">\r\n <span class=\"nxe-datagrid-remote-error-text\">{{ t('grid.remote.error') }}</span>\r\n <button type=\"button\" class=\"nxe-datagrid-remote-retry\" (click)=\"retryRemote()\">{{ t('grid.remote.retry') }}</button>\r\n </div>\r\n } @else 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() ?? t('grid.pager.navigation')\">\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\" [attr.aria-label]=\"t('grid.pager.pageSize')\" (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]=\"t('grid.pager.itemsPerPage', 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 [attr.aria-label]=\"t('grid.pager.previous')\"\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]=\"t('grid.pager.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 [attr.aria-label]=\"t('grid.pager.next')\"\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\">{{ t('grid.filterRow.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;cursor:default;color:var(--nxe-ui-text, #333)}.nxe-data-grid .nxe-gridbase-container{display:flex;flex-direction:column;height:100%;line-height:inherit;min-height:inherit;max-height:inherit}.nxe-data-grid .nxe-gridbase-container>*{flex:0 0 auto}.nxe-data-grid .nxe-datagrid-rowsview{position:relative;overflow:auto;flex:1 1 auto;min-height:0;background-color:var(--nxe-ui-surface, #fff)}.nxe-data-grid .nxe-datagrid-content{display:block;position:relative;min-height:100%}.nxe-data-grid .nxe-datagrid-rowsview .nxe-datagrid-content{overflow-anchor:none}.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:var(--nxe-ui-neutral-600, #757575);font-weight:400;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none;touch-action:pinch-zoom;background:var(--nxe-ui-surface, #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:var(--nxe-ui-surface, #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 var(--nxe-ui-neutral-150, #e6e6e6);border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-neutral-150, #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:var(--nxe-ui-text, #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:var(--nxe-ui-scrim, rgba(255, 255, 255, .5));z-index:2}.nxe-data-grid .nxe-datagrid-remote-error{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--nxe-ui-space-2, 8px);color:var(--nxe-ui-invalid, #d9534f)}.nxe-data-grid .nxe-datagrid-remote-retry{padding:var(--nxe-ui-space-1, 4px) var(--nxe-ui-space-3, 12px);border:1px solid var(--nxe-ui-border, #ddd);border-radius:var(--nxe-ui-radius, 4px);background:var(--nxe-ui-surface, #fff);color:var(--nxe-ui-text, #333);font:inherit;cursor:pointer}.nxe-data-grid .nxe-grid-spinner{width:32px;height:32px;border:3px solid var(--nxe-ui-spinner-track, rgba(48, 55, 76, .2));border-top-color:var(--nxe-ui-ink-strong, #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;scale:var(--nxe-ui-grid-indicator-scale, 1);padding-top:var(--nxe-ui-grid-indicator-offset, 0px)}.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:var(--nxe-ui-neutral-600, #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:var(--nxe-ui-neutral-600, #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:var(--nxe-ui-text, #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-width:2px;outline-style:solid;outline-color:var(--nxe-ui-focus-ring-color, #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:var(--nxe-ui-text, #333);border-color:transparent;background-color:var(--nxe-ui-neutral-230, #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 var(--nxe-ui-border, #ddd);border-radius:3px;font:inherit}.nxe-data-grid .nxe-datagrid-header-panel{padding:.5rem .75rem;text-align:left;border-bottom:var(--nxe-ui-grid-line-width, 1px) solid var(--nxe-ui-border, #ddd)}.nxe-data-grid .nxe-datagrid-group-panel{display:flex;align-items:center;width:100%;min-height:36px;white-space:nowrap;font-size:var(--nxe-ui-font-size, 14px);touch-action:pinch-zoom}.nxe-data-grid .nxe-datagrid-group-panel .nxe-group-panel-message{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nxe-ui-neutral-600, #757575);font-weight:400;padding:7px;border-top:1px solid transparent;border-bottom:1px solid transparent;line-height:1.2858}.nxe-data-grid .nxe-datagrid-group-panel .nxe-group-panel-item{display:inline-block;min-width:30px;white-space:nowrap;margin-right:10px;color:var(--nxe-ui-neutral-600, #757575);font-weight:400;border:1px solid var(--nxe-ui-border, #ddd);padding:7px}.nxe-data-grid .nxe-datagrid-group-panel .nxe-block-separator{position:relative;margin-right:10px;border-radius:3px;background-color:var(--nxe-ui-accent, #337ab7)}.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-icons-select{position:relative;padding-right:23px}.nxe-data-grid .nxe-tree-icons .nxe-tree-checkbox{position:absolute;top:50%;right:7px;margin:-8px 0 0}.nxe-data-grid .nxe-header-row>.nxe-tree-select-all{position:relative;padding-left:44px}.nxe-data-grid .nxe-header-row>.nxe-tree-select-all>.nxe-grid-checkbox{position:absolute;left:21px;margin:1px 0 0}.nxe-data-grid .nxe-tree-expander{height:18px;margin-right:4px;color:var(--nxe-ui-neutral-450, #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:var(--nxe-ui-neutral-60, #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:var(--nxe-ui-neutral-450, #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{position:relative}.nxe-data-grid .nxe-datagrid-total-footer>tr>td,.nxe-data-grid tfoot.nxe-datagrid-total-footer td{border-top:1px solid var(--nxe-ui-border, #ddd);padding:6px 4px;font-weight:700;color:var(--nxe-ui-neutral-450, rgb(149, 149, 149))}.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 var(--nxe-ui-border, #ddd);border-radius:2px;font:inherit;color:var(--nxe-ui-text, #333);outline:none}.nxe-data-grid .nxe-grid-filter-input:focus{border-color:var(--nxe-ui-ink-strong, #30374c)}.nxe-data-grid .nxe-datagrid-filter-row select.nxe-grid-filter-input{appearance:none;background:var(--nxe-ui-surface, #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 var(--nxe-ui-border, #ddd);border-radius:2px;background:var(--nxe-ui-surface, #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:var(--nxe-ui-neutral-80, #f4f4f4);background-color:var(--nxe-ui-surface, #fff)}@keyframes nxe-grid-spin{to{transform:rotate(360deg)}}:where(.nxe-data-grid .nxe-row)>td{padding:var(--nxe-ui-grid-cell-padding, 7px);vertical-align:var(--nxe-ui-grid-cell-vertical-align, middle)}:where(.nxe-data-grid .nxe-row)>td:first-child{padding-left:var(--nxe-ui-grid-edge-padding, var(--nxe-ui-grid-cell-padding, 7px))}:where(.nxe-data-grid .nxe-row)>td:last-child{padding-right:var(--nxe-ui-grid-edge-padding, var(--nxe-ui-grid-cell-padding, 7px))}:where(.nxe-data-grid) .nxe-header-row{background-color:var(--nxe-ui-grid-header-bg, transparent);color:var(--nxe-ui-grid-header-color, inherit)}.nxe-datagrid-rowsview .nxe-row{border-top:1px solid transparent;border-bottom:1px solid transparent}.nxe-datagrid-rowsview .nxe-datagrid-table .nxe-row.nxe-freespace-row,.nxe-datagrid-rowsview .nxe-datagrid-table .nxe-row.nxe-virtual-row{border-top:0;border-bottom:0}.nxe-datagrid-rowsview .nxe-datagrid-table .nxe-row.nxe-freespace-row>td,.nxe-datagrid-rowsview .nxe-datagrid-table .nxe-row.nxe-virtual-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:var(--nxe-ui-neutral-150, #e6e6e6);color:var(--nxe-ui-text, #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:var(--nxe-ui-neutral-50, #fafafa)}.nxe-grid-cell-editor{width:100%;box-sizing:border-box;border:1px solid var(--nxe-ui-accent, #337ab7);padding:2px 4px;font:inherit;background:var(--nxe-ui-surface, #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:var(--nxe-ui-accent, #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 var(--nxe-ui-border, #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:var(--nxe-ui-neutral-500, #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:var(--nxe-ui-surface, #fff);border:1px solid var(--nxe-ui-border, #ddd);box-shadow:var(--nxe-ui-shadow-sm, 0 2px 6px rgba(0, 0, 0, .15));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:var(--nxe-ui-text, #333)}.nxe-fr-op-menu .nxe-menu-item:hover{background:var(--nxe-ui-neutral-100, #f0f0f0)}.nxe-fr-op-menu .nxe-menu-item.nxe-menu-item-selected{background:var(--nxe-ui-neutral-150, #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:var(--nxe-ui-accent, #337ab7);z-index:10000;pointer-events:none}\n"] }]
|
|
6146
|
+
}], 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 }] }], resizeFillsGridInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizeFillsGrid", required: false }] }], remoteOperations: [{ type: i0.Input, args: [{ isSignal: true, alias: "remoteOperations", required: false }] }], pageIndexModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageIndex", required: false }] }, { type: i0.Output, args: ["pageIndexChange"] }], pageSizeModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }, { type: i0.Output, args: ["pageSizeChange"] }], sortModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }, { type: i0.Output, args: ["sortChange"] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], optionsChange: [{ type: i0.Output, args: ["optionsChange"] }], 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: [{
|
|
5113
6147
|
type: Output
|
|
5114
6148
|
}], onRowPrepared: [{
|
|
5115
6149
|
type: Output
|
|
@@ -5121,5 +6155,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
5121
6155
|
* Generated bundle index. Do not edit.
|
|
5122
6156
|
*/
|
|
5123
6157
|
|
|
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 };
|
|
6158
|
+
export { NXE_HF_BLANKS, NXE_REMOTE_DEFAULT_PAGE_SIZE, NxeColumnComponent, NxeDataGridComponent, NxeDragGesture, NxeEditingComponent, NxeFilterRowComponent, NxeGestureClickSuppression, NxeGridDragController, NxeGroupItemComponent, NxeGroupPanelComponent, NxeGroupingComponent, NxeHeaderFilterComponent, NxeHeaderFilterModel, NxeHeaderFilterPanelComponent, NxeMasterDetailComponent, NxePagerComponent, NxePaginatorComponent, NxePagingComponent, NxeRemoteDataSource, NxeScrollingComponent, NxeSelectionComponent, NxeSummaryComponent, NxeTotalItemComponent, exportDataGrid, exportNxeGridToExcel, nxeDatePattern, nxeDefaultFormat, nxeExcelNumFmt, nxeExcelNumberMask, nxeFilterAnd, nxeFilterOr, nxeFlattenFilter, nxeFormatNumberValue, nxeHfKey, nxeIsStoreLike, nxeLoadOptions, nxeNumberMask, nxeNumberValue, nxeSameQuery, nxeValueDataType, openNxeHeaderFilterOverlay };
|
|
5125
6159
|
//# sourceMappingURL=nexterajs-ui-grid.mjs.map
|