@mmlogic/components 0.2.0 → 0.3.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.
@@ -1364,7 +1364,6 @@ const MrdLayoutSection = class {
1364
1364
  this.mrdNavigate = createEvent(this, "mrdNavigate");
1365
1365
  this.mrdSearch = createEvent(this, "mrdSearch");
1366
1366
  this.mrdDownload = createEvent(this, "mrdDownload");
1367
- this.mrdLoadView = createEvent(this, "mrdLoadView");
1368
1367
  this.mrdLoadViewPage = createEvent(this, "mrdLoadViewPage");
1369
1368
  this.mrdLoadImage = createEvent(this, "mrdLoadImage");
1370
1369
  this.mrdViewAction = createEvent(this, "mrdViewAction");
@@ -1373,24 +1372,19 @@ const MrdLayoutSection = class {
1373
1372
  this.items = [];
1374
1373
  /** Record data object; keys are field names, _links holds relation and related-view links. */
1375
1374
  this.data = {};
1376
- /** View metadata map (ClientDashboardMetadata.views) for RELATED_VIEW and VIEW items. */
1375
+ /** Legacy: view metadata map (ClientDashboardMetadata.views). Not needed in new flat format. */
1377
1376
  this.views = {};
1378
- /** Top-level _links from ClientDashboardMetadata; used to resolve hrefs for VIEW items. */
1377
+ /** Legacy: top-level _links from ClientDashboardMetadata. Not needed in new flat format. */
1379
1378
  this.links = {};
1380
1379
  this.locale = navigator.language;
1381
1380
  this.searchQueryMap = {};
1382
1381
  this.searchResultsMap = {};
1383
1382
  this.imagePreviewUrl = null;
1384
1383
  this.imagePreviews = {};
1385
- this.activeViewMap = {};
1386
- this.viewLinksMap = {};
1387
- this.activeFiltersMap = {};
1388
1384
  this.searchTimers = {};
1389
1385
  this.handleViewLoadPage = (e, name) => {
1390
- var _a;
1391
1386
  e.stopPropagation();
1392
- const filters = (_a = this.activeFiltersMap[name]) !== null && _a !== void 0 ? _a : [];
1393
- this.mrdLoadViewPage.emit({ name, page: e.detail.page, sort: e.detail.sort, filters });
1387
+ this.mrdLoadViewPage.emit({ name, page: e.detail.page, sort: e.detail.sort, path: e.detail.path, qs: e.detail.qs });
1394
1388
  };
1395
1389
  this.handleSearchInput = (dataClass, query) => {
1396
1390
  this.searchQueryMap = Object.assign(Object.assign({}, this.searchQueryMap), { [dataClass]: query });
@@ -1407,59 +1401,29 @@ const MrdLayoutSection = class {
1407
1401
  }
1408
1402
  componentDidLoad() {
1409
1403
  setTimeout(() => {
1410
- this.emitLoadViews();
1404
+ this.initEmbeddedTables();
1411
1405
  this.emitLoadImages();
1412
1406
  }, 0);
1413
1407
  }
1414
- linksChanged(newVal) {
1415
- if (Object.keys(newVal !== null && newVal !== void 0 ? newVal : {}).length > 0) {
1416
- this.emitLoadViews();
1417
- }
1418
- }
1419
1408
  dataChanged(newVal) {
1420
- var _a;
1421
- if (newVal && Object.keys((_a = newVal === null || newVal === void 0 ? void 0 : newVal._links) !== null && _a !== void 0 ? _a : {}).length > 0) {
1422
- this.emitLoadViews();
1409
+ if (newVal && Object.keys(newVal).length > 0) {
1410
+ setTimeout(() => this.initEmbeddedTables(), 0);
1423
1411
  }
1424
1412
  }
1425
- resolveViewFilters(viewConfig) {
1426
- var _a;
1427
- return ((_a = viewConfig.filter) !== null && _a !== void 0 ? _a : []).map(f => {
1428
- var _a, _b, _c, _d;
1429
- const base = { field: f.name, dataType: 'TEXT' };
1430
- switch (f.operator) {
1431
- case 'FROM': return Object.assign(Object.assign({}, base), { from: (_a = f.value) !== null && _a !== void 0 ? _a : null });
1432
- case 'TO': return Object.assign(Object.assign({}, base), { to: (_b = f.value) !== null && _b !== void 0 ? _b : null });
1433
- case 'STARTS_WITH': return Object.assign(Object.assign({}, base), { operator: 'startsWith', value: (_c = f.value) !== null && _c !== void 0 ? _c : null });
1434
- case 'NOT_EMPTY': return Object.assign(Object.assign({}, base), { operator: 'isNotEmpty' });
1435
- case 'EMPTY': return Object.assign(Object.assign({}, base), { operator: 'isEmpty' });
1436
- default: return Object.assign(Object.assign({}, base), { operator: 'equals', value: (_d = f.value) !== null && _d !== void 0 ? _d : null });
1437
- }
1438
- });
1439
- }
1440
- emitLoadViews() {
1441
- var _a, _b, _c, _d, _e, _f, _g;
1442
- const dataLinks = ((_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a._links) !== null && _b !== void 0 ? _b : {});
1443
- for (const item of this.flattenItems(this.items)) {
1444
- if (item.type === ClientLayoutItemType.RELATED_VIEW && item.name) {
1445
- const viewConfig = this.views[item.name];
1446
- if (!viewConfig)
1447
- continue;
1448
- const href = (_d = dataLinks[(_c = item.relatedClass) !== null && _c !== void 0 ? _c : '']) === null || _d === void 0 ? void 0 : _d.href;
1449
- this.mrdLoadView.emit({ name: item.name, href, viewConfig, sort: (_e = viewConfig.defaultSort) !== null && _e !== void 0 ? _e : '', filters: this.resolveViewFilters(viewConfig) });
1450
- }
1451
- else if (item.type === ClientLayoutItemType.VIEW) {
1452
- const viewName = item.name;
1453
- if (!viewName)
1454
- continue;
1455
- const viewConfig = this.views[viewName];
1456
- if (!viewConfig)
1457
- continue;
1458
- const href = (_f = this.links[viewName]) === null || _f === void 0 ? void 0 : _f.href;
1459
- this.mrdLoadView.emit({ name: viewName, href, viewConfig, sort: (_g = viewConfig.defaultSort) !== null && _g !== void 0 ? _g : '', filters: this.resolveViewFilters(viewConfig) });
1413
+ async initEmbeddedTables() {
1414
+ const tables = this.el.querySelectorAll('mrd-table[data-view]');
1415
+ for (const table of Array.from(tables)) {
1416
+ if (typeof table.init === 'function') {
1417
+ await table.init();
1460
1418
  }
1461
1419
  }
1462
1420
  }
1421
+ viewKeyFor(item) {
1422
+ var _a, _b, _c, _d;
1423
+ if (item.type === ClientLayoutItemType.RELATED_VIEW)
1424
+ return (_b = (_a = item.relatedClass) !== null && _a !== void 0 ? _a : item.name) !== null && _b !== void 0 ? _b : '';
1425
+ return (_d = (_c = item.dataClass) !== null && _c !== void 0 ? _c : item.name) !== null && _d !== void 0 ? _d : '';
1426
+ }
1463
1427
  emitLoadImages() {
1464
1428
  for (const item of this.flattenItems(this.items)) {
1465
1429
  if (item.type === ClientLayoutItemType.FIELD && item.dataType === ClientLayoutItemFieldDataType.IMAGE) {
@@ -1489,21 +1453,17 @@ const MrdLayoutSection = class {
1489
1453
  }
1490
1454
  /**
1491
1455
  * Inject data into an embedded mrd-table for a RELATED_VIEW or VIEW item.
1492
- * Pass totalElements on page 0 to initialise the table; omit on subsequent pages.
1493
- * Pass pageLinks (_links from the page response) on page 0 to enable action hrefs in mrdViewAction.
1456
+ * Pass totalElements to update the pagination total (safe to pass on every page).
1457
+ * Pass hasNext (from _links.next presence) so the table can decide whether to emit aggregations.
1494
1458
  */
1495
- async setViewPage(name, page, rows, totalElements, pageLinks) {
1496
- if (pageLinks) {
1497
- this.viewLinksMap = Object.assign(Object.assign({}, this.viewLinksMap), { [name]: pageLinks });
1498
- }
1459
+ async setViewPage(name, page, rows, totalElements, hasNext) {
1499
1460
  const table = this.el.querySelector(`mrd-table[data-view="${name}"]`);
1500
1461
  if (!table)
1501
1462
  return;
1502
1463
  if (totalElements !== undefined) {
1503
1464
  table.totalElements = totalElements;
1504
- await table.init();
1505
1465
  }
1506
- await table.setPage(page, rows);
1466
+ await table.setPage(page, rows, hasNext);
1507
1467
  }
1508
1468
  /** Inject aggregation totals into an embedded mrd-table for a VIEW or RELATED_VIEW item. */
1509
1469
  async setViewAggregations(name, data) {
@@ -1637,63 +1597,36 @@ const MrdLayoutSection = class {
1637
1597
  return (h("div", { class: "mrd-layout-section__search", key: `search-${dataClass}` }, h("div", { class: "mrd-layout-section__search-wrap" }, h("svg", { class: "mrd-layout-section__search-icon", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" }, h("path", { "fill-rule": "evenodd", d: "M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z", "clip-rule": "evenodd" })), h("input", { class: "mrd-layout-section__search-input", type: "text", value: query, placeholder: (_c = item.label) !== null && _c !== void 0 ? _c : '', onInput: e => this.handleSearchInput(dataClass, e.target.value) })), results.length > 0 && (h("ul", { class: "mrd-layout-section__search-results" }, results.map(r => (h("li", { key: r.id, class: "mrd-layout-section__search-result" }, h("button", { class: "mrd-layout-section__search-result-btn", onClick: () => this.mrdNavigate.emit({ href: r.id, label: r.label }) }, h("span", { class: "mrd-layout-section__search-result-label" }, r.label), r.description && h("span", { class: "mrd-layout-section__search-result-desc" }, r.description)))))))));
1638
1598
  }
1639
1599
  renderRelatedView(item) {
1640
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1641
- const isRelated = item.type === ClientLayoutItemType.RELATED_VIEW;
1642
- const name = item.name;
1643
- if (!name)
1600
+ var _a, _b, _c, _d, _e, _f;
1601
+ const key = this.viewKeyFor(item);
1602
+ if (!key)
1644
1603
  return null;
1645
- const viewConfig = this.views[name];
1646
- if (!viewConfig)
1604
+ if (!item.view)
1647
1605
  return null;
1648
1606
  const showTitle = (_a = item.showTitle) !== null && _a !== void 0 ? _a : false;
1649
- const activeName = (_b = this.activeViewMap[name]) !== null && _b !== void 0 ? _b : name;
1650
- const activeViewConfig = (_c = this.views[activeName]) !== null && _c !== void 0 ? _c : viewConfig;
1651
- // Build the full view list (original + alternatives) so the switcher can always go back.
1652
- const originalLabel = (_f = (_e = (_d = viewConfig.pluralLabel) !== null && _d !== void 0 ? _d : viewConfig.singularLabel) !== null && _e !== void 0 ? _e : item.label) !== null && _f !== void 0 ? _f : name;
1653
- const allViews = [{ name, label: originalLabel }, ...((_g = item.alternativeViews) !== null && _g !== void 0 ? _g : [])];
1654
- const activeEntry = allViews.find(v => v.name === activeName);
1655
- const viewLabel = (_k = (_j = (_h = activeEntry === null || activeEntry === void 0 ? void 0 : activeEntry.label) !== null && _h !== void 0 ? _h : activeViewConfig.pluralLabel) !== null && _j !== void 0 ? _j : activeViewConfig.singularLabel) !== null && _k !== void 0 ? _k : '';
1656
- const altViews = allViews.filter(v => v.name !== activeName);
1657
- const rawActions = (_l = item.actions) !== null && _l !== void 0 ? _l : ['NEW', 'EXPORT'];
1658
- const tableActions = rawActions.reduce((acc, a) => {
1659
- if (a === 'NEW')
1660
- acc.push({ action: 'create', label: t('table_new_record', this.locale), icon: 'assets/sprites.svg#icon-plus', variant: 'primary' });
1661
- if (a === 'EXPORT')
1662
- acc.push({ action: 'export', label: t('table_export_excel', this.locale), icon: 'assets/sprites.svg#icon-file-excel' });
1663
- return acc;
1664
- }, []);
1665
- return (h("div", { class: "mrd-layout-section__related-view", key: `view-${name}` }, showTitle && item.label && h("h3", { class: "mrd-layout-section__related-view-title" }, item.label), h("mrd-table", { "data-view": name, columns: activeViewConfig.values, locale: this.locale, defaultSort: (_m = activeViewConfig.defaultSort) !== null && _m !== void 0 ? _m : '', viewLabel: viewLabel, alternativeViews: altViews, actions: tableActions, onMrdLoadPage: (e) => this.handleViewLoadPage(e, name), onMrdSwitchView: (e) => {
1666
- var _a, _b, _c, _d, _e, _f, _g, _h;
1667
- e.stopPropagation();
1668
- const newViewName = e.detail.name;
1669
- const newViewConfig = this.views[newViewName];
1670
- if (!newViewConfig)
1671
- return;
1672
- this.activeViewMap = Object.assign(Object.assign({}, this.activeViewMap), { [name]: newViewName });
1673
- this.activeFiltersMap = Object.assign(Object.assign({}, this.activeFiltersMap), { [name]: [] });
1674
- const dataLinks = ((_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a._links) !== null && _b !== void 0 ? _b : {});
1675
- const href = isRelated
1676
- ? (_d = dataLinks[(_c = item.relatedClass) !== null && _c !== void 0 ? _c : '']) === null || _d === void 0 ? void 0 : _d.href
1677
- : ((_f = (_e = this.links[newViewName]) === null || _e === void 0 ? void 0 : _e.href) !== null && _f !== void 0 ? _f : (_g = this.links[name]) === null || _g === void 0 ? void 0 : _g.href);
1678
- this.mrdLoadView.emit({ name, href, viewConfig: newViewConfig, sort: (_h = newViewConfig.defaultSort) !== null && _h !== void 0 ? _h : '', filters: this.resolveViewFilters(newViewConfig) });
1679
- }, onMrdFilter: (e) => {
1607
+ // Extract parentId from data._links.self.href for RELATED_VIEW path construction
1608
+ const selfHref = (_e = (_d = (_c = (_b = this.data) === null || _b === void 0 ? void 0 : _b._links) === null || _c === void 0 ? void 0 : _c.self) === null || _d === void 0 ? void 0 : _d.href) !== null && _e !== void 0 ? _e : '';
1609
+ const parentId = (_f = selfHref.split('/').filter(Boolean).pop()) !== null && _f !== void 0 ? _f : '';
1610
+ return (h("div", { class: "mrd-layout-section__related-view", key: `view-${key}` }, showTitle && item.label && h("h3", { class: "mrd-layout-section__related-view-title" }, item.label), h("mrd-table", { "data-view": key, item: item, parentId: parentId, locale: this.locale, onMrdLoadPage: (e) => this.handleViewLoadPage(e, key), onMrdLoadAggregations: (e) => {
1611
+ var _a;
1680
1612
  e.stopPropagation();
1681
- this.activeFiltersMap = Object.assign(Object.assign({}, this.activeFiltersMap), { [name]: e.detail.filters });
1682
- }, onMrdLoadAggregations: (e) => {
1683
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1613
+ this.mrdLoadViewAggregations.emit(Object.assign({ name: key, dataClass: (_a = item.dataClass) !== null && _a !== void 0 ? _a : key }, e.detail));
1614
+ }, onMrdRowClick: (e) => {
1615
+ var _a, _b, _c;
1684
1616
  e.stopPropagation();
1685
- const dataLinks = ((_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a._links) !== null && _b !== void 0 ? _b : {});
1686
- const href = isRelated
1687
- ? (_d = dataLinks[(_c = item.relatedClass) !== null && _c !== void 0 ? _c : '']) === null || _d === void 0 ? void 0 : _d.href
1688
- : ((_g = (_f = this.links[(_e = this.activeViewMap[name]) !== null && _e !== void 0 ? _e : name]) === null || _f === void 0 ? void 0 : _f.href) !== null && _g !== void 0 ? _g : (_h = this.links[name]) === null || _h === void 0 ? void 0 : _h.href);
1689
- const filters = (_j = this.activeFiltersMap[name]) !== null && _j !== void 0 ? _j : [];
1690
- this.mrdLoadViewAggregations.emit(Object.assign({ name, href, filters }, e.detail));
1617
+ const row = e.detail;
1618
+ this.mrdNavigate.emit({ href: (_b = (_a = row === null || row === void 0 ? void 0 : row._links) === null || _a === void 0 ? void 0 : _a.self) === null || _b === void 0 ? void 0 : _b.href, label: (_c = row === null || row === void 0 ? void 0 : row.name) !== null && _c !== void 0 ? _c : '' });
1691
1619
  }, onMrdAction: (e) => {
1692
- var _a, _b, _c;
1620
+ var _a;
1693
1621
  e.stopPropagation();
1694
- const pl = (_a = this.viewLinksMap[name]) !== null && _a !== void 0 ? _a : {};
1695
- const href = e.detail.action === 'export' ? (_b = pl['excel']) === null || _b === void 0 ? void 0 : _b.href : (_c = pl['self']) === null || _c === void 0 ? void 0 : _c.href;
1696
- this.mrdViewAction.emit({ name, action: e.detail.action, href });
1622
+ this.mrdViewAction.emit({
1623
+ name: key,
1624
+ action: e.detail.action,
1625
+ dataClass: (_a = item.dataClass) !== null && _a !== void 0 ? _a : key,
1626
+ path: e.detail.path,
1627
+ qs: e.detail.qs,
1628
+ parentPath: e.detail.parentPath,
1629
+ });
1697
1630
  } })));
1698
1631
  }
1699
1632
  renderItem(item) {
@@ -1727,13 +1660,10 @@ const MrdLayoutSection = class {
1727
1660
  return (h("div", { class: "mrd-layout-section__modal-backdrop", onClick: () => { this.imagePreviewUrl = null; } }, h("div", { class: "mrd-layout-section__modal", onClick: (e) => e.stopPropagation() }, h("button", { class: "mrd-layout-section__modal-close", onClick: () => { this.imagePreviewUrl = null; } }, "\u2715"), h("img", { class: "mrd-layout-section__modal-image", src: this.imagePreviewUrl, alt: "" }))));
1728
1661
  }
1729
1662
  render() {
1730
- return (h(Host, { key: '7a91a541e056965dc79d74a50827e8c270c33a0d' }, h("div", { key: 'bcf4a2e81e704d136fb437cd2cb22acb4a05a8b3', class: "mrd-layout-section" }, this.items.map(item => this.renderItem(item))), this.renderImageModal()));
1663
+ return (h(Host, { key: '0a3a58f5c80716bc0a7ba1b9468b721706d2ce4a' }, h("div", { key: '7cf63580c584811c1bb84b419e8d13026e432fba', class: "mrd-layout-section" }, this.items.map(item => this.renderItem(item))), this.renderImageModal()));
1731
1664
  }
1732
1665
  get el() { return getElement(this); }
1733
1666
  static get watchers() { return {
1734
- "links": [{
1735
- "linksChanged": 0
1736
- }],
1737
1667
  "data": [{
1738
1668
  "dataChanged": 0
1739
1669
  }]
@@ -2199,9 +2129,7 @@ const MrdTable = class {
2199
2129
  this.mrdLoadPage = createEvent(this, "mrdLoadPage");
2200
2130
  this.mrdRowClick = createEvent(this, "mrdRowClick");
2201
2131
  this.mrdAction = createEvent(this, "mrdAction");
2202
- this.mrdFilter = createEvent(this, "mrdFilter");
2203
2132
  this.mrdDownload = createEvent(this, "mrdDownload");
2204
- this.mrdSwitchView = createEvent(this, "mrdSwitchView");
2205
2133
  this.mrdLoadAggregations = createEvent(this, "mrdLoadAggregations");
2206
2134
  // ── Non-state internals ────────────────────────────────────────────────────
2207
2135
  this.pendingPages = new Set();
@@ -2209,7 +2137,10 @@ const MrdTable = class {
2209
2137
  this.outsideClickHandler = null;
2210
2138
  this.keydownHandler = null;
2211
2139
  // ── Props ──────────────────────────────────────────────────────────────────
2212
- this.columns = [];
2140
+ /** The VIEW or RELATED_VIEW layout item. Contains view config, dataClass, fromClass, actions etc. */
2141
+ this.item = null;
2142
+ /** Parent record id — required for RELATED_VIEW to build /{fromClass}/{parentId}/{dataClass}. */
2143
+ this.parentId = '';
2213
2144
  /** Direct rows (non-paginated mode, used when totalElements === 0). */
2214
2145
  this.rows = [];
2215
2146
  this.locale = navigator.language;
@@ -2221,16 +2152,9 @@ const MrdTable = class {
2221
2152
  this.rowHeight = 36;
2222
2153
  /** Height of the scroll container in px. */
2223
2154
  this.tableHeight = 500;
2224
- /** Initial sort applied on load, e.g. "timestamp,desc" or "name".
2225
- * Parsed by init() into sortField + sortDir. */
2226
- this.defaultSort = '';
2227
- /** Toolbar action buttons rendered above the table. */
2228
- this.actions = [];
2229
- /** Display label of the current view — shown in the toolbar center as a view picker trigger. */
2230
- this.viewLabel = '';
2231
- /** Alternative views available for this table; renders a dropdown when non-empty. */
2232
- this.alternativeViews = [];
2233
2155
  // ── Internal state ─────────────────────────────────────────────────────────
2156
+ /** Index into allViews[] for the currently displayed view. 0 = primary, 1+ = alternatives. */
2157
+ this.activeViewIdx = 0;
2234
2158
  this.loadedPages = new Map();
2235
2159
  this.requestedPages = new Set();
2236
2160
  this.renderStart = 0;
@@ -2256,10 +2180,16 @@ const MrdTable = class {
2256
2180
  this.jsonModal = null;
2257
2181
  /** Aggregation totals received from the host via setAggregations(). Null = not yet loaded. */
2258
2182
  this.aggregations = null;
2183
+ /** Record count received via setAggregations().total; overrides totalElements for display. */
2184
+ this.aggregationsTotal = null;
2185
+ /** True when a fresh aggregations request is needed (set on init / filter change). */
2186
+ this.aggregationsPending = false;
2187
+ /** Lower bound on total derived from setPage() hasNext info; grows as pages load. */
2188
+ this.minKnownTotal = 0;
2259
2189
  this.handleScroll = (e) => {
2260
2190
  const scroller = e.currentTarget;
2261
2191
  const scrollTop = scroller.scrollTop;
2262
- const total = this.totalElements;
2192
+ const total = this.baseTotal;
2263
2193
  const visStart = Math.floor(scrollTop / this.rowHeight);
2264
2194
  const visEnd = Math.min(visStart + this.visibleCount(), total - 1);
2265
2195
  this.scrollTop = scrollTop;
@@ -2273,13 +2203,16 @@ const MrdTable = class {
2273
2203
  totalElementsChanged(newVal) {
2274
2204
  this.renderEnd = Math.min(this.renderEnd, Math.max(0, newVal - 1));
2275
2205
  }
2276
- /** Apply defaultSort when the prop changes (e.g. after a view switch). */
2277
- defaultSortChanged(newVal) {
2278
- this.applyDefaultSort(newVal);
2206
+ /** Reset to primary view when the item prop is replaced from outside. */
2207
+ itemChanged(newVal) {
2208
+ var _a, _b;
2209
+ this.activeViewIdx = 0;
2210
+ this.applyDefaultSort((_b = (_a = newVal === null || newVal === void 0 ? void 0 : newVal.view) === null || _a === void 0 ? void 0 : _a.defaultSort) !== null && _b !== void 0 ? _b : '');
2279
2211
  }
2280
2212
  // ── Lifecycle ──────────────────────────────────────────────────────────────
2281
2213
  componentWillLoad() {
2282
- this.applyDefaultSort(this.defaultSort);
2214
+ var _a, _b, _c;
2215
+ 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 : '');
2283
2216
  }
2284
2217
  // ── Helpers ────────────────────────────────────────────────────────────────
2285
2218
  applyDefaultSort(defaultSort) {
@@ -2311,14 +2244,19 @@ const MrdTable = class {
2311
2244
  this.colWidths = [];
2312
2245
  this.scrollTop = 0;
2313
2246
  this.renderStart = 0;
2314
- // No BUFFER on init — only request what fits the visible area (page 0).
2315
- // BUFFER is applied during scroll to pre-fetch the next page proactively.
2316
- this.renderEnd = Math.max(0, Math.min(this.visibleCount() - 1, this.totalElements - 1));
2247
+ // Always fill the visible viewport on init — totalElements may be stale from a
2248
+ // previous view. setPage() clamps renderEnd when the page is shorter than pageSize.
2249
+ this.renderEnd = this.visibleCount() - 1;
2317
2250
  const scroller = this.el.querySelector('.mrd-table__scroll');
2318
2251
  if (scroller)
2319
2252
  scroller.scrollTop = 0;
2320
2253
  this.aggregations = null;
2321
- this.emitLoadAggregations();
2254
+ this.aggregationsTotal = null;
2255
+ this.aggregationsPending = true;
2256
+ this.minKnownTotal = 0;
2257
+ // Always request page 0 — totalElements may be unknown (0) on first load.
2258
+ this.mrdLoadPage.emit({ page: 0, sort: this.sortParam(), path: this.buildDataPath(), qs: this.buildQueryParams(0) });
2259
+ this.requestedPages = new Set([0]);
2322
2260
  }
2323
2261
  /**
2324
2262
  * Inject the rows for a given page (0-based).
@@ -2327,20 +2265,41 @@ const MrdTable = class {
2327
2265
  * When the page contains fewer rows than pageSize it is the last page.
2328
2266
  * renderEnd is clamped immediately so no loading-placeholder rows appear
2329
2267
  * beyond the actual data — without requiring the host to update totalElements.
2268
+ *
2269
+ * Pass hasNext (from _links.next in the API response) for accurate last-page
2270
+ * detection even when rows.length === pageSize (exact multiple of page size).
2330
2271
  */
2331
- async setPage(pageNumber, rows) {
2332
- if (rows.length < this.pageSize) {
2272
+ async setPage(pageNumber, rows, hasNext) {
2273
+ const isLastPage = hasNext !== undefined ? !hasNext : rows.length < this.pageSize;
2274
+ if (isLastPage) {
2333
2275
  // lastRowIdx is -1 when the page is empty; clamp renderEnd to -1 so the
2334
2276
  // render loop does not execute and no shimmer rows appear.
2335
2277
  const lastRowIdx = pageNumber * this.pageSize + rows.length - 1;
2336
2278
  this.renderEnd = Math.min(this.renderEnd, lastRowIdx);
2279
+ // Exact total is known: update minKnownTotal so the scroll container has the right height.
2280
+ this.minKnownTotal = pageNumber * this.pageSize + rows.length;
2281
+ }
2282
+ else {
2283
+ // There is at least one more page — ensure the scrollbar covers at least that next page.
2284
+ this.minKnownTotal = Math.max(this.minKnownTotal, (pageNumber + 1) * this.pageSize + 1);
2337
2285
  }
2338
2286
  const next = new Map(this.loadedPages);
2339
2287
  next.set(pageNumber, rows);
2340
2288
  this.loadedPages = next;
2289
+ if (pageNumber === 0 && this.aggregationsPending) {
2290
+ this.aggregationsPending = false;
2291
+ const hasAggColumns = this.columns.some(c => c.type === 'FIELD' && c.aggregate);
2292
+ if (!isLastPage || hasAggColumns) {
2293
+ this.emitLoadAggregations();
2294
+ }
2295
+ }
2341
2296
  }
2342
2297
  /** Inject aggregation totals returned by the /aggregations endpoint. */
2343
2298
  async setAggregations(data) {
2299
+ var _a;
2300
+ const total = (_a = data.total) !== null && _a !== void 0 ? _a : (typeof data.count === 'number' ? data.count : undefined);
2301
+ if (total != null)
2302
+ this.aggregationsTotal = total;
2344
2303
  this.aggregations = data;
2345
2304
  }
2346
2305
  // ── Lifecycle ──────────────────────────────────────────────────────────────
@@ -2371,6 +2330,147 @@ const MrdTable = class {
2371
2330
  return '';
2372
2331
  return this.sortDir === 'desc' ? `${this.sortField},desc` : this.sortField;
2373
2332
  }
2333
+ /** Stable ordered list: primary view first, then alternatives (from the item prop). */
2334
+ get allViews() {
2335
+ var _a, _b, _c, _d, _e, _f, _g;
2336
+ if (!this.item)
2337
+ return [];
2338
+ const it = this.item;
2339
+ return [
2340
+ { label: (_e = (_d = (_c = (_a = it.label) !== null && _a !== void 0 ? _a : (_b = it.view) === null || _b === void 0 ? void 0 : _b.pluralLabel) !== null && _c !== void 0 ? _c : it.dataClass) !== null && _d !== void 0 ? _d : it.relatedClass) !== null && _e !== void 0 ? _e : '', dataClass: (_f = it.dataClass) !== null && _f !== void 0 ? _f : it.relatedClass, fromClass: it.fromClass, filterClass: it.filterClass, view: it.view },
2341
+ ...((_g = it.alternativeViews) !== null && _g !== void 0 ? _g : []).map(av => {
2342
+ var _a, _b, _c, _d;
2343
+ return ({
2344
+ label: (_d = (_c = (_a = av.label) !== null && _a !== void 0 ? _a : (_b = av.view) === null || _b === void 0 ? void 0 : _b.pluralLabel) !== null && _c !== void 0 ? _c : av.dataClass) !== null && _d !== void 0 ? _d : '',
2345
+ dataClass: av.dataClass,
2346
+ fromClass: av.fromClass,
2347
+ filterClass: av.filterClass,
2348
+ view: av.view,
2349
+ });
2350
+ }),
2351
+ ];
2352
+ }
2353
+ /** Relative excel export path for the current view.
2354
+ * VIEW: /excel/{dataClass}
2355
+ * RELATED_VIEW: /excel/{fromClass}/{parentId}/{dataClass} */
2356
+ buildExcelPath() {
2357
+ var _a, _b, _c, _d;
2358
+ const v = this.allViews[this.activeViewIdx];
2359
+ if (!v)
2360
+ return '';
2361
+ if (((_a = this.item) === null || _a === void 0 ? void 0 : _a.type) === 'RELATED_VIEW') {
2362
+ return `/excel/${(_b = v.fromClass) !== null && _b !== void 0 ? _b : ''}/${this.parentId}/${(_c = v.dataClass) !== null && _c !== void 0 ? _c : ''}`;
2363
+ }
2364
+ return `/excel/${(_d = v.dataClass) !== null && _d !== void 0 ? _d : ''}`;
2365
+ }
2366
+ buildActionDetail(action) {
2367
+ var _a, _b, _c;
2368
+ if (action === 'export') {
2369
+ return { action, path: this.buildExcelPath(), qs: this.buildQueryParams(0) };
2370
+ }
2371
+ if (action === 'create') {
2372
+ const v = this.allViews[this.activeViewIdx];
2373
+ const parentPath = ((_a = this.item) === null || _a === void 0 ? void 0 : _a.type) === 'RELATED_VIEW'
2374
+ ? `/${(_b = v === null || v === void 0 ? void 0 : v.fromClass) !== null && _b !== void 0 ? _b : ''}/${this.parentId}`
2375
+ : null;
2376
+ return { action, dataClass: (_c = v === null || v === void 0 ? void 0 : v.dataClass) !== null && _c !== void 0 ? _c : '', parentPath };
2377
+ }
2378
+ return { action };
2379
+ }
2380
+ /** Relative data path for the current view, without query string.
2381
+ * VIEW: /{dataClass}
2382
+ * RELATED_VIEW: /{fromClass}/{parentId}/{dataClass} */
2383
+ buildDataPath() {
2384
+ var _a, _b, _c, _d;
2385
+ const v = this.allViews[this.activeViewIdx];
2386
+ if (!v)
2387
+ return '';
2388
+ if (((_a = this.item) === null || _a === void 0 ? void 0 : _a.type) === 'RELATED_VIEW') {
2389
+ return `/${(_b = v.fromClass) !== null && _b !== void 0 ? _b : ''}/${this.parentId}/${(_c = v.dataClass) !== null && _c !== void 0 ? _c : ''}`;
2390
+ }
2391
+ return `/${(_d = v.dataClass) !== null && _d !== void 0 ? _d : ''}`;
2392
+ }
2393
+ /** Build query params for a page request from current sort, view filters, filterClass and active column filters. */
2394
+ buildQueryParams(page) {
2395
+ var _a, _b, _c, _d, _e, _f, _g;
2396
+ const v = this.allViews[this.activeViewIdx];
2397
+ const p = new URLSearchParams();
2398
+ if (page > 0)
2399
+ p.set('page', String(page));
2400
+ const sort = this.sortParam();
2401
+ if (sort)
2402
+ p.set('sort', sort);
2403
+ const filterClass = v === null || v === void 0 ? void 0 : v.filterClass;
2404
+ if (filterClass)
2405
+ p.set('type', filterClass);
2406
+ 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 : [])) {
2407
+ if (!f.name)
2408
+ continue;
2409
+ if (f.operator === 'EMPTY') {
2410
+ p.set(f.name, '');
2411
+ continue;
2412
+ }
2413
+ if (f.operator === 'NOT_EMPTY') {
2414
+ p.set(f.name + '_notempty', 'true');
2415
+ continue;
2416
+ }
2417
+ if (f.operator === 'STARTS_WITH') {
2418
+ p.set(f.name + '_startswith', String((_c = f.value) !== null && _c !== void 0 ? _c : ''));
2419
+ continue;
2420
+ }
2421
+ if (f.operator === 'FROM') {
2422
+ p.set(f.name + '_from', String((_d = f.value) !== null && _d !== void 0 ? _d : ''));
2423
+ continue;
2424
+ }
2425
+ if (f.operator === 'TO') {
2426
+ p.set(f.name + '_to', String((_e = f.value) !== null && _e !== void 0 ? _e : ''));
2427
+ continue;
2428
+ }
2429
+ if (f.value != null) {
2430
+ p.set(f.name, String(f.value));
2431
+ }
2432
+ }
2433
+ for (const f of this.activeFilters.values()) {
2434
+ if (f.operator === 'isEmpty') {
2435
+ p.set(f.field, '');
2436
+ continue;
2437
+ }
2438
+ if (f.operator === 'isNotEmpty') {
2439
+ p.set(f.field + '_notempty', 'true');
2440
+ continue;
2441
+ }
2442
+ if (f.operator === 'startsWith') {
2443
+ p.set(f.field + '_startswith', String((_f = f.value) !== null && _f !== void 0 ? _f : ''));
2444
+ continue;
2445
+ }
2446
+ if ((_g = f.values) === null || _g === void 0 ? void 0 : _g.length) {
2447
+ p.set(f.field, f.values.join(','));
2448
+ continue;
2449
+ }
2450
+ if (f.value != null)
2451
+ p.set(f.field, String(f.value));
2452
+ if (f.from != null)
2453
+ p.set(f.field + '_from', String(f.from));
2454
+ if (f.to != null)
2455
+ p.set(f.field + '_to', String(f.to));
2456
+ }
2457
+ return p.toString();
2458
+ }
2459
+ get columns() {
2460
+ var _a, _b, _c;
2461
+ return ((_c = (_b = (_a = this.allViews[this.activeViewIdx]) === null || _a === void 0 ? void 0 : _a.view) === null || _b === void 0 ? void 0 : _b.values) !== null && _c !== void 0 ? _c : []);
2462
+ }
2463
+ get tableActions() {
2464
+ var _a, _b;
2465
+ const raw = (_b = (_a = this.item) === null || _a === void 0 ? void 0 : _a.actions) !== null && _b !== void 0 ? _b : [];
2466
+ return (raw !== null && raw !== void 0 ? raw : []).reduce((acc, a) => {
2467
+ if (a === 'NEW')
2468
+ acc.push({ action: 'create', label: t('table_new_record', this.locale), icon: 'assets/sprites.svg#icon-plus', variant: 'primary' });
2469
+ if (a === 'EXPORT')
2470
+ acc.push({ action: 'export', label: t('table_export_excel', this.locale), icon: 'assets/sprites.svg#icon-file-excel' });
2471
+ return acc;
2472
+ }, []);
2473
+ }
2374
2474
  colName(col) {
2375
2475
  var _a;
2376
2476
  return (_a = col.name) !== null && _a !== void 0 ? _a : '';
@@ -2381,6 +2481,25 @@ const MrdTable = class {
2381
2481
  return 'RELATION';
2382
2482
  return (_a = col.dataType) !== null && _a !== void 0 ? _a : 'TEXT';
2383
2483
  }
2484
+ /** True when we have a reliable total: either from the aggregations response or because
2485
+ * a short page told us it was the last page (exact count from row length). */
2486
+ isTotalKnown() {
2487
+ if (this.aggregationsTotal != null)
2488
+ return true;
2489
+ for (const rows of this.loadedPages.values()) {
2490
+ if (rows.length < this.pageSize)
2491
+ return true;
2492
+ }
2493
+ return false;
2494
+ }
2495
+ /** Effective total: aggregations-response > totalElements prop > minKnownTotal from setPage(). */
2496
+ get baseTotal() {
2497
+ if (this.aggregationsTotal != null)
2498
+ return this.aggregationsTotal;
2499
+ if (this.totalElements > 0)
2500
+ return this.totalElements;
2501
+ return this.minKnownTotal;
2502
+ }
2384
2503
  // ── Aggregation helpers ────────────────────────────────────────────────────
2385
2504
  buildAggregationParams() {
2386
2505
  var _a;
@@ -2401,17 +2520,32 @@ const MrdTable = class {
2401
2520
  params.count = groups.count;
2402
2521
  return Object.keys(params).length > 0 ? params : null;
2403
2522
  }
2523
+ buildAggregationQs() {
2524
+ var _a, _b, _c;
2525
+ const p = new URLSearchParams(this.buildQueryParams(0));
2526
+ p.delete('page');
2527
+ p.delete('sort');
2528
+ const groups = this.buildAggregationParams();
2529
+ if ((_a = groups === null || groups === void 0 ? void 0 : groups.sum) === null || _a === void 0 ? void 0 : _a.length)
2530
+ p.set('sum', groups.sum.join(','));
2531
+ if ((_b = groups === null || groups === void 0 ? void 0 : groups.avg) === null || _b === void 0 ? void 0 : _b.length)
2532
+ p.set('avg', groups.avg.join(','));
2533
+ if ((_c = groups === null || groups === void 0 ? void 0 : groups.count) === null || _c === void 0 ? void 0 : _c.length)
2534
+ p.set('count', groups.count.join(','));
2535
+ return p.toString();
2536
+ }
2404
2537
  emitLoadAggregations() {
2405
- const params = this.buildAggregationParams();
2406
- if (params)
2407
- this.mrdLoadAggregations.emit(params);
2538
+ this.mrdLoadAggregations.emit({ path: this.buildDataPath(), qs: this.buildQueryParams(0), aggQs: this.buildAggregationQs() });
2408
2539
  }
2409
2540
  renderAggregationValue(col) {
2410
- var _a, _b;
2541
+ var _a;
2411
2542
  if (col.type !== 'FIELD' || !col.aggregate || !this.aggregations)
2412
2543
  return '';
2413
2544
  const fn = col.aggregate.toLowerCase();
2414
- const val = (_a = this.aggregations[fn]) === null || _a === void 0 ? void 0 : _a[(_b = col.name) !== null && _b !== void 0 ? _b : ''];
2545
+ const aggData = this.aggregations[fn];
2546
+ const val = typeof aggData === 'object' && aggData !== null
2547
+ ? aggData[(_a = col.name) !== null && _a !== void 0 ? _a : '']
2548
+ : undefined;
2415
2549
  if (val == null)
2416
2550
  return '';
2417
2551
  const dt = col.dataType;
@@ -2436,9 +2570,10 @@ const MrdTable = class {
2436
2570
  this.colWidths = [];
2437
2571
  this.scrollTop = 0;
2438
2572
  this.renderStart = 0;
2439
- // No BUFFER here — totalElements may be stale after a filter change.
2440
- // Only request what is visible; BUFFER kicks in during scroll as usual.
2441
- this.renderEnd = Math.max(0, Math.min(this.visibleCount() - 1, this.totalElements - 1));
2573
+ this.minKnownTotal = 0;
2574
+ // Mirror init(): use visibleCount so the first page is always requested.
2575
+ // setPage() will clamp renderEnd down when the last page is shorter.
2576
+ this.renderEnd = this.visibleCount() - 1;
2442
2577
  const scroller = this.el.querySelector('.mrd-table__scroll');
2443
2578
  if (scroller)
2444
2579
  scroller.scrollTop = 0;
@@ -2469,7 +2604,7 @@ const MrdTable = class {
2469
2604
  for (let p = firstPage; p <= lastPage; p++) {
2470
2605
  if (!this.loadedPages.has(p) && !next.has(p)) {
2471
2606
  next.add(p);
2472
- this.mrdLoadPage.emit({ page: p, sort: this.sortParam() });
2607
+ this.mrdLoadPage.emit({ page: p, sort: this.sortParam(), path: this.buildDataPath(), qs: this.buildQueryParams(p) });
2473
2608
  changed = true;
2474
2609
  }
2475
2610
  }
@@ -2511,7 +2646,7 @@ const MrdTable = class {
2511
2646
  if (pageEnd < this.renderStart || pageStart > this.renderEnd)
2512
2647
  continue;
2513
2648
  next.add(page);
2514
- this.mrdLoadPage.emit({ page, sort: this.sortParam() });
2649
+ this.mrdLoadPage.emit({ page, sort: this.sortParam(), path: this.buildDataPath(), qs: this.buildQueryParams(page) });
2515
2650
  changed = true;
2516
2651
  }
2517
2652
  this.pendingPages.clear();
@@ -2703,9 +2838,9 @@ const MrdTable = class {
2703
2838
  }
2704
2839
  this.activeFilters = next;
2705
2840
  this.closeFilterPopup();
2706
- this.mrdFilter.emit({ filters: Array.from(this.activeFilters.values()) });
2707
2841
  this.aggregations = null;
2708
- this.emitLoadAggregations();
2842
+ this.aggregationsTotal = null;
2843
+ this.aggregationsPending = true;
2709
2844
  if (this.totalElements > 0) {
2710
2845
  this.resetPages();
2711
2846
  this.emitPagesForWindow(this.renderStart, this.renderEnd);
@@ -2718,9 +2853,9 @@ const MrdTable = class {
2718
2853
  next.delete(name);
2719
2854
  this.activeFilters = next;
2720
2855
  this.closeFilterPopup();
2721
- this.mrdFilter.emit({ filters: Array.from(this.activeFilters.values()) });
2722
2856
  this.aggregations = null;
2723
- this.emitLoadAggregations();
2857
+ this.aggregationsTotal = null;
2858
+ this.aggregationsPending = true;
2724
2859
  if (this.totalElements > 0) {
2725
2860
  this.resetPages();
2726
2861
  this.emitPagesForWindow(this.renderStart, this.renderEnd);
@@ -2728,37 +2863,39 @@ const MrdTable = class {
2728
2863
  }
2729
2864
  clearAllFilters() {
2730
2865
  this.activeFilters = new Map();
2731
- this.mrdFilter.emit({ filters: [] });
2732
2866
  this.aggregations = null;
2733
- this.emitLoadAggregations();
2867
+ this.aggregationsTotal = null;
2868
+ this.aggregationsPending = true;
2734
2869
  if (this.totalElements > 0) {
2735
2870
  this.resetPages();
2736
2871
  this.emitPagesForWindow(this.renderStart, this.renderEnd);
2737
2872
  }
2738
2873
  }
2739
2874
  // ── View switcher ──────────────────────────────────────────────────────────
2740
- handleViewSwitch(view) {
2741
- this.mrdSwitchView.emit({ name: view.name, class: view.class });
2875
+ handleViewSwitch(targetIdx) {
2876
+ var _a, _b;
2877
+ const target = this.allViews[targetIdx];
2878
+ if (!(target === null || target === void 0 ? void 0 : target.view))
2879
+ return;
2880
+ this.activeViewIdx = targetIdx;
2881
+ this.applyDefaultSort((_b = (_a = target.view) === null || _a === void 0 ? void 0 : _a.defaultSort) !== null && _b !== void 0 ? _b : '');
2882
+ this.activeFilters = new Map();
2883
+ this.init();
2742
2884
  }
2743
2885
  // ── Render: toolbar ────────────────────────────────────────────────────────
2744
2886
  renderToolbar() {
2745
- var _a, _b;
2746
2887
  const filterCount = this.activeFilters.size;
2747
- const hasActions = ((_a = this.actions) === null || _a === void 0 ? void 0 : _a.length) > 0;
2748
- const hasViewSwitcher = !!this.viewLabel && ((_b = this.alternativeViews) === null || _b === void 0 ? void 0 : _b.length) > 0;
2888
+ const actions = this.tableActions;
2889
+ const allViews = this.allViews;
2890
+ const hasActions = actions.length > 0;
2891
+ const hasViewSwitcher = allViews.length > 1;
2749
2892
  return (h("div", { class: "mrd-table__toolbar" }, h("div", { class: "mrd-table__toolbar-left" }, h("button", { class: `mrd-table__action mrd-table__action--secondary mrd-table__filter-toggle${this.filterMode ? ' mrd-table__filter-toggle--active' : ''}`, onClick: () => this.handleFilterToggle() }, h("svg", { class: "mrd-table__action-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, h("path", { fill: "currentColor", d: "M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" })), filterCount > 0 && h("span", { class: "mrd-table__filter-badge" }, filterCount), h("span", { class: "mrd-table__action-tooltip" }, this.filterMode ? t('table_filter_hide', this.locale) : t('table_filter', this.locale), filterCount > 0 ? ` (${filterCount} ${t('table_filter_active', this.locale)})` : '')), filterCount > 0 && (h("button", { class: "mrd-table__action mrd-table__action--secondary", onClick: () => this.clearAllFilters() }, h("svg", { class: "mrd-table__action-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, h("path", { fill: "currentColor", d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" })), h("span", { class: "mrd-table__action-tooltip" }, t('table_filter_clear_all', this.locale))))), hasViewSwitcher && (h("div", { class: "mrd-table__toolbar-center" }, h("select", { class: "mrd-table__view-select", onChange: (e) => {
2750
- const sel = e.target;
2751
- const view = this.alternativeViews.find(v => v.name === sel.value);
2752
- if (view) {
2753
- sel.selectedIndex = 0;
2754
- this.handleViewSwitch(view);
2755
- }
2756
- } }, h("option", { value: "" }, this.viewLabel), this.alternativeViews.map(v => {
2757
- var _a;
2758
- return (h("option", { value: v.name }, (_a = v.label) !== null && _a !== void 0 ? _a : v.name));
2759
- })))), hasActions && (h("div", { class: "mrd-table__toolbar-right" }, this.actions.map(a => {
2893
+ const idx = parseInt(e.target.value, 10);
2894
+ if (!isNaN(idx) && idx !== this.activeViewIdx)
2895
+ this.handleViewSwitch(idx);
2896
+ } }, allViews.map((v, i) => (h("option", { value: String(i), selected: i === this.activeViewIdx }, v.label)))))), hasActions && (h("div", { class: "mrd-table__toolbar-right" }, actions.map(a => {
2760
2897
  var _a;
2761
- return (h("button", { class: `mrd-table__action mrd-table__action--${(_a = a.variant) !== null && _a !== void 0 ? _a : 'secondary'}`, disabled: a.disabled, onClick: () => this.mrdAction.emit({ action: a.action }) }, a.icon
2898
+ return (h("button", { class: `mrd-table__action mrd-table__action--${(_a = a.variant) !== null && _a !== void 0 ? _a : 'secondary'}`, disabled: a.disabled, onClick: () => this.mrdAction.emit(this.buildActionDetail(a.action)) }, a.icon
2762
2899
  ? h("svg", { class: "mrd-table__action-icon", "aria-hidden": "true" }, h("use", { href: a.icon }))
2763
2900
  : a.label, h("span", { class: "mrd-table__action-tooltip" }, a.label)));
2764
2901
  })))));
@@ -2852,10 +2989,9 @@ const MrdTable = class {
2852
2989
  return (h("div", { class: "mrd-table__filter-popup", style: { top: `${this.popupPos.top}px`, left: `${this.popupPos.left}px` }, onClick: (e) => e.stopPropagation() }, h("div", { class: "mrd-table__filter-popup-header" }, h("span", { class: "mrd-table__filter-popup-title" }, label), h("button", { class: "mrd-table__filter-close", onClick: () => this.closeFilterPopup() }, "\u2715")), h("div", { class: "mrd-table__filter-section" }, h("div", { class: "mrd-table__filter-section-label" }, t('filter_sorting', this.locale)), h("div", { class: "mrd-table__filter-sort-buttons" }, h("button", { class: `mrd-table__filter-sort-btn${sortActive && this.sortDir === 'asc' ? ' mrd-table__filter-sort-btn--active' : ''}`, onClick: () => this.applySort(col, 'asc') }, "\u25B2 ", t('filter_ascending', this.locale)), h("button", { class: `mrd-table__filter-sort-btn${sortActive && this.sortDir === 'desc' ? ' mrd-table__filter-sort-btn--active' : ''}`, onClick: () => this.applySort(col, 'desc') }, "\u25BC ", t('filter_descending', this.locale)))), h("div", { class: "mrd-table__filter-divider" }), h("div", { class: "mrd-table__filter-section" }, h("div", { class: "mrd-table__filter-section-label" }, t('filter_section', this.locale)), this.renderFilterEditor(col)), h("div", { class: "mrd-table__filter-popup-footer" }, h("button", { class: "mrd-table__filter-btn mrd-table__filter-btn--clear", onClick: () => this.clearFilter() }, t('filter_clear', this.locale)), h("button", { class: "mrd-table__filter-btn mrd-table__filter-btn--apply", onClick: () => this.applyFilter() }, t('filter_apply', this.locale)))));
2853
2990
  }
2854
2991
  // ── Render: footer ────────────────────────────────────────────────────────
2855
- renderFooter(rowCount, effectiveTotal) {
2856
- const total = this.totalElements;
2857
- // Non-paginated mode: show plain row count
2858
- if (total === 0) {
2992
+ renderFooter(rowCount, effectiveTotal, isTotalKnown = true) {
2993
+ // Non-paginated mode: totalElements=0 and no paginated data loaded yet
2994
+ if (this.totalElements === 0 && this.loadedPages.size === 0) {
2859
2995
  const count = rowCount !== null && rowCount !== void 0 ? rowCount : 0;
2860
2996
  if (count === 0)
2861
2997
  return null;
@@ -2864,13 +3000,14 @@ const MrdTable = class {
2864
3000
  // Paginated mode: only show once page 0 has loaded (avoids stale total during filter reset)
2865
3001
  if (!this.loadedPages.has(0))
2866
3002
  return null;
2867
- // Use effectiveTotal (derived from actual page lengths) so the counter
2868
- // is correct even when the host has not yet updated totalElements.
2869
- const displayTotal = effectiveTotal !== null && effectiveTotal !== void 0 ? effectiveTotal : total;
3003
+ // effectiveTotal from render(); fall back to baseTotal when not provided.
3004
+ const displayTotal = effectiveTotal !== null && effectiveTotal !== void 0 ? effectiveTotal : this.baseTotal;
2870
3005
  // Compute from/to independently so partial rows at top/bottom are included.
2871
3006
  const from = Math.min(Math.floor(this.scrollTop / this.rowHeight) + 1, displayTotal);
2872
3007
  const to = Math.min(Math.ceil((this.scrollTop + this.tableHeight) / this.rowHeight), displayTotal);
2873
- return (h("div", { class: "mrd-table__footer" }, from, "\u2013", to, " ", t('table_of', this.locale), " ", displayTotal));
3008
+ // Show '…' for the total until we have a reliable count (aggregations or last page loaded).
3009
+ const totalLabel = isTotalKnown ? String(displayTotal) : '…';
3010
+ return (h("div", { class: "mrd-table__footer" }, from, "\u2013", to, " ", t('table_of', this.locale), " ", totalLabel));
2874
3011
  }
2875
3012
  // ── Render: cell ──────────────────────────────────────────────────────────
2876
3013
  renderCell(col, row) {
@@ -2929,7 +3066,9 @@ const MrdTable = class {
2929
3066
  if (!((_a = this.columns) === null || _a === void 0 ? void 0 : _a.length))
2930
3067
  return null;
2931
3068
  // ── Non-paginated mode ──────────────────────────────────────────────────
2932
- if (this.totalElements === 0) {
3069
+ // Only enter non-paginated mode when totalElements is 0 AND no paginated data
3070
+ // has been loaded yet — prevents the wrong branch when the host omits totalElements.
3071
+ if (this.totalElements === 0 && this.loadedPages.size === 0) {
2933
3072
  return (h(Host, null, this.renderToolbar(), h("div", { class: "mrd-table" }, h("table", { class: "mrd-table__table" }, h("thead", null, h("tr", null, this.columns.map(col => {
2934
3073
  var _a;
2935
3074
  const name = this.colName(col);
@@ -2946,8 +3085,8 @@ const MrdTable = class {
2946
3085
  // Derive the authoritative row count from loaded pages:
2947
3086
  // if any loaded page is shorter than pageSize it is the last page,
2948
3087
  // so the true total cannot exceed (pageNum * pageSize + pageRows.length).
2949
- // This self-corrects without requiring the host to update totalElements.
2950
- let effectiveTotal = this.totalElements;
3088
+ // aggregationsTotal (from setAggregations) takes priority over the totalElements prop.
3089
+ let effectiveTotal = this.baseTotal;
2951
3090
  for (const [pageNum, pageRows] of this.loadedPages) {
2952
3091
  if (pageRows.length < this.pageSize) {
2953
3092
  effectiveTotal = Math.min(effectiveTotal, pageNum * this.pageSize + pageRows.length);
@@ -2984,7 +3123,7 @@ const MrdTable = class {
2984
3123
  isFiltered ? 'mrd-table__header--filtered' : '',
2985
3124
  ].filter(Boolean).join(' ');
2986
3125
  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 && this.renderFilterIcon()));
2987
- }))), 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.renderFilterPopup(), this.renderTextblockModal()));
3126
+ }))), 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()));
2988
3127
  }
2989
3128
  renderFilterIcon() {
2990
3129
  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" }))));
@@ -3004,8 +3143,8 @@ const MrdTable = class {
3004
3143
  "totalElements": [{
3005
3144
  "totalElementsChanged": 0
3006
3145
  }],
3007
- "defaultSort": [{
3008
- "defaultSortChanged": 0
3146
+ "item": [{
3147
+ "itemChanged": 0
3009
3148
  }]
3010
3149
  }; }
3011
3150
  };