@mmlogic/components 0.3.11 → 0.4.1
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/dist/cjs/{index-BPj2cBXs.js → index-CAzwGt9u.js} +38 -0
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/mosterdcomponents.cjs.js +2 -2
- package/dist/cjs/mrd-boolean-field_23.cjs.entry.js +1313 -1088
- package/dist/collection/collection-manifest.json +23 -23
- package/dist/collection/components/{mrd-document-container/mrd-document-container.scss → documents/mrd-document-container/mrd-document-container.css} +3 -3
- package/dist/collection/components/{mrd-document-container → documents/mrd-document-container}/mrd-document-container.js +7 -7
- package/dist/collection/components/documents/mrd-document-list/folder-tree.js +109 -0
- package/dist/collection/components/{mrd-document-list/mrd-document-list.scss → documents/mrd-document-list/mrd-document-list.css} +6 -4
- package/dist/collection/components/{mrd-document-list → documents/mrd-document-list}/mrd-document-list.js +83 -240
- package/dist/collection/components/documents/mrd-document-list/tree-view.js +75 -0
- package/dist/collection/components/{mrd-document-view/mrd-document-view.scss → documents/mrd-document-view/mrd-document-view.css} +1 -1
- package/dist/collection/components/{mrd-document-view → documents/mrd-document-view}/mrd-document-view.js +4 -4
- package/dist/collection/components/fields/field-helpers.js +17 -0
- package/dist/collection/components/fields/file-upload-common.js +38 -0
- package/dist/collection/components/{mrd-boolean-field/mrd-boolean-field.scss → fields/mrd-boolean-field/mrd-boolean-field.css} +3 -3
- package/dist/collection/components/{mrd-boolean-field → fields/mrd-boolean-field}/mrd-boolean-field.js +1 -1
- package/dist/collection/components/{mrd-currency-field/mrd-currency-field.scss → fields/mrd-currency-field/mrd-currency-field.css} +11 -9
- package/dist/collection/components/{mrd-currency-field → fields/mrd-currency-field}/mrd-currency-field.js +7 -13
- package/dist/collection/components/{mrd-date-field/mrd-date-field.scss → fields/mrd-date-field/mrd-date-field.css} +8 -10
- package/dist/collection/components/{mrd-date-field → fields/mrd-date-field}/mrd-date-field.js +3 -10
- package/dist/collection/components/{mrd-datetime-field/mrd-datetime-field.scss → fields/mrd-datetime-field/mrd-datetime-field.css} +8 -10
- package/dist/collection/components/{mrd-datetime-field → fields/mrd-datetime-field}/mrd-datetime-field.js +3 -10
- package/dist/collection/components/{mrd-email-field/mrd-email-field.scss → fields/mrd-email-field/mrd-email-field.css} +2 -11
- package/dist/collection/components/{mrd-email-field → fields/mrd-email-field}/mrd-email-field.js +6 -13
- package/dist/collection/components/{mrd-file-field/mrd-file-field.scss → fields/mrd-file-field/mrd-file-field.css} +19 -24
- package/dist/collection/components/{mrd-file-field → fields/mrd-file-field}/mrd-file-field.js +13 -24
- package/dist/collection/components/{mrd-hyperlink-field/mrd-hyperlink-field.scss → fields/mrd-hyperlink-field/mrd-hyperlink-field.css} +7 -16
- package/dist/collection/components/{mrd-hyperlink-field → fields/mrd-hyperlink-field}/mrd-hyperlink-field.js +9 -15
- package/dist/collection/components/{mrd-image-field/mrd-image-field.scss → fields/mrd-image-field/mrd-image-field.css} +15 -24
- package/dist/collection/components/{mrd-image-field → fields/mrd-image-field}/mrd-image-field.js +13 -28
- package/dist/collection/components/{mrd-list-field/mrd-list-field.scss → fields/mrd-list-field/mrd-list-field.css} +18 -21
- package/dist/collection/components/{mrd-list-field → fields/mrd-list-field}/mrd-list-field.js +10 -25
- package/dist/collection/components/{mrd-longtext-field/mrd-longtext-field.scss → fields/mrd-longtext-field/mrd-longtext-field.css} +21 -18
- package/dist/collection/components/{mrd-longtext-field → fields/mrd-longtext-field}/mrd-longtext-field.js +3 -10
- package/dist/collection/components/{mrd-number-field/mrd-number-field.scss → fields/mrd-number-field/mrd-number-field.css} +9 -10
- package/dist/collection/components/{mrd-number-field → fields/mrd-number-field}/mrd-number-field.js +7 -6
- package/dist/collection/components/{mrd-relation-field/mrd-relation-field.scss → fields/mrd-relation-field/mrd-relation-field.css} +61 -59
- package/dist/collection/components/{mrd-relation-field → fields/mrd-relation-field}/mrd-relation-field.js +16 -26
- package/dist/collection/components/{mrd-secret-field/mrd-secret-field.scss → fields/mrd-secret-field/mrd-secret-field.css} +7 -10
- package/dist/collection/components/{mrd-secret-field → fields/mrd-secret-field}/mrd-secret-field.js +4 -11
- package/dist/collection/components/{mrd-text-field/mrd-text-field.scss → fields/mrd-text-field/mrd-text-field.css} +2 -11
- package/dist/collection/components/{mrd-text-field → fields/mrd-text-field}/mrd-text-field.js +3 -10
- package/dist/collection/components/{mrd-textarea-field/mrd-textarea-field.scss → fields/mrd-textarea-field/mrd-textarea-field.css} +7 -10
- package/dist/collection/components/{mrd-textarea-field → fields/mrd-textarea-field}/mrd-textarea-field.js +3 -9
- package/dist/collection/components/{mrd-time-field/mrd-time-field.scss → fields/mrd-time-field/mrd-time-field.css} +8 -10
- package/dist/collection/components/{mrd-time-field → fields/mrd-time-field}/mrd-time-field.js +3 -10
- package/dist/collection/components/{mrd-field/mrd-field.scss → form/mrd-field/mrd-field.css} +1 -1
- package/dist/collection/components/{mrd-field → form/mrd-field}/mrd-field.js +3 -3
- package/dist/collection/components/{mrd-form/mrd-form.scss → form/mrd-form/mrd-form.css} +1 -1
- package/dist/collection/components/{mrd-form → form/mrd-form}/mrd-form.js +5 -5
- package/dist/collection/components/layout/mrd-layout-section/field-value.js +111 -0
- package/dist/collection/components/layout/mrd-layout-section/layout-helpers.js +29 -0
- package/dist/collection/components/{mrd-layout-section/mrd-layout-section.scss → layout/mrd-layout-section/mrd-layout-section.css} +4 -4
- package/dist/collection/components/{mrd-layout-section → layout/mrd-layout-section}/mrd-layout-section.js +52 -142
- package/dist/collection/components/{mrd-table/mrd-table.scss → table/mrd-table/mrd-table.css} +30 -12
- package/dist/collection/components/{mrd-table → table/mrd-table}/mrd-table.js +234 -443
- package/dist/collection/components/table/mrd-table/table-filter-popup.js +85 -0
- package/dist/collection/components/table/mrd-table/table-parts.js +110 -0
- package/dist/collection/components/table/mrd-table/table-query.js +178 -0
- package/dist/collection/components/table/mrd-table/virtual-scroll.js +63 -0
- package/dist/collection/dev/app.js +19 -18
- package/dist/collection/dev/example-data.js +58 -43
- package/dist/collection/utils/i18n.js +16 -0
- package/dist/collection/utils/query-params.js +43 -0
- package/dist/components/field-helpers.js +1 -0
- package/dist/components/file-upload-common.js +1 -0
- package/dist/components/i18n.js +1 -1
- package/dist/components/mrd-boolean-field2.js +1 -1
- package/dist/components/mrd-currency-field2.js +1 -1
- package/dist/components/mrd-date-field2.js +1 -1
- package/dist/components/mrd-datetime-field2.js +1 -1
- package/dist/components/mrd-document-container2.js +1 -1
- package/dist/components/mrd-document-list2.js +1 -1
- package/dist/components/mrd-document-view2.js +1 -1
- package/dist/components/mrd-email-field2.js +1 -1
- package/dist/components/mrd-file-field2.js +1 -1
- package/dist/components/mrd-form.js +1 -1
- package/dist/components/mrd-hyperlink-field2.js +1 -1
- package/dist/components/mrd-image-field2.js +1 -1
- package/dist/components/mrd-layout-section.js +1 -1
- package/dist/components/mrd-list-field2.js +1 -1
- package/dist/components/mrd-longtext-field2.js +1 -1
- package/dist/components/mrd-number-field2.js +1 -1
- package/dist/components/mrd-relation-field2.js +1 -1
- package/dist/components/mrd-secret-field2.js +1 -1
- package/dist/components/mrd-table2.js +1 -1
- package/dist/components/mrd-text-field2.js +1 -1
- package/dist/components/mrd-textarea-field2.js +1 -1
- package/dist/components/mrd-time-field2.js +1 -1
- package/dist/components/query-params.js +1 -0
- package/dist/esm/{index-_tsCCkAi.js → index-DiSEJMA8.js} +38 -1
- package/dist/esm/loader.js +3 -3
- package/dist/esm/mosterdcomponents.js +3 -3
- package/dist/esm/mrd-boolean-field_23.entry.js +1313 -1088
- package/dist/mosterdcomponents/mosterdcomponents.css +1 -1
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/p-DiSEJMA8.js +2 -0
- package/dist/mosterdcomponents/p-b1a2e108.entry.js +1 -0
- package/dist/types/components/{mrd-document-container → documents/mrd-document-container}/mrd-document-container.d.ts +2 -2
- package/dist/types/components/documents/mrd-document-list/folder-tree.d.ts +66 -0
- package/dist/types/components/{mrd-document-list → documents/mrd-document-list}/mrd-document-list.d.ts +4 -37
- package/dist/types/components/documents/mrd-document-list/tree-view.d.ts +32 -0
- package/dist/types/components/{mrd-document-view → documents/mrd-document-view}/mrd-document-view.d.ts +2 -2
- package/dist/types/components/fields/field-helpers.d.ts +18 -0
- package/dist/types/components/fields/file-upload-common.d.ts +18 -0
- package/dist/types/components/{mrd-boolean-field → fields/mrd-boolean-field}/mrd-boolean-field.d.ts +1 -1
- package/dist/types/components/{mrd-currency-field → fields/mrd-currency-field}/mrd-currency-field.d.ts +2 -2
- package/dist/types/components/{mrd-date-field → fields/mrd-date-field}/mrd-date-field.d.ts +1 -1
- package/dist/types/components/{mrd-datetime-field → fields/mrd-datetime-field}/mrd-datetime-field.d.ts +1 -1
- package/dist/types/components/{mrd-email-field → fields/mrd-email-field}/mrd-email-field.d.ts +1 -1
- package/dist/types/components/{mrd-file-field → fields/mrd-file-field}/mrd-file-field.d.ts +2 -4
- package/dist/types/components/{mrd-hyperlink-field → fields/mrd-hyperlink-field}/mrd-hyperlink-field.d.ts +2 -2
- package/dist/types/components/{mrd-image-field → fields/mrd-image-field}/mrd-image-field.d.ts +2 -4
- package/dist/types/components/{mrd-list-field → fields/mrd-list-field}/mrd-list-field.d.ts +2 -2
- package/dist/types/components/{mrd-longtext-field → fields/mrd-longtext-field}/mrd-longtext-field.d.ts +1 -1
- package/dist/types/components/{mrd-number-field → fields/mrd-number-field}/mrd-number-field.d.ts +2 -2
- package/dist/types/components/{mrd-relation-field → fields/mrd-relation-field}/mrd-relation-field.d.ts +2 -2
- package/dist/types/components/{mrd-secret-field → fields/mrd-secret-field}/mrd-secret-field.d.ts +1 -1
- package/dist/types/components/{mrd-text-field → fields/mrd-text-field}/mrd-text-field.d.ts +1 -1
- package/dist/types/components/{mrd-textarea-field → fields/mrd-textarea-field}/mrd-textarea-field.d.ts +1 -1
- package/dist/types/components/{mrd-time-field → fields/mrd-time-field}/mrd-time-field.d.ts +1 -1
- package/dist/types/components/{mrd-field → form/mrd-field}/mrd-field.d.ts +2 -2
- package/dist/types/components/{mrd-form → form/mrd-form}/mrd-form.d.ts +2 -2
- package/dist/types/components/layout/mrd-layout-section/field-value.d.ts +22 -0
- package/dist/types/components/layout/mrd-layout-section/layout-helpers.d.ts +8 -0
- package/dist/types/components/{mrd-layout-section → layout/mrd-layout-section}/mrd-layout-section.d.ts +4 -11
- package/dist/types/components/{mrd-table → table/mrd-table}/mrd-table.d.ts +29 -13
- package/dist/types/components/table/mrd-table/table-filter-popup.d.ts +35 -0
- package/dist/types/components/table/mrd-table/table-parts.d.ts +61 -0
- package/dist/types/components/table/mrd-table/table-query.d.ts +50 -0
- package/dist/types/components/table/mrd-table/virtual-scroll.d.ts +33 -0
- package/dist/types/components.d.ts +19 -5
- package/dist/types/utils/query-params.d.ts +14 -0
- package/package.json +7 -2
- package/dist/components/validation.js +0 -1
- package/dist/mosterdcomponents/p-_tsCCkAi.js +0 -2
- package/dist/mosterdcomponents/p-a9618055.entry.js +0 -1
|
@@ -1,28 +1,30 @@
|
|
|
1
|
-
import { h, Host } from "@stencil/core";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { h, Host, writeTask } from "@stencil/core";
|
|
2
|
+
import { t } from "../../../utils/i18n";
|
|
3
|
+
import { buildListPath } from "../../../utils/query-params";
|
|
4
|
+
import { TEXT_TYPES, NON_INTERACTIVE_TYPES, parseDefaultSort, buildSortParam, buildTableQueryParams, buildAggregationQs, filterHasValue, normalizeDatetimeFilter, datetimeFilterToDisplay, } from "./table-query";
|
|
5
|
+
import { FilterIcon, TableCell, TotalsRow, TextblockModal, JsonModal, Toolbar } from "./table-parts";
|
|
6
|
+
import { FilterPopup } from "./table-filter-popup";
|
|
7
|
+
import { REQUEST_DEBOUNCE_MS, REQUEST_TIMEOUT_MS, MAX_RETRY_ATTEMPTS, visibleCount, computeWindow, pageRange, getRowFromPages, computeEffectiveTotal, isTotalKnownFromPages, getStalePages, } from "./virtual-scroll";
|
|
8
|
+
/** How often (ms) to scan outstanding page requests for staleness. */
|
|
9
|
+
const STALE_CHECK_INTERVAL_MS = 2000;
|
|
9
10
|
/** Breedte van de filterpopup in px — voor overflow-correctie. */
|
|
10
11
|
const POPUP_WIDTH = 280;
|
|
11
|
-
const TEXT_TYPES = new Set(['TEXT', 'TEXTBLOCK', 'EMAIL', 'HYPERLINK']);
|
|
12
|
-
const NUMERIC_TYPES = new Set(['INTEGER', 'DECIMAL', 'PERCENTAGE', 'CURRENCY']);
|
|
13
|
-
const DATE_TYPES = new Set(['DATE', 'DATETIME', 'TIME']);
|
|
14
|
-
const NO_FILTER_TYPES = new Set(['FILE', 'IMAGE']);
|
|
15
|
-
/** Column types that cannot be sorted or filtered (not stored in PostgreSQL). */
|
|
16
|
-
const NON_INTERACTIVE_TYPES = new Set(['LONGTEXT', 'JSON', 'FILE', 'IMAGE']);
|
|
17
12
|
export class MrdTable {
|
|
18
13
|
constructor() {
|
|
19
14
|
// ── Non-state internals ────────────────────────────────────────────────────
|
|
20
15
|
this.pendingPages = new Set();
|
|
21
16
|
this.debounceTimer = null;
|
|
17
|
+
/** Timestamp of the last mrdLoadPage emission per outstanding page — drives stale-request detection. */
|
|
18
|
+
this.requestedAt = new Map();
|
|
19
|
+
/** Retry count per page since it first became stale. */
|
|
20
|
+
this.retryAttempts = new Map();
|
|
21
|
+
this.staleCheckTimer = null;
|
|
22
22
|
this.outsideClickHandler = null;
|
|
23
23
|
this.keydownHandler = null;
|
|
24
24
|
this.createPickerClickHandler = null;
|
|
25
25
|
this.viewPopoverClickHandler = null;
|
|
26
|
+
/** Guards against scheduling more than one column-width measurement per freeze. */
|
|
27
|
+
this.colWidthMeasureScheduled = false;
|
|
26
28
|
// ── Props ──────────────────────────────────────────────────────────────────
|
|
27
29
|
/** The VIEW or RELATED_VIEW layout item. Contains view config, dataClass, fromClass, actions etc. */
|
|
28
30
|
this.item = null;
|
|
@@ -39,11 +41,16 @@ export class MrdTable {
|
|
|
39
41
|
this.rowHeight = 36;
|
|
40
42
|
/** Height of the scroll container in px. */
|
|
41
43
|
this.tableHeight = 500;
|
|
44
|
+
/** Time (ms) to wait for setPage() before retrying a page request; after
|
|
45
|
+
* MAX_RETRY_ATTEMPTS retries the page is marked failed (see failedPages). */
|
|
46
|
+
this.requestTimeoutMs = REQUEST_TIMEOUT_MS;
|
|
42
47
|
// ── Internal state ─────────────────────────────────────────────────────────
|
|
43
48
|
/** Index into allViews[] for the currently displayed view. 0 = primary, 1+ = alternatives. */
|
|
44
49
|
this.activeViewIdx = 0;
|
|
45
50
|
this.loadedPages = new Map();
|
|
46
51
|
this.requestedPages = new Set();
|
|
52
|
+
/** Pages that timed out MAX_RETRY_ATTEMPTS times — rendered as a retry row instead of shimmer. */
|
|
53
|
+
this.failedPages = new Set();
|
|
47
54
|
this.renderStart = 0;
|
|
48
55
|
this.renderEnd = 0;
|
|
49
56
|
this.colWidths = [];
|
|
@@ -79,13 +86,10 @@ export class MrdTable {
|
|
|
79
86
|
this.minKnownTotal = 0;
|
|
80
87
|
this.handleScroll = (e) => {
|
|
81
88
|
const scroller = e.currentTarget;
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.scrollTop = scrollTop;
|
|
87
|
-
this.renderStart = Math.max(0, visStart - BUFFER);
|
|
88
|
-
this.renderEnd = Math.min(total - 1, visEnd + BUFFER);
|
|
89
|
+
const { renderStart, renderEnd } = computeWindow(scroller.scrollTop, this.rowHeight, this.tableHeight, this.baseTotal);
|
|
90
|
+
this.scrollTop = scroller.scrollTop;
|
|
91
|
+
this.renderStart = renderStart;
|
|
92
|
+
this.renderEnd = renderEnd;
|
|
89
93
|
this.requestPagesForWindow(this.renderStart, this.renderEnd);
|
|
90
94
|
};
|
|
91
95
|
}
|
|
@@ -105,18 +109,14 @@ export class MrdTable {
|
|
|
105
109
|
var _a, _b, _c;
|
|
106
110
|
this.applyDefaultSort((_c = (_b = (_a = this.item) === null || _a === void 0 ? void 0 : _a.view) === null || _b === void 0 ? void 0 : _b.defaultSort) !== null && _c !== void 0 ? _c : '');
|
|
107
111
|
}
|
|
112
|
+
componentDidLoad() {
|
|
113
|
+
this.staleCheckTimer = setInterval(() => this.checkStalePages(), STALE_CHECK_INTERVAL_MS);
|
|
114
|
+
}
|
|
108
115
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
109
116
|
applyDefaultSort(defaultSort) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.sortField = parts[0].trim();
|
|
114
|
-
this.sortDir = ((_a = parts[1]) === null || _a === void 0 ? void 0 : _a.trim()) === 'desc' ? 'desc' : 'asc';
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
this.sortField = '';
|
|
118
|
-
this.sortDir = 'asc';
|
|
119
|
-
}
|
|
117
|
+
const { field, dir } = parseDefaultSort(defaultSort);
|
|
118
|
+
this.sortField = field;
|
|
119
|
+
this.sortDir = dir;
|
|
120
120
|
}
|
|
121
121
|
// ── Public API ─────────────────────────────────────────────────────────────
|
|
122
122
|
/**
|
|
@@ -132,7 +132,11 @@ export class MrdTable {
|
|
|
132
132
|
this.pendingPages.clear();
|
|
133
133
|
this.loadedPages = new Map();
|
|
134
134
|
this.requestedPages = new Set();
|
|
135
|
+
this.failedPages = new Set();
|
|
136
|
+
this.requestedAt.clear();
|
|
137
|
+
this.retryAttempts.clear();
|
|
135
138
|
this.colWidths = [];
|
|
139
|
+
this.colWidthMeasureScheduled = false;
|
|
136
140
|
this.scrollTop = 0;
|
|
137
141
|
this.renderStart = 0;
|
|
138
142
|
// Always fill the visible viewport on init — totalElements may be stale from a
|
|
@@ -146,7 +150,7 @@ export class MrdTable {
|
|
|
146
150
|
this.aggregationsPending = true;
|
|
147
151
|
this.minKnownTotal = 0;
|
|
148
152
|
// Always request page 0 — totalElements may be unknown (0) on first load.
|
|
149
|
-
this.
|
|
153
|
+
this.emitLoadPage(0);
|
|
150
154
|
this.requestedPages = new Set([0]);
|
|
151
155
|
}
|
|
152
156
|
/**
|
|
@@ -177,6 +181,13 @@ export class MrdTable {
|
|
|
177
181
|
const next = new Map(this.loadedPages);
|
|
178
182
|
next.set(pageNumber, rows);
|
|
179
183
|
this.loadedPages = next;
|
|
184
|
+
this.requestedAt.delete(pageNumber);
|
|
185
|
+
this.retryAttempts.delete(pageNumber);
|
|
186
|
+
if (this.failedPages.has(pageNumber)) {
|
|
187
|
+
const nextFailed = new Set(this.failedPages);
|
|
188
|
+
nextFailed.delete(pageNumber);
|
|
189
|
+
this.failedPages = nextFailed;
|
|
190
|
+
}
|
|
180
191
|
if (pageNumber === 0 && this.aggregationsPending) {
|
|
181
192
|
this.aggregationsPending = false;
|
|
182
193
|
const hasAggColumns = this.columns.some(c => c.type === 'FIELD' && c.aggregate);
|
|
@@ -195,6 +206,10 @@ export class MrdTable {
|
|
|
195
206
|
}
|
|
196
207
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
197
208
|
disconnectedCallback() {
|
|
209
|
+
if (this.staleCheckTimer !== null) {
|
|
210
|
+
clearInterval(this.staleCheckTimer);
|
|
211
|
+
this.staleCheckTimer = null;
|
|
212
|
+
}
|
|
198
213
|
if (this.outsideClickHandler) {
|
|
199
214
|
document.removeEventListener('click', this.outsideClickHandler);
|
|
200
215
|
this.outsideClickHandler = null;
|
|
@@ -213,21 +228,34 @@ export class MrdTable {
|
|
|
213
228
|
}
|
|
214
229
|
}
|
|
215
230
|
componentDidRender() {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
231
|
+
// Freeze header widths once the first data page has rendered. The measurement
|
|
232
|
+
// reads offsetWidth and writes the colWidths @State, which would trigger a
|
|
233
|
+
// re-render *during* the render cycle if done synchronously here. Defer it to a
|
|
234
|
+
// writeTask so the state mutation happens outside the render pass (avoids the
|
|
235
|
+
// Stencil "state/prop changed during rendering" warning and potential loops).
|
|
236
|
+
if (this.colWidths.length === 0 &&
|
|
237
|
+
!this.colWidthMeasureScheduled &&
|
|
238
|
+
this.loadedPages.size > 0 &&
|
|
239
|
+
this.totalElements > 0) {
|
|
240
|
+
this.colWidthMeasureScheduled = true;
|
|
241
|
+
writeTask(() => {
|
|
242
|
+
this.colWidthMeasureScheduled = false;
|
|
243
|
+
// Re-check: state may have changed (e.g. a reset) between scheduling and running.
|
|
244
|
+
if (this.colWidths.length !== 0)
|
|
245
|
+
return;
|
|
246
|
+
const ths = this.el.querySelectorAll('.mrd-table__header');
|
|
247
|
+
if (ths.length > 0) {
|
|
248
|
+
this.colWidths = Array.from(ths).map(th => th.offsetWidth);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
221
251
|
}
|
|
222
252
|
}
|
|
223
253
|
// ── Paging / scroll helpers ────────────────────────────────────────────────
|
|
224
254
|
visibleCount() {
|
|
225
|
-
return
|
|
255
|
+
return visibleCount(this.tableHeight, this.rowHeight);
|
|
226
256
|
}
|
|
227
257
|
sortParam() {
|
|
228
|
-
|
|
229
|
-
return '';
|
|
230
|
-
return this.sortDir === 'desc' ? `${this.sortField},desc` : this.sortField;
|
|
258
|
+
return buildSortParam(this.sortField, this.sortDir);
|
|
231
259
|
}
|
|
232
260
|
/** Stable ordered list: primary view first, then alternatives (from the item prop). */
|
|
233
261
|
get allViews() {
|
|
@@ -253,14 +281,16 @@ export class MrdTable {
|
|
|
253
281
|
* VIEW: /excel/{dataClass}
|
|
254
282
|
* RELATED_VIEW: /excel/{fromClass}/{parentId}/{dataClass} */
|
|
255
283
|
buildExcelPath() {
|
|
256
|
-
var _a
|
|
284
|
+
var _a;
|
|
257
285
|
const v = this.allViews[this.activeViewIdx];
|
|
258
286
|
if (!v)
|
|
259
287
|
return '';
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
288
|
+
return '/excel' + buildListPath({
|
|
289
|
+
isRelatedView: ((_a = this.item) === null || _a === void 0 ? void 0 : _a.type) === 'RELATED_VIEW',
|
|
290
|
+
fromClass: v.fromClass,
|
|
291
|
+
parentId: this.parentId,
|
|
292
|
+
dataClass: v.dataClass,
|
|
293
|
+
});
|
|
264
294
|
}
|
|
265
295
|
buildActionDetail(action, basicType) {
|
|
266
296
|
var _a, _b, _c;
|
|
@@ -280,80 +310,28 @@ export class MrdTable {
|
|
|
280
310
|
* VIEW: /{dataClass}
|
|
281
311
|
* RELATED_VIEW: /{fromClass}/{parentId}/{dataClass} */
|
|
282
312
|
buildDataPath() {
|
|
283
|
-
var _a
|
|
313
|
+
var _a;
|
|
284
314
|
const v = this.allViews[this.activeViewIdx];
|
|
285
315
|
if (!v)
|
|
286
316
|
return '';
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
317
|
+
return buildListPath({
|
|
318
|
+
isRelatedView: ((_a = this.item) === null || _a === void 0 ? void 0 : _a.type) === 'RELATED_VIEW',
|
|
319
|
+
fromClass: v.fromClass,
|
|
320
|
+
parentId: this.parentId,
|
|
321
|
+
dataClass: v.dataClass,
|
|
322
|
+
});
|
|
291
323
|
}
|
|
292
324
|
/** Build query params for a page request from current sort, view filters, filterClass and active column filters. */
|
|
293
325
|
buildQueryParams(page) {
|
|
294
|
-
var _a
|
|
326
|
+
var _a;
|
|
295
327
|
const v = this.allViews[this.activeViewIdx];
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (filterClass)
|
|
304
|
-
p.set('type', filterClass);
|
|
305
|
-
for (const f of ((_b = (_a = v === null || v === void 0 ? void 0 : v.view) === null || _a === void 0 ? void 0 : _a.filter) !== null && _b !== void 0 ? _b : [])) {
|
|
306
|
-
if (!f.name)
|
|
307
|
-
continue;
|
|
308
|
-
if (f.operator === 'EMPTY') {
|
|
309
|
-
p.set(f.name, '');
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
if (f.operator === 'NOT_EMPTY') {
|
|
313
|
-
p.set(f.name + '_notempty', 'true');
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
if (f.operator === 'STARTS_WITH') {
|
|
317
|
-
p.set(f.name + '_startswith', String((_c = f.value) !== null && _c !== void 0 ? _c : ''));
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
if (f.operator === 'FROM') {
|
|
321
|
-
p.set(f.name + '_from', String((_d = f.value) !== null && _d !== void 0 ? _d : ''));
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
if (f.operator === 'TO') {
|
|
325
|
-
p.set(f.name + '_to', String((_e = f.value) !== null && _e !== void 0 ? _e : ''));
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
if (f.value != null) {
|
|
329
|
-
p.set(f.name, String(f.value));
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
for (const f of this.activeFilters.values()) {
|
|
333
|
-
if (f.operator === 'isEmpty') {
|
|
334
|
-
p.set(f.field, '');
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
if (f.operator === 'isNotEmpty') {
|
|
338
|
-
p.set(f.field + '_notempty', 'true');
|
|
339
|
-
continue;
|
|
340
|
-
}
|
|
341
|
-
if (f.operator === 'startsWith') {
|
|
342
|
-
p.set(f.field + '_startswith', String((_f = f.value) !== null && _f !== void 0 ? _f : ''));
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
345
|
-
if ((_g = f.values) === null || _g === void 0 ? void 0 : _g.length) {
|
|
346
|
-
p.set(f.field, f.values.join(','));
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
if (f.value != null)
|
|
350
|
-
p.set(f.field, String(f.value));
|
|
351
|
-
if (f.from != null)
|
|
352
|
-
p.set(f.field + '_from', String(f.from));
|
|
353
|
-
if (f.to != null)
|
|
354
|
-
p.set(f.field + '_to', String(f.to));
|
|
355
|
-
}
|
|
356
|
-
return p.toString();
|
|
328
|
+
return buildTableQueryParams({
|
|
329
|
+
page,
|
|
330
|
+
sort: this.sortParam(),
|
|
331
|
+
filterClass: v === null || v === void 0 ? void 0 : v.filterClass,
|
|
332
|
+
viewFilters: (_a = v === null || v === void 0 ? void 0 : v.view) === null || _a === void 0 ? void 0 : _a.filter,
|
|
333
|
+
activeFilters: this.activeFilters.values(),
|
|
334
|
+
});
|
|
357
335
|
}
|
|
358
336
|
get columns() {
|
|
359
337
|
var _a, _b, _c;
|
|
@@ -408,11 +386,7 @@ export class MrdTable {
|
|
|
408
386
|
isTotalKnown() {
|
|
409
387
|
if (this.aggregationsTotal != null)
|
|
410
388
|
return true;
|
|
411
|
-
|
|
412
|
-
if (rows.length < this.pageSize)
|
|
413
|
-
return true;
|
|
414
|
-
}
|
|
415
|
-
return false;
|
|
389
|
+
return isTotalKnownFromPages(this.loadedPages, this.pageSize);
|
|
416
390
|
}
|
|
417
391
|
/** Effective total: aggregations-response > totalElements prop > minKnownTotal from setPage(). */
|
|
418
392
|
get baseTotal() {
|
|
@@ -423,61 +397,9 @@ export class MrdTable {
|
|
|
423
397
|
return this.minKnownTotal;
|
|
424
398
|
}
|
|
425
399
|
// ── Aggregation helpers ────────────────────────────────────────────────────
|
|
426
|
-
buildAggregationParams() {
|
|
427
|
-
var _a;
|
|
428
|
-
const groups = { sum: [], avg: [], count: [] };
|
|
429
|
-
for (const col of this.columns) {
|
|
430
|
-
if (col.type !== 'FIELD' || !col.aggregate)
|
|
431
|
-
continue;
|
|
432
|
-
const fn = col.aggregate.toLowerCase();
|
|
433
|
-
if (fn in groups)
|
|
434
|
-
groups[fn].push((_a = col.name) !== null && _a !== void 0 ? _a : '');
|
|
435
|
-
}
|
|
436
|
-
const params = {};
|
|
437
|
-
if (groups.sum.length)
|
|
438
|
-
params.sum = groups.sum;
|
|
439
|
-
if (groups.avg.length)
|
|
440
|
-
params.avg = groups.avg;
|
|
441
|
-
if (groups.count.length)
|
|
442
|
-
params.count = groups.count;
|
|
443
|
-
return Object.keys(params).length > 0 ? params : null;
|
|
444
|
-
}
|
|
445
|
-
buildAggregationQs() {
|
|
446
|
-
var _a, _b, _c;
|
|
447
|
-
const p = new URLSearchParams(this.buildQueryParams(0));
|
|
448
|
-
p.delete('page');
|
|
449
|
-
p.delete('sort');
|
|
450
|
-
const groups = this.buildAggregationParams();
|
|
451
|
-
if ((_a = groups === null || groups === void 0 ? void 0 : groups.sum) === null || _a === void 0 ? void 0 : _a.length)
|
|
452
|
-
p.set('sum', groups.sum.join(','));
|
|
453
|
-
if ((_b = groups === null || groups === void 0 ? void 0 : groups.avg) === null || _b === void 0 ? void 0 : _b.length)
|
|
454
|
-
p.set('avg', groups.avg.join(','));
|
|
455
|
-
if ((_c = groups === null || groups === void 0 ? void 0 : groups.count) === null || _c === void 0 ? void 0 : _c.length)
|
|
456
|
-
p.set('count', groups.count.join(','));
|
|
457
|
-
return p.toString();
|
|
458
|
-
}
|
|
459
400
|
emitLoadAggregations() {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
renderAggregationValue(col) {
|
|
463
|
-
var _a;
|
|
464
|
-
if (col.type !== 'FIELD' || !col.aggregate || !this.aggregations)
|
|
465
|
-
return '';
|
|
466
|
-
const fn = col.aggregate.toLowerCase();
|
|
467
|
-
const aggData = this.aggregations[fn];
|
|
468
|
-
const val = typeof aggData === 'object' && aggData !== null
|
|
469
|
-
? aggData[(_a = col.name) !== null && _a !== void 0 ? _a : '']
|
|
470
|
-
: undefined;
|
|
471
|
-
if (val == null)
|
|
472
|
-
return '';
|
|
473
|
-
const dt = col.dataType;
|
|
474
|
-
if (dt === 'INTEGER')
|
|
475
|
-
return formatNumber(val, this.locale, { maximumFractionDigits: 0 });
|
|
476
|
-
if (dt === 'PERCENTAGE')
|
|
477
|
-
return formatPercentage(val, this.locale);
|
|
478
|
-
if (dt === 'CURRENCY' && col.currencyCode)
|
|
479
|
-
return formatCurrency(val, col.currencyCode, this.locale);
|
|
480
|
-
return formatNumber(val, this.locale);
|
|
401
|
+
const qs = this.buildQueryParams(0);
|
|
402
|
+
this.mrdLoadAggregations.emit({ path: this.buildDataPath(), qs, aggQs: buildAggregationQs(qs, this.columns) });
|
|
481
403
|
}
|
|
482
404
|
// ── Reset pagination ───────────────────────────────────────────────────────
|
|
483
405
|
/** Reset pagination state and scroll to top (used after sort or filter change). */
|
|
@@ -489,7 +411,11 @@ export class MrdTable {
|
|
|
489
411
|
this.pendingPages.clear();
|
|
490
412
|
this.loadedPages = new Map();
|
|
491
413
|
this.requestedPages = new Set();
|
|
414
|
+
this.failedPages = new Set();
|
|
415
|
+
this.requestedAt.clear();
|
|
416
|
+
this.retryAttempts.clear();
|
|
492
417
|
this.colWidths = [];
|
|
418
|
+
this.colWidthMeasureScheduled = false;
|
|
493
419
|
this.scrollTop = 0;
|
|
494
420
|
this.renderStart = 0;
|
|
495
421
|
this.minKnownTotal = 0;
|
|
@@ -518,15 +444,19 @@ export class MrdTable {
|
|
|
518
444
|
this.resetPages();
|
|
519
445
|
this.emitPagesForWindow(this.renderStart, this.renderEnd);
|
|
520
446
|
}
|
|
447
|
+
/** Emits mrdLoadPage for a page and records the request timestamp for stale-request detection. */
|
|
448
|
+
emitLoadPage(page) {
|
|
449
|
+
this.mrdLoadPage.emit({ page, sort: this.sortParam(), path: this.buildDataPath(), qs: this.buildQueryParams(page) });
|
|
450
|
+
this.requestedAt.set(page, Date.now());
|
|
451
|
+
}
|
|
521
452
|
emitPagesForWindow(start, end) {
|
|
522
|
-
const firstPage =
|
|
523
|
-
const lastPage = Math.floor(end / this.pageSize);
|
|
453
|
+
const { firstPage, lastPage } = pageRange(start, end, this.pageSize);
|
|
524
454
|
const next = new Set(this.requestedPages);
|
|
525
455
|
let changed = false;
|
|
526
456
|
for (let p = firstPage; p <= lastPage; p++) {
|
|
527
457
|
if (!this.loadedPages.has(p) && !next.has(p)) {
|
|
528
458
|
next.add(p);
|
|
529
|
-
this.
|
|
459
|
+
this.emitLoadPage(p);
|
|
530
460
|
changed = true;
|
|
531
461
|
}
|
|
532
462
|
}
|
|
@@ -534,13 +464,10 @@ export class MrdTable {
|
|
|
534
464
|
this.requestedPages = next;
|
|
535
465
|
}
|
|
536
466
|
getRow(i) {
|
|
537
|
-
|
|
538
|
-
const page = this.loadedPages.get(Math.floor(i / this.pageSize));
|
|
539
|
-
return (_a = page === null || page === void 0 ? void 0 : page[i % this.pageSize]) !== null && _a !== void 0 ? _a : null;
|
|
467
|
+
return getRowFromPages(this.loadedPages, i, this.pageSize);
|
|
540
468
|
}
|
|
541
469
|
requestPagesForWindow(start, end) {
|
|
542
|
-
const firstPage =
|
|
543
|
-
const lastPage = Math.floor(end / this.pageSize);
|
|
470
|
+
const { firstPage, lastPage } = pageRange(start, end, this.pageSize);
|
|
544
471
|
let anyNew = false;
|
|
545
472
|
for (let p = firstPage; p <= lastPage; p++) {
|
|
546
473
|
if (!this.loadedPages.has(p) && !this.requestedPages.has(p) && !this.pendingPages.has(p)) {
|
|
@@ -568,13 +495,53 @@ export class MrdTable {
|
|
|
568
495
|
if (pageEnd < this.renderStart || pageStart > this.renderEnd)
|
|
569
496
|
continue;
|
|
570
497
|
next.add(page);
|
|
571
|
-
this.
|
|
498
|
+
this.emitLoadPage(page);
|
|
572
499
|
changed = true;
|
|
573
500
|
}
|
|
574
501
|
this.pendingPages.clear();
|
|
575
502
|
if (changed)
|
|
576
503
|
this.requestedPages = next;
|
|
577
504
|
}
|
|
505
|
+
/** Scans outstanding requests for staleness: retries up to MAX_RETRY_ATTEMPTS times,
|
|
506
|
+
* then gives up and marks the page as failed (rendered as a retry row). */
|
|
507
|
+
checkStalePages() {
|
|
508
|
+
var _a;
|
|
509
|
+
if (this.requestedAt.size === 0)
|
|
510
|
+
return;
|
|
511
|
+
const stale = getStalePages(this.requestedAt, Date.now(), this.requestTimeoutMs)
|
|
512
|
+
.filter(p => !this.loadedPages.has(p));
|
|
513
|
+
if (stale.length === 0)
|
|
514
|
+
return;
|
|
515
|
+
let nextFailed = null;
|
|
516
|
+
for (const page of stale) {
|
|
517
|
+
const attempts = (_a = this.retryAttempts.get(page)) !== null && _a !== void 0 ? _a : 0;
|
|
518
|
+
if (attempts < MAX_RETRY_ATTEMPTS) {
|
|
519
|
+
this.retryAttempts.set(page, attempts + 1);
|
|
520
|
+
this.emitLoadPage(page);
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
this.requestedAt.delete(page);
|
|
524
|
+
this.retryAttempts.delete(page);
|
|
525
|
+
if (!nextFailed)
|
|
526
|
+
nextFailed = new Set(this.failedPages);
|
|
527
|
+
nextFailed.add(page);
|
|
528
|
+
this.mrdPageError.emit({ page });
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (nextFailed)
|
|
532
|
+
this.failedPages = nextFailed;
|
|
533
|
+
}
|
|
534
|
+
/** Manually retry a page the user gave up on (clicked the retry row). */
|
|
535
|
+
retryPage(page) {
|
|
536
|
+
const next = new Set(this.failedPages);
|
|
537
|
+
next.delete(page);
|
|
538
|
+
this.failedPages = next;
|
|
539
|
+
this.retryAttempts.delete(page);
|
|
540
|
+
this.emitLoadPage(page);
|
|
541
|
+
const requested = new Set(this.requestedPages);
|
|
542
|
+
requested.add(page);
|
|
543
|
+
this.requestedPages = requested;
|
|
544
|
+
}
|
|
578
545
|
// ── Filter helpers ─────────────────────────────────────────────────────────
|
|
579
546
|
handleFilterToggle() {
|
|
580
547
|
this.filterMode = !this.filterMode;
|
|
@@ -602,17 +569,7 @@ export class MrdTable {
|
|
|
602
569
|
// If from and to cover the same local day it was an exact-date filter — restore
|
|
603
570
|
// to exact mode so the user sees the single-date input again.
|
|
604
571
|
if (dataType === 'DATETIME' && existing && existing.operator !== 'isEmpty' && existing.operator !== 'isNotEmpty') {
|
|
605
|
-
|
|
606
|
-
if (typeof display.from === 'string' && display.from)
|
|
607
|
-
display.from = this.utcISOToLocalDate(display.from);
|
|
608
|
-
if (typeof display.to === 'string' && display.to)
|
|
609
|
-
display.to = this.utcISOToLocalDateExclusiveEnd(display.to);
|
|
610
|
-
if (display.from && display.to && display.from === display.to) {
|
|
611
|
-
this.pendingFilter = Object.assign(Object.assign({}, display), { value: display.from, from: undefined, to: undefined });
|
|
612
|
-
}
|
|
613
|
-
else {
|
|
614
|
-
this.pendingFilter = display;
|
|
615
|
-
}
|
|
572
|
+
this.pendingFilter = datetimeFilterToDisplay(existing);
|
|
616
573
|
}
|
|
617
574
|
else {
|
|
618
575
|
this.pendingFilter = existing ? Object.assign({}, existing) : { field: name, dataType, operator: defaultOperator };
|
|
@@ -678,81 +635,15 @@ export class MrdTable {
|
|
|
678
635
|
const current = (_b = (_a = this.pendingFilter) === null || _a === void 0 ? void 0 : _a.values) !== null && _b !== void 0 ? _b : [];
|
|
679
636
|
this.pendingFilter = Object.assign(Object.assign({}, this.pendingFilter), { values: checked ? [...current, key] : current.filter(k => k !== key) });
|
|
680
637
|
}
|
|
681
|
-
filterHasValue(f) {
|
|
682
|
-
if (f.operator === 'isEmpty' || f.operator === 'isNotEmpty')
|
|
683
|
-
return true;
|
|
684
|
-
if (f.values !== undefined && f.values.length > 0)
|
|
685
|
-
return true;
|
|
686
|
-
if (f.value != null && f.value !== '')
|
|
687
|
-
return true;
|
|
688
|
-
if (typeof f.value === 'boolean')
|
|
689
|
-
return true;
|
|
690
|
-
if (f.from != null && f.from !== '')
|
|
691
|
-
return true;
|
|
692
|
-
if (f.to != null && f.to !== '')
|
|
693
|
-
return true;
|
|
694
|
-
return false;
|
|
695
|
-
}
|
|
696
|
-
// Convert a local "YYYY-MM-DD" date string to the UTC ISO string at the
|
|
697
|
-
// start of that local day (midnight). new Date(y, m, d) uses local time.
|
|
698
|
-
dateLocalToUTCStart(dateStr) {
|
|
699
|
-
if (!dateStr)
|
|
700
|
-
return dateStr;
|
|
701
|
-
const [year, month, day] = dateStr.split('-').map(Number);
|
|
702
|
-
return new Date(year, month - 1, day).toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
703
|
-
}
|
|
704
|
-
// Start of the day AFTER the given local date (exclusive range end).
|
|
705
|
-
dateLocalToUTCEndExclusive(dateStr) {
|
|
706
|
-
if (!dateStr)
|
|
707
|
-
return dateStr;
|
|
708
|
-
const [year, month, day] = dateStr.split('-').map(Number);
|
|
709
|
-
return new Date(year, month - 1, day + 1).toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
710
|
-
}
|
|
711
|
-
// Convert a stored UTC ISO string back to the local "YYYY-MM-DD" date.
|
|
712
|
-
utcISOToLocalDate(utcStr) {
|
|
713
|
-
if (!utcStr)
|
|
714
|
-
return utcStr;
|
|
715
|
-
const d = new Date(utcStr);
|
|
716
|
-
if (isNaN(d.getTime()))
|
|
717
|
-
return utcStr;
|
|
718
|
-
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
719
|
-
}
|
|
720
|
-
// The stored "to" value is the exclusive end (midnight of the next day).
|
|
721
|
-
// Subtract one day to recover the local date the user entered.
|
|
722
|
-
utcISOToLocalDateExclusiveEnd(utcStr) {
|
|
723
|
-
if (!utcStr)
|
|
724
|
-
return utcStr;
|
|
725
|
-
const d = new Date(utcStr);
|
|
726
|
-
if (isNaN(d.getTime()))
|
|
727
|
-
return utcStr;
|
|
728
|
-
d.setDate(d.getDate() - 1);
|
|
729
|
-
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
730
|
-
}
|
|
731
638
|
applyFilter() {
|
|
732
639
|
const f = this.pendingFilter;
|
|
733
640
|
if (!(f === null || f === void 0 ? void 0 : f.field)) {
|
|
734
641
|
this.closeFilterPopup();
|
|
735
642
|
return;
|
|
736
643
|
}
|
|
737
|
-
|
|
738
|
-
// Exact date → range covering the full local day (from = midnight, to = next midnight).
|
|
739
|
-
// "to" is always the exclusive end (midnight of the next local day).
|
|
740
|
-
let normalized = Object.assign({}, f);
|
|
741
|
-
if (f.dataType === 'DATETIME' && f.operator !== 'isEmpty' && f.operator !== 'isNotEmpty') {
|
|
742
|
-
if (typeof normalized.value === 'string' && normalized.value) {
|
|
743
|
-
normalized.from = this.dateLocalToUTCStart(normalized.value);
|
|
744
|
-
normalized.to = this.dateLocalToUTCEndExclusive(normalized.value);
|
|
745
|
-
normalized.value = undefined;
|
|
746
|
-
}
|
|
747
|
-
else {
|
|
748
|
-
if (typeof normalized.from === 'string' && normalized.from)
|
|
749
|
-
normalized.from = this.dateLocalToUTCStart(normalized.from);
|
|
750
|
-
if (typeof normalized.to === 'string' && normalized.to)
|
|
751
|
-
normalized.to = this.dateLocalToUTCEndExclusive(normalized.to);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
644
|
+
const normalized = normalizeDatetimeFilter(f);
|
|
754
645
|
const next = new Map(this.activeFilters);
|
|
755
|
-
if (
|
|
646
|
+
if (filterHasValue(normalized)) {
|
|
756
647
|
next.set(normalized.field, normalized);
|
|
757
648
|
}
|
|
758
649
|
else {
|
|
@@ -807,140 +698,53 @@ export class MrdTable {
|
|
|
807
698
|
toggleViewPopover(e) {
|
|
808
699
|
e.stopPropagation();
|
|
809
700
|
if (this.viewPopoverOpen) {
|
|
810
|
-
this.
|
|
811
|
-
if (this.viewPopoverClickHandler) {
|
|
812
|
-
document.removeEventListener('click', this.viewPopoverClickHandler);
|
|
813
|
-
this.viewPopoverClickHandler = null;
|
|
814
|
-
}
|
|
701
|
+
this.closeViewPopover();
|
|
815
702
|
return;
|
|
816
703
|
}
|
|
817
704
|
this.viewPopoverOpen = true;
|
|
818
|
-
this.viewPopoverClickHandler = () =>
|
|
819
|
-
|
|
705
|
+
this.viewPopoverClickHandler = () => this.closeViewPopover();
|
|
706
|
+
document.addEventListener('click', this.viewPopoverClickHandler);
|
|
707
|
+
}
|
|
708
|
+
closeViewPopover() {
|
|
709
|
+
this.viewPopoverOpen = false;
|
|
710
|
+
if (this.viewPopoverClickHandler) {
|
|
820
711
|
document.removeEventListener('click', this.viewPopoverClickHandler);
|
|
821
712
|
this.viewPopoverClickHandler = null;
|
|
822
|
-
}
|
|
823
|
-
document.addEventListener('click', this.viewPopoverClickHandler);
|
|
713
|
+
}
|
|
824
714
|
}
|
|
825
715
|
// ── Render: toolbar ────────────────────────────────────────────────────────
|
|
826
716
|
renderToolbar() {
|
|
827
717
|
var _a;
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
} }, v.label)))))), h("button", { class: "mrd-table__view-arrow", "aria-label": "Next view", onClick: () => this.handleViewSwitch((this.activeViewIdx + 1) % allViews.length) }, "\u25B6")))), hasActions && (h("div", { class: "mrd-table__toolbar-right" }, actions.map(a => {
|
|
842
|
-
var _a, _b, _c;
|
|
843
|
-
const createTypes = a.action === 'create' ? (_a = this.item) === null || _a === void 0 ? void 0 : _a.createTypes : null;
|
|
844
|
-
if (createTypes === null || createTypes === void 0 ? void 0 : createTypes.length) {
|
|
845
|
-
return (h("div", { class: "mrd-table__create-picker-wrap", key: `action-${a.action}` }, h("button", { class: `mrd-table__action mrd-table__action--${(_b = a.variant) !== null && _b !== void 0 ? _b : 'secondary'}`, onClick: () => this.toggleCreatePicker() }, a.icon
|
|
846
|
-
? h("svg", { class: "mrd-table__action-icon", "aria-hidden": "true" }, h("use", { href: a.icon }))
|
|
847
|
-
: a.label, h("span", { class: "mrd-table__action-tooltip" }, a.label)), this.createPickerOpen && (h("div", { class: "mrd-table__create-picker" }, createTypes.map(ct => (h("button", { key: ct.type, class: "mrd-table__create-picker-item", onClick: () => {
|
|
848
|
-
this.mrdAction.emit(this.buildActionDetail('create', ct.type));
|
|
849
|
-
this.closeCreatePicker();
|
|
850
|
-
} }, ct.label)))))));
|
|
851
|
-
}
|
|
852
|
-
return (h("button", { key: `action-${a.action}`, class: `mrd-table__action mrd-table__action--${(_c = a.variant) !== null && _c !== void 0 ? _c : 'secondary'}`, disabled: a.disabled, onClick: () => this.mrdAction.emit(this.buildActionDetail(a.action)) }, a.icon
|
|
853
|
-
? h("svg", { class: "mrd-table__action-icon", "aria-hidden": "true" }, h("use", { href: a.icon }))
|
|
854
|
-
: a.label, h("span", { class: "mrd-table__action-tooltip" }, a.label)));
|
|
855
|
-
})))));
|
|
718
|
+
return (h(Toolbar, { locale: this.locale, filterMode: this.filterMode, filterCount: this.activeFilters.size, actions: this.tableActions, views: this.allViews, activeViewIdx: this.activeViewIdx, viewPopoverOpen: this.viewPopoverOpen, createPickerOpen: this.createPickerOpen, createTypes: (_a = this.item) === null || _a === void 0 ? void 0 : _a.createTypes, cb: {
|
|
719
|
+
onFilterToggle: () => this.handleFilterToggle(),
|
|
720
|
+
onClearAllFilters: () => this.clearAllFilters(),
|
|
721
|
+
onViewSwitch: idx => this.handleViewSwitch(idx),
|
|
722
|
+
onToggleViewPopover: e => this.toggleViewPopover(e),
|
|
723
|
+
onCloseViewPopover: () => this.closeViewPopover(),
|
|
724
|
+
onToggleCreatePicker: () => this.toggleCreatePicker(),
|
|
725
|
+
onCreate: basicType => {
|
|
726
|
+
this.mrdAction.emit(this.buildActionDetail('create', basicType));
|
|
727
|
+
this.closeCreatePicker();
|
|
728
|
+
},
|
|
729
|
+
onAction: action => this.mrdAction.emit(this.buildActionDetail(action)),
|
|
730
|
+
} }));
|
|
856
731
|
}
|
|
857
732
|
// ── Render: filter popup ───────────────────────────────────────────────────
|
|
858
|
-
renderFilterEditor(col) {
|
|
859
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
860
|
-
const pf = (_a = this.pendingFilter) !== null && _a !== void 0 ? _a : {};
|
|
861
|
-
const dataType = this.colDataType(col);
|
|
862
|
-
if (NO_FILTER_TYPES.has(dataType)) {
|
|
863
|
-
return h("p", { class: "mrd-table__filter-no-support" }, t('filter_no_support', this.locale));
|
|
864
|
-
}
|
|
865
|
-
if (dataType === 'BOOLEAN') {
|
|
866
|
-
const boolOp = pf.operator;
|
|
867
|
-
const noValueOp = boolOp === 'isEmpty' || boolOp === 'isNotEmpty';
|
|
868
|
-
return (h("div", { class: "mrd-table__filter-radio-group" }, [
|
|
869
|
-
{ labelKey: 'filter_all', value: null },
|
|
870
|
-
{ labelKey: 'yes', value: true },
|
|
871
|
-
{ labelKey: 'no', value: false },
|
|
872
|
-
].map(opt => (h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `bf-${this.openFilterCol}`, checked: !noValueOp && pf.value === opt.value, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: undefined, value: opt.value }); } }), t(opt.labelKey, this.locale)))), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `bf-${this.openFilterCol}`, checked: boolOp === 'isEmpty', onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: 'isEmpty', value: undefined }); } }), t('filter_is_empty', this.locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `bf-${this.openFilterCol}`, checked: boolOp === 'isNotEmpty', onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: 'isNotEmpty', value: undefined }); } }), t('filter_is_not_empty', this.locale))));
|
|
873
|
-
}
|
|
874
|
-
if (dataType === 'LIST') {
|
|
875
|
-
const items = (_b = col.listItems) !== null && _b !== void 0 ? _b : [];
|
|
876
|
-
const selected = (_c = pf.values) !== null && _c !== void 0 ? _c : [];
|
|
877
|
-
return (h("div", { class: "mrd-table__filter-list" }, h("div", { class: "mrd-table__filter-list-controls" }, h("button", { class: "mrd-table__filter-list-btn", onClick: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { values: items.map(i => i.key) }); } }, t('filter_select_all', this.locale)), h("button", { class: "mrd-table__filter-list-btn", onClick: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { values: [] }); } }, t('filter_select_none', this.locale))), items.map(item => (h("label", { class: "mrd-table__filter-checkbox-label" }, h("input", { type: "checkbox", checked: selected.includes(item.key), onChange: (e) => this.togglePendingValue(item.key, e.target.checked) }), item.label)))));
|
|
878
|
-
}
|
|
879
|
-
if (TEXT_TYPES.has(dataType) || dataType === 'RELATION') {
|
|
880
|
-
const op = (_d = pf.operator) !== null && _d !== void 0 ? _d : 'startsWith';
|
|
881
|
-
const noInput = op === 'isEmpty' || op === 'isNotEmpty';
|
|
882
|
-
return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => this.setPending('operator', e.target.value) }, [
|
|
883
|
-
{ val: 'startsWith', labelKey: 'filter_starts_with' },
|
|
884
|
-
{ val: 'equals', labelKey: 'filter_equals' },
|
|
885
|
-
{ val: 'isEmpty', labelKey: 'filter_is_empty' },
|
|
886
|
-
{ val: 'isNotEmpty', labelKey: 'filter_is_not_empty' },
|
|
887
|
-
].map(o => h("option", { value: o.val, selected: op === o.val }, t(o.labelKey, this.locale)))), !noInput && (h("input", { type: "text", class: "mrd-table__filter-input", value: String((_e = pf.value) !== null && _e !== void 0 ? _e : ''), placeholder: t('filter_search_value', this.locale), onInput: (e) => this.setPending('value', e.target.value) }))));
|
|
888
|
-
}
|
|
889
|
-
if (NUMERIC_TYPES.has(dataType)) {
|
|
890
|
-
const numOp = pf.operator;
|
|
891
|
-
const noInput = numOp === 'isEmpty' || numOp === 'isNotEmpty';
|
|
892
|
-
const rangeMode = !noInput && (pf.from !== undefined || pf.to !== undefined);
|
|
893
|
-
return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => {
|
|
894
|
-
const val = e.target.value;
|
|
895
|
-
if (val === 'isEmpty' || val === 'isNotEmpty') {
|
|
896
|
-
this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: val, value: undefined, from: undefined, to: undefined });
|
|
897
|
-
}
|
|
898
|
-
else {
|
|
899
|
-
this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: undefined });
|
|
900
|
-
}
|
|
901
|
-
} }, h("option", { value: "", selected: !noInput }, t('filter_has_value', this.locale)), h("option", { value: "isEmpty", selected: numOp === 'isEmpty' }, t('filter_is_empty', this.locale)), h("option", { value: "isNotEmpty", selected: numOp === 'isNotEmpty' }, t('filter_is_not_empty', this.locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `nm-${this.openFilterCol}`, checked: !rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { from: undefined, to: undefined }); } }), t('filter_exact', this.locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `nm-${this.openFilterCol}`, checked: rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null }); } }), t('filter_range', this.locale))), !rangeMode ? (h("input", { type: "number", class: "mrd-table__filter-input", value: pf.value != null ? String(pf.value) : '', onInput: (e) => this.setPending('value', e.target.value) })) : (h("div", { class: "mrd-table__filter-range" }, h("input", { type: "number", class: "mrd-table__filter-input", placeholder: t('filter_from', this.locale), value: pf.from != null ? String(pf.from) : '', onInput: (e) => this.setPending('from', e.target.value) }), h("span", { class: "mrd-table__filter-range-sep" }, "\u2013"), h("input", { type: "number", class: "mrd-table__filter-input", placeholder: t('filter_to', this.locale), value: pf.to != null ? String(pf.to) : '', onInput: (e) => this.setPending('to', e.target.value) })))))));
|
|
902
|
-
}
|
|
903
|
-
if (dataType === 'DATETIME') {
|
|
904
|
-
const dtOp = pf.operator;
|
|
905
|
-
const noInput = dtOp === 'isEmpty' || dtOp === 'isNotEmpty';
|
|
906
|
-
const rangeMode = !noInput && (pf.from !== undefined || pf.to !== undefined);
|
|
907
|
-
return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => {
|
|
908
|
-
const val = e.target.value;
|
|
909
|
-
if (val === 'isEmpty' || val === 'isNotEmpty') {
|
|
910
|
-
this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: val, value: undefined, from: undefined, to: undefined });
|
|
911
|
-
}
|
|
912
|
-
else {
|
|
913
|
-
this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: undefined });
|
|
914
|
-
}
|
|
915
|
-
} }, h("option", { value: "", selected: !noInput }, t('filter_has_value', this.locale)), h("option", { value: "isEmpty", selected: dtOp === 'isEmpty' }, t('filter_is_empty', this.locale)), h("option", { value: "isNotEmpty", selected: dtOp === 'isNotEmpty' }, t('filter_is_not_empty', this.locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: !rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { from: undefined, to: undefined }); } }), t('filter_exact', this.locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null }); } }), t('filter_range', this.locale))), !rangeMode ? (h("input", { type: "date", class: "mrd-table__filter-input", value: String((_f = pf.value) !== null && _f !== void 0 ? _f : ''), onInput: (e) => this.setPending('value', e.target.value) })) : (h("div", { class: "mrd-table__filter-range mrd-table__filter-range--stacked" }, h("label", { class: "mrd-table__filter-range-label" }, t('filter_from', this.locale)), h("input", { type: "date", class: "mrd-table__filter-input", value: pf.from != null ? String(pf.from) : '', onInput: (e) => this.setPending('from', e.target.value) }), h("label", { class: "mrd-table__filter-range-label" }, t('filter_to', this.locale)), h("input", { type: "date", class: "mrd-table__filter-input", value: pf.to != null ? String(pf.to) : '', onInput: (e) => this.setPending('to', e.target.value) })))))));
|
|
916
|
-
}
|
|
917
|
-
if (DATE_TYPES.has(dataType)) {
|
|
918
|
-
const inputType = dataType === 'DATE' ? 'date' : 'time';
|
|
919
|
-
const dtdOp = pf.operator;
|
|
920
|
-
const noInput = dtdOp === 'isEmpty' || dtdOp === 'isNotEmpty';
|
|
921
|
-
const rangeMode = !noInput && (pf.from !== undefined || pf.to !== undefined);
|
|
922
|
-
return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => {
|
|
923
|
-
const val = e.target.value;
|
|
924
|
-
if (val === 'isEmpty' || val === 'isNotEmpty') {
|
|
925
|
-
this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: val, value: undefined, from: undefined, to: undefined });
|
|
926
|
-
}
|
|
927
|
-
else {
|
|
928
|
-
this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: undefined });
|
|
929
|
-
}
|
|
930
|
-
} }, h("option", { value: "", selected: !noInput }, t('filter_has_value', this.locale)), h("option", { value: "isEmpty", selected: dtdOp === 'isEmpty' }, t('filter_is_empty', this.locale)), h("option", { value: "isNotEmpty", selected: dtdOp === 'isNotEmpty' }, t('filter_is_not_empty', this.locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: !rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { from: undefined, to: undefined }); } }), t('filter_exact', this.locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null }); } }), t('filter_range', this.locale))), !rangeMode ? (h("input", { type: inputType, class: "mrd-table__filter-input", value: String((_g = pf.value) !== null && _g !== void 0 ? _g : ''), onInput: (e) => this.setPending('value', e.target.value) })) : (h("div", { class: "mrd-table__filter-range" }, h("input", { type: inputType, class: "mrd-table__filter-input", placeholder: t('filter_from', this.locale), value: pf.from != null ? String(pf.from) : '', onInput: (e) => this.setPending('from', e.target.value) }), h("input", { type: inputType, class: "mrd-table__filter-input", placeholder: t('filter_to', this.locale), value: pf.to != null ? String(pf.to) : '', onInput: (e) => this.setPending('to', e.target.value) })))))));
|
|
931
|
-
}
|
|
932
|
-
return null;
|
|
933
|
-
}
|
|
934
733
|
renderFilterPopup() {
|
|
935
|
-
var _a;
|
|
936
734
|
if (!this.openFilterCol || !this.pendingFilter)
|
|
937
735
|
return null;
|
|
938
736
|
const col = this.columns.find(c => this.colName(c) === this.openFilterCol);
|
|
939
737
|
if (!col)
|
|
940
738
|
return null;
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
739
|
+
return (h(FilterPopup, { col: col, dataType: this.colDataType(col), openFilterCol: this.openFilterCol, pendingFilter: this.pendingFilter, popupPos: this.popupPos, sortField: this.sortField, sortDir: this.sortDir, locale: this.locale, cb: {
|
|
740
|
+
onSetPending: (key, val) => this.setPending(key, val),
|
|
741
|
+
onReplacePending: pf => (this.pendingFilter = pf),
|
|
742
|
+
onToggleValue: (key, checked) => this.togglePendingValue(key, checked),
|
|
743
|
+
onApplySort: dir => this.applySort(col, dir),
|
|
744
|
+
onApply: () => this.applyFilter(),
|
|
745
|
+
onClear: () => this.clearFilter(),
|
|
746
|
+
onClose: () => this.closeFilterPopup(),
|
|
747
|
+
} }));
|
|
944
748
|
}
|
|
945
749
|
// ── Render: footer ────────────────────────────────────────────────────────
|
|
946
750
|
renderFooter(rowCount, effectiveTotal, isTotalKnown = true) {
|
|
@@ -965,54 +769,11 @@ export class MrdTable {
|
|
|
965
769
|
}
|
|
966
770
|
// ── Render: cell ──────────────────────────────────────────────────────────
|
|
967
771
|
renderCell(col, row) {
|
|
968
|
-
|
|
969
|
-
const numericTypes = new Set(['INTEGER', 'DECIMAL', 'PERCENTAGE', 'CURRENCY']);
|
|
970
|
-
const dataType = (_a = col.dataType) !== null && _a !== void 0 ? _a : '';
|
|
971
|
-
const isNumeric = col.type === 'FIELD' && numericTypes.has(dataType);
|
|
972
|
-
const isFile = col.type === 'FIELD' && (dataType === 'FILE' || dataType === 'IMAGE');
|
|
973
|
-
if (isFile) {
|
|
974
|
-
const name = (_b = col.name) !== null && _b !== void 0 ? _b : '';
|
|
975
|
-
const fileVal = row === null || row === void 0 ? void 0 : row[name];
|
|
976
|
-
const href = fileVal === null || fileVal === void 0 ? void 0 : fileVal.href;
|
|
977
|
-
const fileName = fileVal === null || fileVal === void 0 ? void 0 : fileVal.fileName;
|
|
978
|
-
return (h("td", { class: "mrd-table__cell" }, href && fileName ? (h("button", { class: "mrd-table__file-btn", title: fileName, onClick: (e) => {
|
|
979
|
-
e.stopPropagation();
|
|
980
|
-
this.mrdDownload.emit({ href, fileName });
|
|
981
|
-
} }, h("svg", { class: "mrd-table__file-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, h("path", { fill: "currentColor", d: "M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 7V3.5L18.5 9H13zm-3 8l-3-3 1.41-1.41L10 14.17l4.59-4.58L16 11l-6 6z" })), t('download', this.locale))) : ''));
|
|
982
|
-
}
|
|
983
|
-
if (dataType === 'JSON') {
|
|
984
|
-
const name = (_c = col.name) !== null && _c !== void 0 ? _c : '';
|
|
985
|
-
const rawValue = name ? row === null || row === void 0 ? void 0 : row[name] : undefined;
|
|
986
|
-
if (rawValue == null || rawValue === '')
|
|
987
|
-
return h("td", { class: "mrd-table__cell" });
|
|
988
|
-
const compactHtml = CellRenderer.formatJson(rawValue, true);
|
|
989
|
-
const prettyHtml = CellRenderer.formatJson(rawValue, false);
|
|
990
|
-
return (h("td", { class: "mrd-table__cell" }, h("span", { class: "mrd-table__json-preview", innerHTML: compactHtml }), h("button", { class: "mrd-table__textblock-btn", onClick: (e) => { e.stopPropagation(); this.openJsonModal(prettyHtml); }, "aria-label": t('textblock_show_more', this.locale) }, "\u22EF")));
|
|
991
|
-
}
|
|
992
|
-
const TEXTBLOCK_MAX = 200;
|
|
993
|
-
if (dataType === 'TEXTBLOCK') {
|
|
994
|
-
const full = CellRenderer.render(col, row, this.locale);
|
|
995
|
-
if (full.length <= TEXTBLOCK_MAX) {
|
|
996
|
-
return h("td", { class: "mrd-table__cell" }, full);
|
|
997
|
-
}
|
|
998
|
-
const preview = full.slice(0, TEXTBLOCK_MAX) + '…';
|
|
999
|
-
return (h("td", { class: "mrd-table__cell" }, preview, h("button", { class: "mrd-table__textblock-btn", onClick: (e) => { e.stopPropagation(); this.openTextblockModal(full); }, "aria-label": t('textblock_show_more', this.locale) }, "\u22EF")));
|
|
1000
|
-
}
|
|
1001
|
-
const value = CellRenderer.render(col, row, this.locale);
|
|
1002
|
-
return (h("td", { class: `mrd-table__cell${isNumeric ? ' mrd-table__cell--numeric' : ''}` }, value));
|
|
772
|
+
return (h(TableCell, { col: col, row: row, locale: this.locale, onDownload: detail => this.mrdDownload.emit(detail), onOpenTextblock: full => this.openTextblockModal(full), onOpenJson: html => this.openJsonModal(html) }));
|
|
1003
773
|
}
|
|
1004
774
|
// ── Render: totals row ────────────────────────────────────────────────────
|
|
1005
775
|
renderTotalsRow() {
|
|
1006
|
-
|
|
1007
|
-
return null;
|
|
1008
|
-
if (!this.columns.some(c => c.type === 'FIELD' && c.aggregate))
|
|
1009
|
-
return null;
|
|
1010
|
-
return (h("tfoot", null, h("tr", { class: "mrd-table__totals-row" }, this.columns.map(col => {
|
|
1011
|
-
var _a;
|
|
1012
|
-
const val = this.renderAggregationValue(col);
|
|
1013
|
-
const isNumeric = col.type === 'FIELD' && NUMERIC_TYPES.has((_a = col.dataType) !== null && _a !== void 0 ? _a : '');
|
|
1014
|
-
return (h("td", { class: `mrd-table__totals-cell${isNumeric ? ' mrd-table__totals-cell--numeric' : ''}` }, val));
|
|
1015
|
-
}))));
|
|
776
|
+
return h(TotalsRow, { columns: this.columns, aggregations: this.aggregations, locale: this.locale });
|
|
1016
777
|
}
|
|
1017
778
|
// ── Render ─────────────────────────────────────────────────────────────────
|
|
1018
779
|
render() {
|
|
@@ -1032,7 +793,7 @@ export class MrdTable {
|
|
|
1032
793
|
isFiltered ? 'mrd-table__header--filtered' : '',
|
|
1033
794
|
this.filterMode ? 'mrd-table__header--sortable' : '',
|
|
1034
795
|
].filter(Boolean).join(' ');
|
|
1035
|
-
return (h("th", { class: cls, onClick: this.filterMode ? (e) => this.handleFilterOpen(col, e) : undefined }, h("span", { class: "mrd-table__header-label" }, (_a = col.label) !== null && _a !== void 0 ? _a : ''), isFiltered &&
|
|
796
|
+
return (h("th", { class: cls, onClick: this.filterMode ? (e) => this.handleFilterOpen(col, e) : undefined }, h("span", { class: "mrd-table__header-label" }, (_a = col.label) !== null && _a !== void 0 ? _a : ''), isFiltered && h(FilterIcon, null)));
|
|
1036
797
|
}))), h("tbody", null, (_b = this.rows) === null || _b === void 0 ? void 0 : _b.map((row, i) => (h("tr", { class: "mrd-table__row mrd-table__row--clickable", style: { background: i % 2 === 0 ? '' : 'var(--mrd-color-neutral-100)' }, onClick: () => this.mrdRowClick.emit(row) }, this.columns.map(col => this.renderCell(col, row)))))), this.renderTotalsRow()), (!this.rows || this.rows.length === 0) && (h("p", { class: "mrd-table__empty" }, t('no_results', this.locale)))), this.renderFooter((_c = this.rows) === null || _c === void 0 ? void 0 : _c.length), this.renderFilterPopup(), this.renderTextblockModal(), this.renderJsonModal()));
|
|
1037
798
|
}
|
|
1038
799
|
// ── Paginated / virtual-scroll mode ────────────────────────────────────
|
|
@@ -1040,12 +801,7 @@ export class MrdTable {
|
|
|
1040
801
|
// if any loaded page is shorter than pageSize it is the last page,
|
|
1041
802
|
// so the true total cannot exceed (pageNum * pageSize + pageRows.length).
|
|
1042
803
|
// aggregationsTotal (from setAggregations) takes priority over the totalElements prop.
|
|
1043
|
-
|
|
1044
|
-
for (const [pageNum, pageRows] of this.loadedPages) {
|
|
1045
|
-
if (pageRows.length < this.pageSize) {
|
|
1046
|
-
effectiveTotal = Math.min(effectiveTotal, pageNum * this.pageSize + pageRows.length);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
804
|
+
const effectiveTotal = computeEffectiveTotal(this.loadedPages, this.pageSize, this.baseTotal);
|
|
1049
805
|
// Clamp renderEnd to what we actually know exists (-1 when empty)
|
|
1050
806
|
const clampedEnd = Math.min(this.renderEnd, effectiveTotal - 1);
|
|
1051
807
|
const colCount = this.columns.length;
|
|
@@ -1058,7 +814,13 @@ export class MrdTable {
|
|
|
1058
814
|
for (let i = this.renderStart; i <= clampedEnd; i++) {
|
|
1059
815
|
const row = this.getRow(i);
|
|
1060
816
|
if (row === null) {
|
|
1061
|
-
|
|
817
|
+
const page = Math.floor(i / this.pageSize);
|
|
818
|
+
if (this.failedPages.has(page)) {
|
|
819
|
+
renderedRows.push(h("tr", { class: "mrd-table__row mrd-table__row--failed" }, h("td", { class: "mrd-table__cell--failed", colSpan: colCount }, h("button", { type: "button", class: "mrd-table__retry-button", onClick: () => this.retryPage(page) }, t('load_failed_retry', this.locale)))));
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
renderedRows.push(h("tr", { class: "mrd-table__row mrd-table__row--loading" }, h("td", { class: "mrd-table__cell--placeholder", colSpan: colCount }, h("span", { class: "mrd-table__placeholder-bar" }))));
|
|
823
|
+
}
|
|
1062
824
|
}
|
|
1063
825
|
else {
|
|
1064
826
|
renderedRows.push(h("tr", { class: "mrd-table__row mrd-table__row--clickable", style: { background: i % 2 === 0 ? '' : 'var(--mrd-color-neutral-100)' }, onClick: () => this.mrdRowClick.emit(row) }, this.columns.map(col => this.renderCell(col, row))));
|
|
@@ -1076,21 +838,14 @@ export class MrdTable {
|
|
|
1076
838
|
isActive ? `mrd-table__header--sorted-${this.sortDir}` : '',
|
|
1077
839
|
isFiltered ? 'mrd-table__header--filtered' : '',
|
|
1078
840
|
].filter(Boolean).join(' ');
|
|
1079
|
-
return (h("th", { class: cls, style: this.colWidths[idx] ? { width: `${this.colWidths[idx]}px` } : undefined, onClick: isInteractive ? ((e) => this.filterMode ? this.handleFilterOpen(col, e) : this.handleSortClick(col)) : undefined }, h("span", { class: "mrd-table__header-label" }, (_a = col.label) !== null && _a !== void 0 ? _a : ''), isInteractive && isActive && (h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, this.sortDir === 'asc' ? '▲' : '▼')), isInteractive && !isActive && !this.filterMode && (h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, "\u21C5")), isInteractive && isFiltered &&
|
|
1080
|
-
}))), h("tbody", null, topSpacerHeight > 0 && (h("tr", { class: "mrd-table__spacer", style: { height: `${topSpacerHeight}px` } }, h("td", { colSpan: colCount }))), renderedRows, bottomSpacerHeight > 0 && (h("tr", { class: "mrd-table__spacer", style: { height: `${bottomSpacerHeight}px` } }, h("td", { colSpan: colCount })))), this.renderTotalsRow())), effectiveTotal === 0 && this.loadedPages.has(0) && (h("p", { class: "mrd-table__empty" }, t('no_results', this.locale))), effectiveTotal > 0 && this.renderFooter(undefined, effectiveTotal, this.isTotalKnown()), this.renderFilterPopup(), this.renderTextblockModal()));
|
|
1081
|
-
}
|
|
1082
|
-
renderFilterIcon() {
|
|
1083
|
-
return (h("span", { class: "mrd-table__filter-icon", "aria-hidden": "true" }, h("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "currentColor" }, h("path", { d: "M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" }))));
|
|
841
|
+
return (h("th", { class: cls, style: this.colWidths[idx] ? { width: `${this.colWidths[idx]}px` } : undefined, onClick: isInteractive ? ((e) => this.filterMode ? this.handleFilterOpen(col, e) : this.handleSortClick(col)) : undefined }, h("span", { class: "mrd-table__header-label" }, (_a = col.label) !== null && _a !== void 0 ? _a : ''), isInteractive && isActive && (h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, this.sortDir === 'asc' ? '▲' : '▼')), isInteractive && !isActive && !this.filterMode && (h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, "\u21C5")), isInteractive && isFiltered && h(FilterIcon, null)));
|
|
842
|
+
}))), h("tbody", null, topSpacerHeight > 0 && (h("tr", { class: "mrd-table__spacer", style: { height: `${topSpacerHeight}px` } }, h("td", { colSpan: colCount }))), renderedRows, bottomSpacerHeight > 0 && (h("tr", { class: "mrd-table__spacer", style: { height: `${bottomSpacerHeight}px` } }, h("td", { colSpan: colCount })))), this.renderTotalsRow())), effectiveTotal === 0 && this.loadedPages.has(0) && (h("p", { class: "mrd-table__empty" }, t('no_results', this.locale))), effectiveTotal > 0 && this.renderFooter(undefined, effectiveTotal, this.isTotalKnown()), this.renderFilterPopup(), this.renderTextblockModal(), this.renderJsonModal()));
|
|
1084
843
|
}
|
|
1085
844
|
renderTextblockModal() {
|
|
1086
|
-
|
|
1087
|
-
return null;
|
|
1088
|
-
return (h("div", { class: "mrd-table__modal-backdrop", onClick: () => this.closeTextblockModal(), role: "dialog", "aria-modal": "true" }, h("div", { class: "mrd-table__modal", onClick: (e) => e.stopPropagation() }, h("button", { class: "mrd-table__modal-close", onClick: () => this.closeTextblockModal(), "aria-label": t('close', this.locale) }, "\u2715"), h("p", { class: "mrd-table__modal-text" }, this.textblockModal))));
|
|
845
|
+
return h(TextblockModal, { text: this.textblockModal, locale: this.locale, onClose: () => this.closeTextblockModal() });
|
|
1089
846
|
}
|
|
1090
847
|
renderJsonModal() {
|
|
1091
|
-
|
|
1092
|
-
return null;
|
|
1093
|
-
return (h("div", { class: "mrd-table__modal-backdrop", onClick: () => this.closeJsonModal(), role: "dialog", "aria-modal": "true" }, h("div", { class: "mrd-table__modal", onClick: (e) => e.stopPropagation() }, h("button", { class: "mrd-table__modal-close", onClick: () => this.closeJsonModal(), "aria-label": t('close', this.locale) }, "\u2715"), h("pre", { class: "mrd-table__modal-json", innerHTML: this.jsonModal }))));
|
|
848
|
+
return h(JsonModal, { html: this.jsonModal, locale: this.locale, onClose: () => this.closeJsonModal() });
|
|
1094
849
|
}
|
|
1095
850
|
static get is() { return "mrd-table"; }
|
|
1096
851
|
static get encapsulation() { return "scoped"; }
|
|
@@ -1115,7 +870,7 @@ export class MrdTable {
|
|
|
1115
870
|
"references": {
|
|
1116
871
|
"ClientLayoutItem": {
|
|
1117
872
|
"location": "import",
|
|
1118
|
-
"path": "
|
|
873
|
+
"path": "../../../types/client-layout",
|
|
1119
874
|
"id": "src/types/client-layout.ts::ClientLayoutItem",
|
|
1120
875
|
"referenceLocation": "ClientLayoutItem"
|
|
1121
876
|
}
|
|
@@ -1273,6 +1028,26 @@ export class MrdTable {
|
|
|
1273
1028
|
"reflect": false,
|
|
1274
1029
|
"attribute": "table-height",
|
|
1275
1030
|
"defaultValue": "500"
|
|
1031
|
+
},
|
|
1032
|
+
"requestTimeoutMs": {
|
|
1033
|
+
"type": "number",
|
|
1034
|
+
"mutable": false,
|
|
1035
|
+
"complexType": {
|
|
1036
|
+
"original": "number",
|
|
1037
|
+
"resolved": "number",
|
|
1038
|
+
"references": {}
|
|
1039
|
+
},
|
|
1040
|
+
"required": false,
|
|
1041
|
+
"optional": false,
|
|
1042
|
+
"docs": {
|
|
1043
|
+
"tags": [],
|
|
1044
|
+
"text": "Time (ms) to wait for setPage() before retrying a page request; after\nMAX_RETRY_ATTEMPTS retries the page is marked failed (see failedPages)."
|
|
1045
|
+
},
|
|
1046
|
+
"getter": false,
|
|
1047
|
+
"setter": false,
|
|
1048
|
+
"reflect": false,
|
|
1049
|
+
"attribute": "request-timeout-ms",
|
|
1050
|
+
"defaultValue": "REQUEST_TIMEOUT_MS"
|
|
1276
1051
|
}
|
|
1277
1052
|
};
|
|
1278
1053
|
}
|
|
@@ -1281,6 +1056,7 @@ export class MrdTable {
|
|
|
1281
1056
|
"activeViewIdx": {},
|
|
1282
1057
|
"loadedPages": {},
|
|
1283
1058
|
"requestedPages": {},
|
|
1059
|
+
"failedPages": {},
|
|
1284
1060
|
"renderStart": {},
|
|
1285
1061
|
"renderEnd": {},
|
|
1286
1062
|
"colWidths": {},
|
|
@@ -1317,6 +1093,21 @@ export class MrdTable {
|
|
|
1317
1093
|
"resolved": "{ page: number; sort: string; path: string; qs: string; }",
|
|
1318
1094
|
"references": {}
|
|
1319
1095
|
}
|
|
1096
|
+
}, {
|
|
1097
|
+
"method": "mrdPageError",
|
|
1098
|
+
"name": "mrdPageError",
|
|
1099
|
+
"bubbles": true,
|
|
1100
|
+
"cancelable": true,
|
|
1101
|
+
"composed": true,
|
|
1102
|
+
"docs": {
|
|
1103
|
+
"tags": [],
|
|
1104
|
+
"text": "Fired when a page request timed out MAX_RETRY_ATTEMPTS times without setPage() being called.\nPurely informational \u2014 the table shows its own retry row regardless of whether a host listens."
|
|
1105
|
+
},
|
|
1106
|
+
"complexType": {
|
|
1107
|
+
"original": "{ page: number }",
|
|
1108
|
+
"resolved": "{ page: number; }",
|
|
1109
|
+
"references": {}
|
|
1110
|
+
}
|
|
1320
1111
|
}, {
|
|
1321
1112
|
"method": "mrdRowClick",
|
|
1322
1113
|
"name": "mrdRowClick",
|
|
@@ -1455,7 +1246,7 @@ export class MrdTable {
|
|
|
1455
1246
|
},
|
|
1456
1247
|
"AggregationResult": {
|
|
1457
1248
|
"location": "import",
|
|
1458
|
-
"path": "
|
|
1249
|
+
"path": "../../../types/client-layout",
|
|
1459
1250
|
"id": "src/types/client-layout.ts::AggregationResult",
|
|
1460
1251
|
"referenceLocation": "AggregationResult"
|
|
1461
1252
|
}
|