@mmlogic/components 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/cjs/loader.cjs.js +1 -1
  2. package/dist/cjs/mosterdcomponents.cjs.js +1 -1
  3. package/dist/cjs/mrd-boolean-field_19.cjs.entry.js +3142 -0
  4. package/dist/collection/collection-manifest.json +1 -0
  5. package/dist/collection/components/mrd-layout-section/mrd-layout-section.js +733 -0
  6. package/dist/collection/components/mrd-layout-section/mrd-layout-section.scss +354 -0
  7. package/dist/collection/components/mrd-longtext-field/mrd-longtext-field.js +1 -1
  8. package/dist/collection/components/mrd-number-field/mrd-number-field.js +1 -1
  9. package/dist/collection/components/mrd-table/mrd-table.js +53 -42
  10. package/dist/collection/components/mrd-table/mrd-table.scss +29 -71
  11. package/dist/collection/components/mrd-text-field/mrd-text-field.js +1 -1
  12. package/dist/collection/components/mrd-textarea-field/mrd-textarea-field.js +1 -1
  13. package/dist/collection/components/mrd-time-field/mrd-time-field.js +1 -1
  14. package/dist/collection/dev/app.js +109 -3
  15. package/dist/collection/dev/example-data.js +324 -0
  16. package/dist/collection/utils/cell-renderer.js +26 -0
  17. package/dist/components/mrd-layout-section.d.ts +11 -0
  18. package/dist/components/mrd-layout-section.js +1 -0
  19. package/dist/components/mrd-longtext-field2.js +1 -1
  20. package/dist/components/mrd-number-field2.js +1 -1
  21. package/dist/components/mrd-table.js +1 -1
  22. package/dist/components/mrd-table2.js +1 -0
  23. package/dist/components/mrd-text-field2.js +1 -1
  24. package/dist/components/mrd-textarea-field2.js +1 -1
  25. package/dist/components/mrd-time-field2.js +1 -1
  26. package/dist/esm/loader.js +1 -1
  27. package/dist/esm/mosterdcomponents.js +1 -1
  28. package/dist/esm/mrd-boolean-field_19.entry.js +3122 -0
  29. package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
  30. package/dist/mosterdcomponents/p-e1a5587b.entry.js +1 -0
  31. package/dist/types/components/mrd-layout-section/mrd-layout-section.d.ts +93 -0
  32. package/dist/types/components/mrd-table/mrd-table.d.ts +5 -6
  33. package/dist/types/components.d.ts +128 -8
  34. package/dist/types/types/client-layout.d.ts +19 -0
  35. package/dist/types/utils/cell-renderer.d.ts +9 -1
  36. package/package.json +1 -1
  37. package/dist/cjs/format-DExY8_nu.js +0 -328
  38. package/dist/cjs/mrd-boolean-field_17.cjs.entry.js +0 -1554
  39. package/dist/cjs/mrd-table.cjs.entry.js +0 -888
  40. package/dist/esm/format-CcRjWvcb.js +0 -319
  41. package/dist/esm/mrd-boolean-field_17.entry.js +0 -1536
  42. package/dist/esm/mrd-table.entry.js +0 -886
  43. package/dist/mosterdcomponents/p-17fe94c6.entry.js +0 -1
  44. package/dist/mosterdcomponents/p-3d856b27.entry.js +0 -1
  45. package/dist/mosterdcomponents/p-CcRjWvcb.js +0 -1
@@ -1,888 +0,0 @@
1
- 'use strict';
2
-
3
- var index$1 = require('./index-BPj2cBXs.js');
4
- var format = require('./format-DExY8_nu.js');
5
- var index = require('./index.cjs.js');
6
-
7
- class CellRenderer {
8
- static render(column, row, locale) {
9
- var _a, _b, _c, _d;
10
- if (column.type === index.ClientLayoutItemType.RELATION) {
11
- const name = (_b = (_a = column.relation) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '';
12
- const link = (_c = row === null || row === void 0 ? void 0 : row._links) === null || _c === void 0 ? void 0 : _c[name];
13
- if (!link)
14
- return '';
15
- if (Array.isArray(link))
16
- return link.map((l) => { var _a; return (_a = l.name) !== null && _a !== void 0 ? _a : ''; }).filter(Boolean).join(', ');
17
- return (_d = link.name) !== null && _d !== void 0 ? _d : '';
18
- }
19
- if (column.type !== index.ClientLayoutItemType.FIELD || !column.field)
20
- return '';
21
- const { name, dataType, listItems } = column.field;
22
- const raw = row === null || row === void 0 ? void 0 : row[name];
23
- if (raw == null || raw === '')
24
- return '';
25
- const values = Array.isArray(raw) ? raw : [raw];
26
- return values
27
- .map(v => CellRenderer.renderValue(dataType !== null && dataType !== void 0 ? dataType : 'TEXT', v, listItems !== null && listItems !== void 0 ? listItems : [], locale))
28
- .filter(s => s !== '')
29
- .join(', ');
30
- }
31
- static renderValue(dataType, value, listItems, locale) {
32
- var _a, _b;
33
- switch (dataType) {
34
- case 'INTEGER':
35
- return format.formatNumber(Number(value), locale, { maximumFractionDigits: 0 });
36
- case 'DECIMAL':
37
- return format.formatNumber(Number(value), locale);
38
- case 'PERCENTAGE':
39
- return format.formatPercentage(Number(value), locale);
40
- case 'CURRENCY': {
41
- const { amount, currency } = typeof value === 'object' && value !== null
42
- ? value
43
- : { amount: value, currency: '' };
44
- return currency
45
- ? format.formatCurrency(Number(amount), currency, locale)
46
- : format.formatNumber(Number(amount), locale);
47
- }
48
- case 'DATE':
49
- return format.formatDate(value, locale);
50
- case 'DATETIME':
51
- return format.formatDateTime(value, locale);
52
- case 'TIME':
53
- return format.formatTime(value, locale);
54
- case 'BOOLEAN':
55
- return value ? '✓' : '';
56
- case 'FILE':
57
- case 'IMAGE':
58
- return typeof value === 'object' && value !== null ? ((_a = value.fileName) !== null && _a !== void 0 ? _a : '') : '';
59
- case 'LIST': {
60
- const item = listItems.find(li => li.key === String(value));
61
- return (_b = item === null || item === void 0 ? void 0 : item.label) !== null && _b !== void 0 ? _b : String(value);
62
- }
63
- case 'TEXTBLOCK': {
64
- const stripped = String(value).replace(/<[^>]*>/g, '');
65
- const txt = document.createElement('textarea');
66
- txt.innerHTML = stripped;
67
- return txt.value.trim();
68
- }
69
- case 'LONGTEXT':
70
- return String(value).replace(/[\r\n]+/g, ' ').trim();
71
- case 'JSON': {
72
- const str = typeof value === 'object' ? JSON.stringify(value) : String(value);
73
- return str.replace(/[\r\n]+/g, ' ').trim();
74
- }
75
- default:
76
- return String(value);
77
- }
78
- }
79
- }
80
-
81
- const mrdTableScss = () => `.sc-mrd-table-h{display:block;width:100%}.mrd-table__scroll.sc-mrd-table{overflow-y:auto;overflow-x:auto;border:1px solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);overflow-anchor:none}.mrd-table.sc-mrd-table{overflow-x:auto}.mrd-table__table.sc-mrd-table{width:auto;min-width:100%;border-collapse:collapse;font-size:var(--mrd-font-size-sm);color:var(--mrd-color-neutral-900)}.mrd-table__scroll.sc-mrd-table .mrd-table__table.sc-mrd-table{min-width:max-content}.mrd-table__header.sc-mrd-table{position:sticky;top:0;z-index:1;background:var(--mrd-color-white);text-align:left;padding:var(--mrd-space-2) var(--mrd-space-4);border-bottom:2px solid var(--mrd-border-color);color:var(--mrd-color-neutral-600);font-weight:var(--mrd-font-weight-medium);white-space:nowrap;font-size:var(--mrd-font-size-xs);text-transform:uppercase;letter-spacing:0.04em}.mrd-table__header--sortable.sc-mrd-table{cursor:pointer;user-select:none}.mrd-table__header--sortable.sc-mrd-table:hover{background:var(--mrd-color-neutral-50);color:var(--mrd-color-neutral-800)}.mrd-table__header--sorted-asc.sc-mrd-table,.mrd-table__header--sorted-desc.sc-mrd-table{color:var(--mrd-color-primary);border-bottom-color:var(--mrd-color-primary)}.mrd-table__header-label.sc-mrd-table{margin-right:var(--mrd-space-1)}.mrd-table__sort-icon.sc-mrd-table{font-size:0.85rem;opacity:0.4;vertical-align:middle}.mrd-table__header--sorted-asc.sc-mrd-table .mrd-table__sort-icon.sc-mrd-table,.mrd-table__header--sorted-desc.sc-mrd-table .mrd-table__sort-icon.sc-mrd-table{opacity:1;color:var(--mrd-color-primary)}.mrd-table__filter-icon.sc-mrd-table{display:inline-flex;align-items:center;vertical-align:middle;margin-left:var(--mrd-space-1);color:var(--mrd-color-primary)}.mrd-table__row.sc-mrd-table{border-bottom:1px solid var(--mrd-border-color)}.mrd-table__row.sc-mrd-table:hover{background:var(--mrd-color-neutral-200) !important}.mrd-table__row--clickable.sc-mrd-table{cursor:pointer}.mrd-table__spacer.sc-mrd-table{border:none}.mrd-table__spacer.sc-mrd-table td.sc-mrd-table{padding:0;border:none}.mrd-table__cell.sc-mrd-table{padding:var(--mrd-space-2) var(--mrd-space-4);vertical-align:top;white-space:nowrap}.mrd-table__cell--numeric.sc-mrd-table{text-align:right;font-variant-numeric:tabular-nums}.mrd-table__row--loading.sc-mrd-table{background:transparent}.mrd-table__cell--placeholder.sc-mrd-table{padding:var(--mrd-space-2) var(--mrd-space-4);border-bottom:1px solid var(--mrd-border-color)}.mrd-table__placeholder-bar.sc-mrd-table{display:block;height:0.75rem;width:55%;border-radius:var(--mrd-border-radius-sm);background:linear-gradient( 90deg, var(--mrd-color-neutral-200) 25%, var(--mrd-color-neutral-100) 50%, var(--mrd-color-neutral-200) 75% );background-size:200% 100%;animation:mrd-shimmer 1.4s ease infinite}@keyframes mrd-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}.mrd-table__toolbar.sc-mrd-table{display:flex;align-items:center;justify-content:space-between;padding-bottom:var(--mrd-space-2)}.mrd-table__toolbar-left.sc-mrd-table,.mrd-table__toolbar-right.sc-mrd-table{display:flex;gap:var(--mrd-space-2);align-items:center}.mrd-table__toolbar-center.sc-mrd-table{flex:1;display:flex;justify-content:center;align-items:center;gap:var(--mrd-space-1)}.mrd-table__view-title.sc-mrd-table{font-size:var(--mrd-font-size-sm);font-weight:600;color:var(--mrd-color-neutral-800)}.mrd-table__view-switcher.sc-mrd-table{position:relative}.mrd-table__view-switcher-btn.sc-mrd-table{display:inline-flex;align-items:center;gap:var(--mrd-space-1);background:none;border:none;cursor:pointer;padding:var(--mrd-space-1) var(--mrd-space-2);color:var(--mrd-color-neutral-400);font-size:var(--mrd-font-size-sm);border-radius:var(--mrd-border-radius);transition:color 0.15s, background-color 0.15s;line-height:1.4}.mrd-table__view-switcher-btn.sc-mrd-table:hover{color:var(--mrd-color-neutral-700);background-color:var(--mrd-color-neutral-50)}.mrd-table__view-switcher-label.sc-mrd-table{font-weight:var(--mrd-font-weight-medium)}.mrd-table__view-switcher-chevron.sc-mrd-table{width:1.1rem;height:1.1rem;flex-shrink:0;transition:transform 0.15s}.mrd-table__view-switcher-btn--open.sc-mrd-table .mrd-table__view-switcher-chevron.sc-mrd-table{transform:rotate(180deg)}.mrd-table__view-switcher-dropdown.sc-mrd-table{position:absolute;top:calc(100% + 4px);left:50%;transform:translateX(-50%);min-width:160px;background:var(--mrd-color-white);border:1px solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);box-shadow:var(--mrd-shadow-md, 0 4px 12px rgba(0,0,0,.12));z-index:var(--mrd-z-dropdown, 200);overflow:hidden}.mrd-table__view-switcher-item.sc-mrd-table{display:block;width:100%;padding:var(--mrd-space-2) var(--mrd-space-3);background:none;border:none;text-align:left;cursor:pointer;font-size:var(--mrd-font-size-sm);color:var(--mrd-color-neutral-700);white-space:nowrap;transition:background-color 0.1s, color 0.1s}.mrd-table__view-switcher-item.sc-mrd-table:hover{background-color:var(--mrd-color-neutral-50);color:var(--mrd-color-neutral-900)}.mrd-table__action.sc-mrd-table{position:relative;display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;padding:0;background:transparent;border:1px solid transparent;border-radius:var(--mrd-border-radius);cursor:pointer;color:var(--mrd-color-neutral-400);transition:background-color 0.15s, border-color 0.15s, color 0.15s}.mrd-table__action.sc-mrd-table:hover{background-color:var(--mrd-color-neutral-100);border-color:var(--mrd-color-neutral-300);color:var(--mrd-color-neutral-700)}.mrd-table__action.sc-mrd-table:disabled{opacity:0.4;cursor:not-allowed}.mrd-table__action--primary.sc-mrd-table{color:var(--mrd-color-neutral-500)}.mrd-table__action--primary.sc-mrd-table:hover{background:var(--mrd-color-primary);border-color:var(--mrd-color-primary);color:var(--mrd-color-white)}.mrd-table__action--danger.sc-mrd-table{color:var(--mrd-color-error)}.mrd-table__action--danger.sc-mrd-table:hover{background-color:var(--mrd-color-error-light, #fef2f2);border-color:var(--mrd-color-error)}.mrd-table__action-icon.sc-mrd-table{width:1.25rem;height:1.25rem;pointer-events:none;fill:currentColor}.mrd-table__action-tooltip.sc-mrd-table{display:none;position:absolute;bottom:calc(100% + 6px);right:0;padding:var(--mrd-space-1) var(--mrd-space-2);font-size:var(--mrd-font-size-xs);white-space:nowrap;background:var(--mrd-color-tooltip, #fffce1);color:var(--mrd-color-neutral-900);border:1px solid var(--mrd-border-color);border-radius:var(--mrd-border-radius-sm, var(--mrd-border-radius));pointer-events:none;z-index:10}.mrd-table__action.sc-mrd-table:hover .mrd-table__action-tooltip.sc-mrd-table{display:block}.mrd-table__filter-toggle--active.sc-mrd-table{background:var(--mrd-color-primary);border-color:var(--mrd-color-primary);color:var(--mrd-color-white)}.mrd-table__filter-toggle--active.sc-mrd-table:hover{background:var(--mrd-color-primary-dark, var(--mrd-color-primary));border-color:var(--mrd-color-primary-dark, var(--mrd-color-primary));color:var(--mrd-color-white)}.mrd-table__filter-badge.sc-mrd-table{position:absolute;top:-6px;right:-6px;min-width:1.25rem;height:1.25rem;padding:0 3px;background:var(--mrd-color-error, #e53e3e);color:var(--mrd-color-white);border-radius:9999px;font-size:0.65rem;font-weight:var(--mrd-font-weight-medium);line-height:1.25rem;text-align:center;pointer-events:none}.mrd-table__header--filtered.sc-mrd-table{color:var(--mrd-color-primary);border-bottom-color:var(--mrd-color-primary)}.mrd-table__filter-popup.sc-mrd-table{position:fixed;width:280px;background:var(--mrd-color-white);border:1px solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);box-shadow:var(--mrd-shadow-md, 0 4px 12px rgba(0,0,0,.12));z-index:var(--mrd-z-dropdown, 200);font-size:var(--mrd-font-size-sm)}.mrd-table__filter-popup-header.sc-mrd-table{display:flex;align-items:center;justify-content:space-between;padding:var(--mrd-space-2) var(--mrd-space-3);border-bottom:1px solid var(--mrd-border-color)}.mrd-table__filter-popup-title.sc-mrd-table{font-weight:var(--mrd-font-weight-medium);color:var(--mrd-color-neutral-800);font-size:var(--mrd-font-size-sm)}.mrd-table__filter-close.sc-mrd-table{background:transparent;border:none;cursor:pointer;color:var(--mrd-color-neutral-500);font-size:0.9rem;padding:2px 4px;border-radius:3px;line-height:1}.mrd-table__filter-close.sc-mrd-table:hover{background:var(--mrd-color-neutral-100);color:var(--mrd-color-neutral-800)}.mrd-table__filter-section.sc-mrd-table{padding:var(--mrd-space-2) var(--mrd-space-3)}.mrd-table__filter-section-label.sc-mrd-table{font-size:var(--mrd-font-size-xs);font-weight:var(--mrd-font-weight-medium);text-transform:uppercase;letter-spacing:0.04em;color:var(--mrd-color-neutral-500);margin-bottom:var(--mrd-space-2)}.mrd-table__filter-sort-buttons.sc-mrd-table{display:flex;gap:var(--mrd-space-2)}.mrd-table__filter-sort-btn.sc-mrd-table{flex:1;padding:var(--mrd-space-1) var(--mrd-space-2);background:transparent;border:1px solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);cursor:pointer;font-size:var(--mrd-font-size-xs);color:var(--mrd-color-neutral-700)}.mrd-table__filter-sort-btn.sc-mrd-table:hover{background:var(--mrd-color-neutral-100)}.mrd-table__filter-sort-btn--active.sc-mrd-table{background:var(--mrd-color-primary);border-color:var(--mrd-color-primary);color:var(--mrd-color-white)}.mrd-table__filter-divider.sc-mrd-table{height:1px;background:var(--mrd-border-color);margin:0}.mrd-table__filter-editor.sc-mrd-table{display:flex;flex-direction:column;gap:var(--mrd-space-2)}.mrd-table__filter-select.sc-mrd-table,.mrd-table__filter-input.sc-mrd-table{width:100%;padding:var(--mrd-space-1) var(--mrd-space-2);border:1px solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);font-size:var(--mrd-font-size-sm);color:var(--mrd-color-neutral-900);background:var(--mrd-color-white);box-sizing:border-box}.mrd-table__filter-select.sc-mrd-table:focus,.mrd-table__filter-input.sc-mrd-table:focus{outline:none;border-color:var(--mrd-color-primary);box-shadow:0 0 0 2px rgba(0,0,0,.06)}.mrd-table__filter-range.sc-mrd-table{display:flex;align-items:center;gap:var(--mrd-space-1)}.mrd-table__filter-range.sc-mrd-table .mrd-table__filter-input.sc-mrd-table{flex:1;min-width:0}.mrd-table__filter-range-sep.sc-mrd-table{color:var(--mrd-color-neutral-400);flex-shrink:0}.mrd-table__filter-range--stacked.sc-mrd-table{flex-direction:column;align-items:stretch;gap:var(--mrd-space-2)}.mrd-table__filter-range-label.sc-mrd-table{font-size:var(--mrd-font-size-xs);color:var(--mrd-color-neutral-500);margin-bottom:2px}.mrd-table__filter-radio-group.sc-mrd-table{display:flex;flex-direction:column;gap:var(--mrd-space-1)}.mrd-table__filter-radio-group--inline.sc-mrd-table{flex-direction:row;gap:var(--mrd-space-3)}.mrd-table__filter-radio-label.sc-mrd-table{display:flex;align-items:center;gap:var(--mrd-space-1);cursor:pointer;font-size:var(--mrd-font-size-sm);color:var(--mrd-color-neutral-800)}.mrd-table__filter-list.sc-mrd-table{display:flex;flex-direction:column;gap:var(--mrd-space-1);max-height:180px;overflow-y:auto}.mrd-table__filter-list-controls.sc-mrd-table{display:flex;gap:var(--mrd-space-2);margin-bottom:var(--mrd-space-1)}.mrd-table__filter-list-btn.sc-mrd-table{font-size:var(--mrd-font-size-xs);color:var(--mrd-color-primary);background:transparent;border:none;cursor:pointer;padding:0;text-decoration:underline}.mrd-table__filter-checkbox-label.sc-mrd-table{display:flex;align-items:center;gap:var(--mrd-space-1);cursor:pointer;font-size:var(--mrd-font-size-sm);color:var(--mrd-color-neutral-800)}.mrd-table__filter-no-support.sc-mrd-table{font-size:var(--mrd-font-size-sm);color:var(--mrd-color-neutral-500);margin:0;font-style:italic}.mrd-table__filter-popup-footer.sc-mrd-table{display:flex;justify-content:flex-end;gap:var(--mrd-space-2);padding:var(--mrd-space-2) var(--mrd-space-3);border-top:1px solid var(--mrd-border-color)}.mrd-table__filter-btn.sc-mrd-table{padding:var(--mrd-space-1) var(--mrd-space-3);border-radius:var(--mrd-border-radius);border:1px solid var(--mrd-border-color);font-size:var(--mrd-font-size-sm);cursor:pointer}.mrd-table__filter-btn--clear.sc-mrd-table{background:transparent;color:var(--mrd-color-neutral-600)}.mrd-table__filter-btn--clear.sc-mrd-table:hover{background:var(--mrd-color-neutral-100)}.mrd-table__filter-btn--apply.sc-mrd-table{background:var(--mrd-color-primary);border-color:var(--mrd-color-primary);color:var(--mrd-color-white)}.mrd-table__filter-btn--apply.sc-mrd-table:hover{background:var(--mrd-color-primary-dark, var(--mrd-color-primary));border-color:var(--mrd-color-primary-dark, var(--mrd-color-primary))}.mrd-table__totals-row.sc-mrd-table{border-top:2px solid var(--mrd-border-color)}.mrd-table__totals-cell.sc-mrd-table{position:sticky;bottom:0;z-index:2;padding:var(--mrd-space-2) var(--mrd-space-4);background:var(--mrd-color-white);font-weight:var(--mrd-font-weight-medium);font-variant-numeric:tabular-nums;white-space:nowrap;border-top:2px solid var(--mrd-border-color)}.mrd-table__totals-cell--numeric.sc-mrd-table{text-align:right}.mrd-table__footer.sc-mrd-table{padding:var(--mrd-space-1) var(--mrd-space-2);font-size:var(--mrd-font-size-xs);color:var(--mrd-color-neutral-500);text-align:right}.mrd-table__empty.sc-mrd-table{padding:var(--mrd-space-4) var(--mrd-space-3);color:var(--mrd-color-neutral-500);font-size:var(--mrd-font-size-sm);text-align:center;margin:0}.mrd-table__file-btn.sc-mrd-table{display:inline-flex;align-items:center;gap:var(--mrd-space-1);background:none;border:none;padding:0;cursor:pointer;color:var(--mrd-color-primary);font-size:var(--mrd-font-size-sm);font-family:inherit;max-width:100%;overflow:hidden}.mrd-table__file-btn.sc-mrd-table:hover{text-decoration:underline;color:var(--mrd-color-primary-dark)}.mrd-table__file-icon.sc-mrd-table{flex-shrink:0;width:1rem;height:1rem}.mrd-table__textblock-btn.sc-mrd-table{display:inline;background:none;border:none;padding:0 0 0 var(--mrd-space-1);cursor:pointer;color:var(--mrd-color-primary);font-size:var(--mrd-font-size-sm);font-family:inherit;line-height:inherit;vertical-align:middle}.mrd-table__textblock-btn.sc-mrd-table:hover{color:var(--mrd-color-primary-dark)}.mrd-table__modal-backdrop.sc-mrd-table{position:fixed;inset:0;background:rgba(0, 0, 0, 0.4);z-index:var(--mrd-z-modal, 300);display:flex;align-items:center;justify-content:center}.mrd-table__modal.sc-mrd-table{background:#fff;border-radius:var(--mrd-radius-md, 0.5rem);padding:var(--mrd-space-6);max-width:min(600px, 90vw);max-height:70vh;overflow-y:auto;position:relative;box-shadow:var(--mrd-shadow-lg)}.mrd-table__modal-close.sc-mrd-table{position:absolute;top:var(--mrd-space-3);right:var(--mrd-space-3);background:none;border:none;cursor:pointer;font-size:1.25rem;line-height:1;color:var(--mrd-color-text-muted, #6b7280);padding:0}.mrd-table__modal-close.sc-mrd-table:hover{color:var(--mrd-color-text, #111827)}.mrd-table__modal-text.sc-mrd-table{margin:0;padding-right:var(--mrd-space-6);white-space:pre-wrap;word-break:break-word;font-size:var(--mrd-font-size-sm);line-height:1.6}`;
82
-
83
- const BUFFER = 10;
84
- /** Wacht deze tijd (ms) na het laatste scroll-event voordat pagina's worden
85
- * aangevraagd. Pagina's die de gebruiker snel voorbij scrollt worden zo geskipt. */
86
- const REQUEST_DEBOUNCE_MS = 150;
87
- /** Breedte van de filterpopup in px — voor overflow-correctie. */
88
- const POPUP_WIDTH = 280;
89
- const TEXT_TYPES = new Set(['TEXT', 'TEXTBLOCK', 'EMAIL', 'HYPERLINK']);
90
- const NUMERIC_TYPES = new Set(['INTEGER', 'DECIMAL', 'PERCENTAGE', 'CURRENCY']);
91
- const DATE_TYPES = new Set(['DATE', 'DATETIME', 'TIME']);
92
- const NO_FILTER_TYPES = new Set(['FILE', 'IMAGE']);
93
- const MrdTable = class {
94
- constructor(hostRef) {
95
- index$1.registerInstance(this, hostRef);
96
- this.mrdLoadPage = index$1.createEvent(this, "mrdLoadPage");
97
- this.mrdRowClick = index$1.createEvent(this, "mrdRowClick");
98
- this.mrdAction = index$1.createEvent(this, "mrdAction");
99
- this.mrdFilter = index$1.createEvent(this, "mrdFilter");
100
- this.mrdDownload = index$1.createEvent(this, "mrdDownload");
101
- this.mrdSwitchView = index$1.createEvent(this, "mrdSwitchView");
102
- this.mrdLoadAggregations = index$1.createEvent(this, "mrdLoadAggregations");
103
- // ── Non-state internals ────────────────────────────────────────────────────
104
- this.pendingPages = new Set();
105
- this.debounceTimer = null;
106
- this.outsideClickHandler = null;
107
- this.viewSwitcherClickHandler = null;
108
- this.keydownHandler = null;
109
- // ── Props ──────────────────────────────────────────────────────────────────
110
- this.columns = [];
111
- /** Direct rows (non-paginated mode, used when totalElements === 0). */
112
- this.rows = [];
113
- this.locale = navigator.language;
114
- /** Total number of records across all pages. 0 = non-paginated mode. */
115
- this.totalElements = 0;
116
- /** Records per page (must match the API page size). */
117
- this.pageSize = 20;
118
- /** Row height in px — used for spacer and scroll-position maths. */
119
- this.rowHeight = 36;
120
- /** Height of the scroll container in px. */
121
- this.tableHeight = 500;
122
- /** Initial sort applied on load, e.g. "timestamp,desc" or "name".
123
- * Parsed by init() into sortField + sortDir. */
124
- this.defaultSort = '';
125
- /** Toolbar action buttons rendered above the table. */
126
- this.actions = [];
127
- /** Display label of the current view — shown in the toolbar center as a view picker trigger. */
128
- this.viewLabel = '';
129
- /** Alternative views available for this table; renders a dropdown when non-empty. */
130
- this.alternativeViews = [];
131
- // ── Internal state ─────────────────────────────────────────────────────────
132
- this.loadedPages = new Map();
133
- this.requestedPages = new Set();
134
- this.renderStart = 0;
135
- this.renderEnd = 0;
136
- this.colWidths = [];
137
- this.sortField = '';
138
- this.sortDir = 'asc';
139
- /** Whether the filter UI is visible on column headers. */
140
- this.filterMode = false;
141
- /** Active filters keyed by field name. */
142
- this.activeFilters = new Map();
143
- /** Field name of the currently open filter popup (null = closed). */
144
- this.openFilterCol = null;
145
- /** Filter state being edited in the open popup. */
146
- this.pendingFilter = null;
147
- /** Viewport-relative position for the filter popup. */
148
- this.popupPos = { top: 0, left: 0 };
149
- /** Current scroll offset of the scroll container — drives pagination footer. */
150
- this.scrollTop = 0;
151
- /** Full text shown in the TEXTBLOCK expand modal (null = closed). */
152
- this.textblockModal = null;
153
- /** Aggregation totals received from the host via setAggregations(). Null = not yet loaded. */
154
- this.aggregations = null;
155
- /** Whether the view switcher dropdown is open. */
156
- this.viewSwitcherOpen = false;
157
- this.handleScroll = (e) => {
158
- const scroller = e.currentTarget;
159
- const scrollTop = scroller.scrollTop;
160
- const total = this.totalElements;
161
- const visStart = Math.floor(scrollTop / this.rowHeight);
162
- const visEnd = Math.min(visStart + this.visibleCount(), total - 1);
163
- this.scrollTop = scrollTop;
164
- this.renderStart = Math.max(0, visStart - BUFFER);
165
- this.renderEnd = Math.min(total - 1, visEnd + BUFFER);
166
- this.requestPagesForWindow(this.renderStart, this.renderEnd);
167
- };
168
- }
169
- // ── Prop watchers ─────────────────────────────────────────────────────────
170
- /** Clamp renderEnd when totalElements shrinks (e.g. after a filter is applied). */
171
- totalElementsChanged(newVal) {
172
- this.renderEnd = Math.min(this.renderEnd, Math.max(0, newVal - 1));
173
- }
174
- // ── Public API ─────────────────────────────────────────────────────────────
175
- /**
176
- * Initialise (or reset) the virtual scroll.
177
- * Call after setting all props and registering the mrdLoadPage listener,
178
- * but before calling setPage(0, rows).
179
- */
180
- async init() {
181
- var _a;
182
- if (this.debounceTimer !== null) {
183
- clearTimeout(this.debounceTimer);
184
- this.debounceTimer = null;
185
- }
186
- this.pendingPages.clear();
187
- this.loadedPages = new Map();
188
- this.requestedPages = new Set();
189
- this.colWidths = [];
190
- if (this.defaultSort) {
191
- const parts = this.defaultSort.split(',');
192
- this.sortField = parts[0].trim();
193
- this.sortDir = ((_a = parts[1]) === null || _a === void 0 ? void 0 : _a.trim()) === 'desc' ? 'desc' : 'asc';
194
- }
195
- else {
196
- this.sortField = '';
197
- this.sortDir = 'asc';
198
- }
199
- this.scrollTop = 0;
200
- this.renderStart = 0;
201
- // No BUFFER on init — only request what fits the visible area (page 0).
202
- // BUFFER is applied during scroll to pre-fetch the next page proactively.
203
- this.renderEnd = Math.max(0, Math.min(this.visibleCount() - 1, this.totalElements - 1));
204
- const scroller = this.el.querySelector('.mrd-table__scroll');
205
- if (scroller)
206
- scroller.scrollTop = 0;
207
- this.aggregations = null;
208
- this.emitLoadAggregations();
209
- }
210
- /**
211
- * Inject the rows for a given page (0-based).
212
- * Creates a new Map reference so Stencil detects the state change.
213
- *
214
- * When the page contains fewer rows than pageSize it is the last page.
215
- * renderEnd is clamped immediately so no loading-placeholder rows appear
216
- * beyond the actual data — without requiring the host to update totalElements.
217
- */
218
- async setPage(pageNumber, rows) {
219
- if (rows.length < this.pageSize) {
220
- // lastRowIdx is -1 when the page is empty; clamp renderEnd to -1 so the
221
- // render loop does not execute and no shimmer rows appear.
222
- const lastRowIdx = pageNumber * this.pageSize + rows.length - 1;
223
- this.renderEnd = Math.min(this.renderEnd, lastRowIdx);
224
- }
225
- const next = new Map(this.loadedPages);
226
- next.set(pageNumber, rows);
227
- this.loadedPages = next;
228
- }
229
- /** Inject aggregation totals returned by the /aggregations endpoint. */
230
- async setAggregations(data) {
231
- this.aggregations = data;
232
- }
233
- // ── Lifecycle ──────────────────────────────────────────────────────────────
234
- disconnectedCallback() {
235
- if (this.outsideClickHandler) {
236
- document.removeEventListener('click', this.outsideClickHandler);
237
- this.outsideClickHandler = null;
238
- }
239
- if (this.viewSwitcherClickHandler) {
240
- document.removeEventListener('click', this.viewSwitcherClickHandler);
241
- this.viewSwitcherClickHandler = null;
242
- }
243
- if (this.keydownHandler) {
244
- document.removeEventListener('keydown', this.keydownHandler);
245
- this.keydownHandler = null;
246
- }
247
- }
248
- componentDidRender() {
249
- if (this.colWidths.length === 0 && this.loadedPages.size > 0 && this.totalElements > 0) {
250
- const ths = this.el.querySelectorAll('.mrd-table__header');
251
- if (ths.length > 0) {
252
- this.colWidths = Array.from(ths).map(th => th.offsetWidth);
253
- }
254
- }
255
- }
256
- // ── Paging / scroll helpers ────────────────────────────────────────────────
257
- visibleCount() {
258
- return Math.ceil(this.tableHeight / this.rowHeight);
259
- }
260
- sortParam() {
261
- if (!this.sortField)
262
- return '';
263
- return this.sortDir === 'desc' ? `${this.sortField},desc` : this.sortField;
264
- }
265
- colName(col) {
266
- var _a, _b, _c, _d;
267
- return (_d = (_b = (_a = col.field) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : (_c = col.relation) === null || _c === void 0 ? void 0 : _c.name) !== null && _d !== void 0 ? _d : '';
268
- }
269
- colDataType(col) {
270
- var _a, _b;
271
- if (col.type === 'RELATION')
272
- return 'RELATION';
273
- return (_b = (_a = col.field) === null || _a === void 0 ? void 0 : _a.dataType) !== null && _b !== void 0 ? _b : 'TEXT';
274
- }
275
- // ── Aggregation helpers ────────────────────────────────────────────────────
276
- buildAggregationParams() {
277
- var _a;
278
- const groups = { sum: [], avg: [], count: [] };
279
- for (const col of this.columns) {
280
- if (col.type !== 'FIELD' || !((_a = col.field) === null || _a === void 0 ? void 0 : _a.aggregate))
281
- continue;
282
- const fn = col.field.aggregate.toLowerCase();
283
- if (fn in groups)
284
- groups[fn].push(col.field.name);
285
- }
286
- const params = {};
287
- if (groups.sum.length)
288
- params.sum = groups.sum;
289
- if (groups.avg.length)
290
- params.avg = groups.avg;
291
- if (groups.count.length)
292
- params.count = groups.count;
293
- return Object.keys(params).length > 0 ? params : null;
294
- }
295
- emitLoadAggregations() {
296
- const params = this.buildAggregationParams();
297
- if (params)
298
- this.mrdLoadAggregations.emit(params);
299
- }
300
- renderAggregationValue(col) {
301
- var _a, _b;
302
- if (col.type !== 'FIELD' || !((_a = col.field) === null || _a === void 0 ? void 0 : _a.aggregate) || !this.aggregations)
303
- return '';
304
- const fn = col.field.aggregate.toLowerCase();
305
- const val = (_b = this.aggregations[fn]) === null || _b === void 0 ? void 0 : _b[col.field.name];
306
- if (val == null)
307
- return '';
308
- const dt = col.field.dataType;
309
- if (dt === 'INTEGER')
310
- return format.formatNumber(val, this.locale, { maximumFractionDigits: 0 });
311
- if (dt === 'PERCENTAGE')
312
- return format.formatPercentage(val, this.locale);
313
- if (dt === 'CURRENCY' && col.field.currencyCode)
314
- return format.formatCurrency(val, col.field.currencyCode, this.locale);
315
- return format.formatNumber(val, this.locale);
316
- }
317
- // ── Reset pagination ───────────────────────────────────────────────────────
318
- /** Reset pagination state and scroll to top (used after sort or filter change). */
319
- resetPages() {
320
- if (this.debounceTimer !== null) {
321
- clearTimeout(this.debounceTimer);
322
- this.debounceTimer = null;
323
- }
324
- this.pendingPages.clear();
325
- this.loadedPages = new Map();
326
- this.requestedPages = new Set();
327
- this.colWidths = [];
328
- this.scrollTop = 0;
329
- this.renderStart = 0;
330
- // No BUFFER here — totalElements may be stale after a filter change.
331
- // Only request what is visible; BUFFER kicks in during scroll as usual.
332
- this.renderEnd = Math.max(0, Math.min(this.visibleCount() - 1, this.totalElements - 1));
333
- const scroller = this.el.querySelector('.mrd-table__scroll');
334
- if (scroller)
335
- scroller.scrollTop = 0;
336
- }
337
- handleSortClick(col) {
338
- const name = this.colName(col);
339
- if (this.sortField === name) {
340
- this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc';
341
- }
342
- else {
343
- this.sortField = name;
344
- this.sortDir = 'asc';
345
- }
346
- this.resetPages();
347
- this.emitPagesForWindow(this.renderStart, this.renderEnd);
348
- }
349
- applySort(col, dir) {
350
- this.sortField = this.colName(col);
351
- this.sortDir = dir;
352
- this.resetPages();
353
- this.emitPagesForWindow(this.renderStart, this.renderEnd);
354
- }
355
- emitPagesForWindow(start, end) {
356
- const firstPage = Math.floor(start / this.pageSize);
357
- const lastPage = Math.floor(end / this.pageSize);
358
- const next = new Set(this.requestedPages);
359
- let changed = false;
360
- for (let p = firstPage; p <= lastPage; p++) {
361
- if (!this.loadedPages.has(p) && !next.has(p)) {
362
- next.add(p);
363
- this.mrdLoadPage.emit({ page: p, sort: this.sortParam() });
364
- changed = true;
365
- }
366
- }
367
- if (changed)
368
- this.requestedPages = next;
369
- }
370
- getRow(i) {
371
- var _a;
372
- const page = this.loadedPages.get(Math.floor(i / this.pageSize));
373
- return (_a = page === null || page === void 0 ? void 0 : page[i % this.pageSize]) !== null && _a !== void 0 ? _a : null;
374
- }
375
- requestPagesForWindow(start, end) {
376
- const firstPage = Math.floor(start / this.pageSize);
377
- const lastPage = Math.floor(end / this.pageSize);
378
- let anyNew = false;
379
- for (let p = firstPage; p <= lastPage; p++) {
380
- if (!this.loadedPages.has(p) && !this.requestedPages.has(p) && !this.pendingPages.has(p)) {
381
- this.pendingPages.add(p);
382
- anyNew = true;
383
- }
384
- }
385
- if (!anyNew)
386
- return;
387
- if (this.debounceTimer !== null)
388
- clearTimeout(this.debounceTimer);
389
- this.debounceTimer = setTimeout(() => this.flushPendingPages(), REQUEST_DEBOUNCE_MS);
390
- }
391
- flushPendingPages() {
392
- this.debounceTimer = null;
393
- if (this.pendingPages.size === 0)
394
- return;
395
- const next = new Set(this.requestedPages);
396
- let changed = false;
397
- for (const page of this.pendingPages) {
398
- if (this.loadedPages.has(page) || next.has(page))
399
- continue;
400
- const pageStart = page * this.pageSize;
401
- const pageEnd = pageStart + this.pageSize - 1;
402
- if (pageEnd < this.renderStart || pageStart > this.renderEnd)
403
- continue;
404
- next.add(page);
405
- this.mrdLoadPage.emit({ page, sort: this.sortParam() });
406
- changed = true;
407
- }
408
- this.pendingPages.clear();
409
- if (changed)
410
- this.requestedPages = next;
411
- }
412
- // ── Filter helpers ─────────────────────────────────────────────────────────
413
- handleFilterToggle() {
414
- this.filterMode = !this.filterMode;
415
- if (!this.filterMode)
416
- this.closeFilterPopup();
417
- }
418
- handleFilterOpen(col, e) {
419
- e.stopPropagation();
420
- const btn = e.currentTarget;
421
- const rect = btn.getBoundingClientRect();
422
- let left = rect.left;
423
- if (left + POPUP_WIDTH > window.innerWidth - 8)
424
- left = rect.right - POPUP_WIDTH;
425
- this.popupPos = { top: rect.bottom + 4, left: Math.max(8, left) };
426
- const name = this.colName(col);
427
- const dataType = this.colDataType(col);
428
- const existing = this.activeFilters.get(name);
429
- // Set the default operator explicitly so it is present in the ColumnFilter
430
- // even when the user never touches the operator dropdown.
431
- const defaultOperator = (TEXT_TYPES.has(dataType) || dataType === 'RELATION')
432
- ? 'startsWith'
433
- : undefined;
434
- // For DATETIME, stored values are UTC ISO strings; convert back to local
435
- // "YYYY-MM-DD" dates so the date inputs show what the user originally entered.
436
- // If from and to cover the same local day it was an exact-date filter — restore
437
- // to exact mode so the user sees the single-date input again.
438
- if (dataType === 'DATETIME' && existing && existing.operator !== 'isEmpty' && existing.operator !== 'isNotEmpty') {
439
- const display = Object.assign({}, existing);
440
- if (typeof display.from === 'string' && display.from)
441
- display.from = this.utcISOToLocalDate(display.from);
442
- if (typeof display.to === 'string' && display.to)
443
- display.to = this.utcISOToLocalDateExclusiveEnd(display.to);
444
- if (display.from && display.to && display.from === display.to) {
445
- this.pendingFilter = Object.assign(Object.assign({}, display), { value: display.from, from: undefined, to: undefined });
446
- }
447
- else {
448
- this.pendingFilter = display;
449
- }
450
- }
451
- else {
452
- this.pendingFilter = existing ? Object.assign({}, existing) : { field: name, dataType, operator: defaultOperator };
453
- }
454
- this.openFilterCol = name;
455
- // Close on outside click — re-register to replace any stale handler
456
- if (this.outsideClickHandler)
457
- document.removeEventListener('click', this.outsideClickHandler);
458
- this.outsideClickHandler = (ev) => {
459
- const popup = this.el.querySelector('.mrd-table__filter-popup');
460
- if (popup && !popup.contains(ev.target))
461
- this.closeFilterPopup();
462
- };
463
- document.addEventListener('click', this.outsideClickHandler);
464
- }
465
- closeFilterPopup() {
466
- this.openFilterCol = null;
467
- this.pendingFilter = null;
468
- if (this.outsideClickHandler) {
469
- document.removeEventListener('click', this.outsideClickHandler);
470
- this.outsideClickHandler = null;
471
- }
472
- }
473
- openTextblockModal(text) {
474
- this.textblockModal = text;
475
- if (this.keydownHandler)
476
- document.removeEventListener('keydown', this.keydownHandler);
477
- this.keydownHandler = (ev) => {
478
- if (ev.key === 'Escape')
479
- this.closeTextblockModal();
480
- };
481
- document.addEventListener('keydown', this.keydownHandler);
482
- }
483
- closeTextblockModal() {
484
- this.textblockModal = null;
485
- if (this.keydownHandler) {
486
- document.removeEventListener('keydown', this.keydownHandler);
487
- this.keydownHandler = null;
488
- }
489
- }
490
- setPending(key, val) {
491
- this.pendingFilter = Object.assign(Object.assign({}, this.pendingFilter), { [key]: val });
492
- }
493
- togglePendingValue(key, checked) {
494
- var _a, _b;
495
- const current = (_b = (_a = this.pendingFilter) === null || _a === void 0 ? void 0 : _a.values) !== null && _b !== void 0 ? _b : [];
496
- this.pendingFilter = Object.assign(Object.assign({}, this.pendingFilter), { values: checked ? [...current, key] : current.filter(k => k !== key) });
497
- }
498
- filterHasValue(f) {
499
- if (f.operator === 'isEmpty' || f.operator === 'isNotEmpty')
500
- return true;
501
- if (f.values !== undefined && f.values.length > 0)
502
- return true;
503
- if (f.value != null && f.value !== '')
504
- return true;
505
- if (typeof f.value === 'boolean')
506
- return true;
507
- if (f.from != null && f.from !== '')
508
- return true;
509
- if (f.to != null && f.to !== '')
510
- return true;
511
- return false;
512
- }
513
- // Convert a local "YYYY-MM-DD" date string to the UTC ISO string at the
514
- // start of that local day (midnight). new Date(y, m, d) uses local time.
515
- dateLocalToUTCStart(dateStr) {
516
- if (!dateStr)
517
- return dateStr;
518
- const [year, month, day] = dateStr.split('-').map(Number);
519
- return new Date(year, month - 1, day).toISOString().replace(/\.\d{3}Z$/, 'Z');
520
- }
521
- // Start of the day AFTER the given local date (exclusive range end).
522
- dateLocalToUTCEndExclusive(dateStr) {
523
- if (!dateStr)
524
- return dateStr;
525
- const [year, month, day] = dateStr.split('-').map(Number);
526
- return new Date(year, month - 1, day + 1).toISOString().replace(/\.\d{3}Z$/, 'Z');
527
- }
528
- // Convert a stored UTC ISO string back to the local "YYYY-MM-DD" date.
529
- utcISOToLocalDate(utcStr) {
530
- if (!utcStr)
531
- return utcStr;
532
- const d = new Date(utcStr);
533
- if (isNaN(d.getTime()))
534
- return utcStr;
535
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
536
- }
537
- // The stored "to" value is the exclusive end (midnight of the next day).
538
- // Subtract one day to recover the local date the user entered.
539
- utcISOToLocalDateExclusiveEnd(utcStr) {
540
- if (!utcStr)
541
- return utcStr;
542
- const d = new Date(utcStr);
543
- if (isNaN(d.getTime()))
544
- return utcStr;
545
- d.setDate(d.getDate() - 1);
546
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
547
- }
548
- applyFilter() {
549
- const f = this.pendingFilter;
550
- if (!(f === null || f === void 0 ? void 0 : f.field)) {
551
- this.closeFilterPopup();
552
- return;
553
- }
554
- // For DATETIME fields the user enters local dates; convert to UTC ISO strings.
555
- // Exact date → range covering the full local day (from = midnight, to = next midnight).
556
- // "to" is always the exclusive end (midnight of the next local day).
557
- let normalized = Object.assign({}, f);
558
- if (f.dataType === 'DATETIME' && f.operator !== 'isEmpty' && f.operator !== 'isNotEmpty') {
559
- if (typeof normalized.value === 'string' && normalized.value) {
560
- normalized.from = this.dateLocalToUTCStart(normalized.value);
561
- normalized.to = this.dateLocalToUTCEndExclusive(normalized.value);
562
- normalized.value = undefined;
563
- }
564
- else {
565
- if (typeof normalized.from === 'string' && normalized.from)
566
- normalized.from = this.dateLocalToUTCStart(normalized.from);
567
- if (typeof normalized.to === 'string' && normalized.to)
568
- normalized.to = this.dateLocalToUTCEndExclusive(normalized.to);
569
- }
570
- }
571
- const next = new Map(this.activeFilters);
572
- if (this.filterHasValue(normalized)) {
573
- next.set(normalized.field, normalized);
574
- }
575
- else {
576
- next.delete(normalized.field);
577
- }
578
- this.activeFilters = next;
579
- this.closeFilterPopup();
580
- this.mrdFilter.emit({ filters: Array.from(this.activeFilters.values()) });
581
- this.aggregations = null;
582
- this.emitLoadAggregations();
583
- if (this.totalElements > 0) {
584
- this.resetPages();
585
- this.emitPagesForWindow(this.renderStart, this.renderEnd);
586
- }
587
- }
588
- clearFilter() {
589
- const name = this.openFilterCol;
590
- const next = new Map(this.activeFilters);
591
- if (name)
592
- next.delete(name);
593
- this.activeFilters = next;
594
- this.closeFilterPopup();
595
- this.mrdFilter.emit({ filters: Array.from(this.activeFilters.values()) });
596
- this.aggregations = null;
597
- this.emitLoadAggregations();
598
- if (this.totalElements > 0) {
599
- this.resetPages();
600
- this.emitPagesForWindow(this.renderStart, this.renderEnd);
601
- }
602
- }
603
- clearAllFilters() {
604
- this.activeFilters = new Map();
605
- this.mrdFilter.emit({ filters: [] });
606
- this.aggregations = null;
607
- this.emitLoadAggregations();
608
- if (this.totalElements > 0) {
609
- this.resetPages();
610
- this.emitPagesForWindow(this.renderStart, this.renderEnd);
611
- }
612
- }
613
- // ── View switcher ──────────────────────────────────────────────────────────
614
- openViewSwitcher() {
615
- this.viewSwitcherOpen = true;
616
- if (this.viewSwitcherClickHandler)
617
- document.removeEventListener('click', this.viewSwitcherClickHandler);
618
- this.viewSwitcherClickHandler = (ev) => {
619
- const wrapper = this.el.querySelector('.mrd-table__view-switcher');
620
- if (wrapper && !wrapper.contains(ev.target))
621
- this.closeViewSwitcher();
622
- };
623
- document.addEventListener('click', this.viewSwitcherClickHandler);
624
- }
625
- closeViewSwitcher() {
626
- this.viewSwitcherOpen = false;
627
- if (this.viewSwitcherClickHandler) {
628
- document.removeEventListener('click', this.viewSwitcherClickHandler);
629
- this.viewSwitcherClickHandler = null;
630
- }
631
- }
632
- handleViewSwitch(view) {
633
- this.closeViewSwitcher();
634
- this.mrdSwitchView.emit({ name: view.name, class: view.class });
635
- }
636
- renderViewSwitcher() {
637
- return (index$1.h("div", { class: "mrd-table__view-switcher" }, index$1.h("button", { class: `mrd-table__view-switcher-btn${this.viewSwitcherOpen ? ' mrd-table__view-switcher-btn--open' : ''}`, "aria-label": this.viewLabel, onClick: (e) => {
638
- e.stopPropagation();
639
- this.viewSwitcherOpen ? this.closeViewSwitcher() : this.openViewSwitcher();
640
- } }, index$1.h("svg", { class: "mrd-table__view-switcher-chevron", viewBox: "0 0 24 24", "aria-hidden": "true" }, index$1.h("path", { fill: "currentColor", d: "M7 10l5 5 5-5z" }))), this.viewSwitcherOpen && (index$1.h("div", { class: "mrd-table__view-switcher-dropdown", onClick: (e) => e.stopPropagation() }, this.alternativeViews.map(view => {
641
- var _a;
642
- return (index$1.h("button", { class: "mrd-table__view-switcher-item", onClick: () => this.handleViewSwitch(view) }, (_a = view.label) !== null && _a !== void 0 ? _a : view.name));
643
- })))));
644
- }
645
- // ── Render: toolbar ────────────────────────────────────────────────────────
646
- renderToolbar() {
647
- var _a, _b;
648
- const filterCount = this.activeFilters.size;
649
- const hasActions = ((_a = this.actions) === null || _a === void 0 ? void 0 : _a.length) > 0;
650
- const hasViewSwitcher = !!this.viewLabel && ((_b = this.alternativeViews) === null || _b === void 0 ? void 0 : _b.length) > 0;
651
- return (index$1.h("div", { class: "mrd-table__toolbar" }, index$1.h("div", { class: "mrd-table__toolbar-left" }, index$1.h("button", { class: `mrd-table__action mrd-table__action--secondary mrd-table__filter-toggle${this.filterMode ? ' mrd-table__filter-toggle--active' : ''}`, onClick: () => this.handleFilterToggle() }, index$1.h("svg", { class: "mrd-table__action-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, index$1.h("path", { fill: "currentColor", d: "M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" })), filterCount > 0 && index$1.h("span", { class: "mrd-table__filter-badge" }, filterCount), index$1.h("span", { class: "mrd-table__action-tooltip" }, this.filterMode ? format.t('table_filter_hide', this.locale) : format.t('table_filter', this.locale), filterCount > 0 ? ` (${filterCount} ${format.t('table_filter_active', this.locale)})` : '')), filterCount > 0 && (index$1.h("button", { class: "mrd-table__action mrd-table__action--secondary", onClick: () => this.clearAllFilters() }, index$1.h("svg", { class: "mrd-table__action-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, index$1.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" })), index$1.h("span", { class: "mrd-table__action-tooltip" }, format.t('table_filter_clear_all', this.locale))))), hasViewSwitcher && (index$1.h("div", { class: "mrd-table__toolbar-center" }, index$1.h("span", { class: "mrd-table__view-title" }, this.viewLabel), this.renderViewSwitcher())), hasActions && (index$1.h("div", { class: "mrd-table__toolbar-right" }, this.actions.map(a => {
652
- var _a;
653
- return (index$1.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
654
- ? index$1.h("svg", { class: "mrd-table__action-icon", "aria-hidden": "true" }, index$1.h("use", { href: a.icon }))
655
- : a.label, index$1.h("span", { class: "mrd-table__action-tooltip" }, a.label)));
656
- })))));
657
- }
658
- // ── Render: filter popup ───────────────────────────────────────────────────
659
- renderFilterEditor(col) {
660
- var _a, _b, _c, _d, _e, _f, _g, _h;
661
- const pf = (_a = this.pendingFilter) !== null && _a !== void 0 ? _a : {};
662
- const dataType = this.colDataType(col);
663
- if (NO_FILTER_TYPES.has(dataType)) {
664
- return index$1.h("p", { class: "mrd-table__filter-no-support" }, format.t('filter_no_support', this.locale));
665
- }
666
- if (dataType === 'BOOLEAN') {
667
- const boolOp = pf.operator;
668
- const noValueOp = boolOp === 'isEmpty' || boolOp === 'isNotEmpty';
669
- return (index$1.h("div", { class: "mrd-table__filter-radio-group" }, [
670
- { labelKey: 'filter_all', value: null },
671
- { labelKey: 'yes', value: true },
672
- { labelKey: 'no', value: false },
673
- ].map(opt => (index$1.h("label", { class: "mrd-table__filter-radio-label" }, index$1.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 }); } }), format.t(opt.labelKey, this.locale)))), index$1.h("label", { class: "mrd-table__filter-radio-label" }, index$1.h("input", { type: "radio", name: `bf-${this.openFilterCol}`, checked: boolOp === 'isEmpty', onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: 'isEmpty', value: undefined }); } }), format.t('filter_is_empty', this.locale)), index$1.h("label", { class: "mrd-table__filter-radio-label" }, index$1.h("input", { type: "radio", name: `bf-${this.openFilterCol}`, checked: boolOp === 'isNotEmpty', onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: 'isNotEmpty', value: undefined }); } }), format.t('filter_is_not_empty', this.locale))));
674
- }
675
- if (dataType === 'LIST') {
676
- const items = (_c = (_b = col.field) === null || _b === void 0 ? void 0 : _b.listItems) !== null && _c !== void 0 ? _c : [];
677
- const selected = (_d = pf.values) !== null && _d !== void 0 ? _d : [];
678
- return (index$1.h("div", { class: "mrd-table__filter-list" }, index$1.h("div", { class: "mrd-table__filter-list-controls" }, index$1.h("button", { class: "mrd-table__filter-list-btn", onClick: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { values: items.map(i => i.key) }); } }, format.t('filter_select_all', this.locale)), index$1.h("button", { class: "mrd-table__filter-list-btn", onClick: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { values: [] }); } }, format.t('filter_select_none', this.locale))), items.map(item => (index$1.h("label", { class: "mrd-table__filter-checkbox-label" }, index$1.h("input", { type: "checkbox", checked: selected.includes(item.key), onChange: (e) => this.togglePendingValue(item.key, e.target.checked) }), item.label)))));
679
- }
680
- if (TEXT_TYPES.has(dataType) || dataType === 'RELATION') {
681
- const op = (_e = pf.operator) !== null && _e !== void 0 ? _e : 'startsWith';
682
- const noInput = op === 'isEmpty' || op === 'isNotEmpty';
683
- return (index$1.h("div", { class: "mrd-table__filter-editor" }, index$1.h("select", { class: "mrd-table__filter-select", onChange: (e) => this.setPending('operator', e.target.value) }, [
684
- { val: 'startsWith', labelKey: 'filter_starts_with' },
685
- { val: 'equals', labelKey: 'filter_equals' },
686
- { val: 'isEmpty', labelKey: 'filter_is_empty' },
687
- { val: 'isNotEmpty', labelKey: 'filter_is_not_empty' },
688
- ].map(o => index$1.h("option", { value: o.val, selected: op === o.val }, format.t(o.labelKey, this.locale)))), !noInput && (index$1.h("input", { type: "text", class: "mrd-table__filter-input", value: String((_f = pf.value) !== null && _f !== void 0 ? _f : ''), placeholder: format.t('filter_search_value', this.locale), onInput: (e) => this.setPending('value', e.target.value) }))));
689
- }
690
- if (NUMERIC_TYPES.has(dataType)) {
691
- const numOp = pf.operator;
692
- const noInput = numOp === 'isEmpty' || numOp === 'isNotEmpty';
693
- const rangeMode = !noInput && (pf.from !== undefined || pf.to !== undefined);
694
- return (index$1.h("div", { class: "mrd-table__filter-editor" }, index$1.h("select", { class: "mrd-table__filter-select", onChange: (e) => {
695
- const val = e.target.value;
696
- if (val === 'isEmpty' || val === 'isNotEmpty') {
697
- this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: val, value: undefined, from: undefined, to: undefined });
698
- }
699
- else {
700
- this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: undefined });
701
- }
702
- } }, index$1.h("option", { value: "", selected: !noInput }, format.t('filter_has_value', this.locale)), index$1.h("option", { value: "isEmpty", selected: numOp === 'isEmpty' }, format.t('filter_is_empty', this.locale)), index$1.h("option", { value: "isNotEmpty", selected: numOp === 'isNotEmpty' }, format.t('filter_is_not_empty', this.locale))), !noInput && (index$1.h("div", { class: "mrd-table__filter-editor" }, index$1.h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, index$1.h("label", { class: "mrd-table__filter-radio-label" }, index$1.h("input", { type: "radio", name: `nm-${this.openFilterCol}`, checked: !rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { from: undefined, to: undefined }); } }), format.t('filter_exact', this.locale)), index$1.h("label", { class: "mrd-table__filter-radio-label" }, index$1.h("input", { type: "radio", name: `nm-${this.openFilterCol}`, checked: rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null }); } }), format.t('filter_range', this.locale))), !rangeMode ? (index$1.h("input", { type: "number", class: "mrd-table__filter-input", value: pf.value != null ? String(pf.value) : '', onInput: (e) => this.setPending('value', e.target.value) })) : (index$1.h("div", { class: "mrd-table__filter-range" }, index$1.h("input", { type: "number", class: "mrd-table__filter-input", placeholder: format.t('filter_from', this.locale), value: pf.from != null ? String(pf.from) : '', onInput: (e) => this.setPending('from', e.target.value) }), index$1.h("span", { class: "mrd-table__filter-range-sep" }, "\u2013"), index$1.h("input", { type: "number", class: "mrd-table__filter-input", placeholder: format.t('filter_to', this.locale), value: pf.to != null ? String(pf.to) : '', onInput: (e) => this.setPending('to', e.target.value) })))))));
703
- }
704
- if (dataType === 'DATETIME') {
705
- const dtOp = pf.operator;
706
- const noInput = dtOp === 'isEmpty' || dtOp === 'isNotEmpty';
707
- const rangeMode = !noInput && (pf.from !== undefined || pf.to !== undefined);
708
- return (index$1.h("div", { class: "mrd-table__filter-editor" }, index$1.h("select", { class: "mrd-table__filter-select", onChange: (e) => {
709
- const val = e.target.value;
710
- if (val === 'isEmpty' || val === 'isNotEmpty') {
711
- this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: val, value: undefined, from: undefined, to: undefined });
712
- }
713
- else {
714
- this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: undefined });
715
- }
716
- } }, index$1.h("option", { value: "", selected: !noInput }, format.t('filter_has_value', this.locale)), index$1.h("option", { value: "isEmpty", selected: dtOp === 'isEmpty' }, format.t('filter_is_empty', this.locale)), index$1.h("option", { value: "isNotEmpty", selected: dtOp === 'isNotEmpty' }, format.t('filter_is_not_empty', this.locale))), !noInput && (index$1.h("div", { class: "mrd-table__filter-editor" }, index$1.h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, index$1.h("label", { class: "mrd-table__filter-radio-label" }, index$1.h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: !rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { from: undefined, to: undefined }); } }), format.t('filter_exact', this.locale)), index$1.h("label", { class: "mrd-table__filter-radio-label" }, index$1.h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null }); } }), format.t('filter_range', this.locale))), !rangeMode ? (index$1.h("input", { type: "date", class: "mrd-table__filter-input", value: String((_g = pf.value) !== null && _g !== void 0 ? _g : ''), onInput: (e) => this.setPending('value', e.target.value) })) : (index$1.h("div", { class: "mrd-table__filter-range mrd-table__filter-range--stacked" }, index$1.h("label", { class: "mrd-table__filter-range-label" }, format.t('filter_from', this.locale)), index$1.h("input", { type: "date", class: "mrd-table__filter-input", value: pf.from != null ? String(pf.from) : '', onInput: (e) => this.setPending('from', e.target.value) }), index$1.h("label", { class: "mrd-table__filter-range-label" }, format.t('filter_to', this.locale)), index$1.h("input", { type: "date", class: "mrd-table__filter-input", value: pf.to != null ? String(pf.to) : '', onInput: (e) => this.setPending('to', e.target.value) })))))));
717
- }
718
- if (DATE_TYPES.has(dataType)) {
719
- const inputType = dataType === 'DATE' ? 'date' : 'time';
720
- const dtdOp = pf.operator;
721
- const noInput = dtdOp === 'isEmpty' || dtdOp === 'isNotEmpty';
722
- const rangeMode = !noInput && (pf.from !== undefined || pf.to !== undefined);
723
- return (index$1.h("div", { class: "mrd-table__filter-editor" }, index$1.h("select", { class: "mrd-table__filter-select", onChange: (e) => {
724
- const val = e.target.value;
725
- if (val === 'isEmpty' || val === 'isNotEmpty') {
726
- this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: val, value: undefined, from: undefined, to: undefined });
727
- }
728
- else {
729
- this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: undefined });
730
- }
731
- } }, index$1.h("option", { value: "", selected: !noInput }, format.t('filter_has_value', this.locale)), index$1.h("option", { value: "isEmpty", selected: dtdOp === 'isEmpty' }, format.t('filter_is_empty', this.locale)), index$1.h("option", { value: "isNotEmpty", selected: dtdOp === 'isNotEmpty' }, format.t('filter_is_not_empty', this.locale))), !noInput && (index$1.h("div", { class: "mrd-table__filter-editor" }, index$1.h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, index$1.h("label", { class: "mrd-table__filter-radio-label" }, index$1.h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: !rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { from: undefined, to: undefined }); } }), format.t('filter_exact', this.locale)), index$1.h("label", { class: "mrd-table__filter-radio-label" }, index$1.h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null }); } }), format.t('filter_range', this.locale))), !rangeMode ? (index$1.h("input", { type: inputType, class: "mrd-table__filter-input", value: String((_h = pf.value) !== null && _h !== void 0 ? _h : ''), onInput: (e) => this.setPending('value', e.target.value) })) : (index$1.h("div", { class: "mrd-table__filter-range" }, index$1.h("input", { type: inputType, class: "mrd-table__filter-input", placeholder: format.t('filter_from', this.locale), value: pf.from != null ? String(pf.from) : '', onInput: (e) => this.setPending('from', e.target.value) }), index$1.h("input", { type: inputType, class: "mrd-table__filter-input", placeholder: format.t('filter_to', this.locale), value: pf.to != null ? String(pf.to) : '', onInput: (e) => this.setPending('to', e.target.value) })))))));
732
- }
733
- return null;
734
- }
735
- renderFilterPopup() {
736
- var _a, _b, _c, _d;
737
- if (!this.openFilterCol || !this.pendingFilter)
738
- return null;
739
- const col = this.columns.find(c => this.colName(c) === this.openFilterCol);
740
- if (!col)
741
- return null;
742
- const label = (_d = (_b = (_a = col.field) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : (_c = col.relation) === null || _c === void 0 ? void 0 : _c.label) !== null && _d !== void 0 ? _d : this.openFilterCol;
743
- const sortActive = this.sortField === this.openFilterCol;
744
- return (index$1.h("div", { class: "mrd-table__filter-popup", style: { top: `${this.popupPos.top}px`, left: `${this.popupPos.left}px` }, onClick: (e) => e.stopPropagation() }, index$1.h("div", { class: "mrd-table__filter-popup-header" }, index$1.h("span", { class: "mrd-table__filter-popup-title" }, label), index$1.h("button", { class: "mrd-table__filter-close", onClick: () => this.closeFilterPopup() }, "\u2715")), index$1.h("div", { class: "mrd-table__filter-section" }, index$1.h("div", { class: "mrd-table__filter-section-label" }, format.t('filter_sorting', this.locale)), index$1.h("div", { class: "mrd-table__filter-sort-buttons" }, index$1.h("button", { class: `mrd-table__filter-sort-btn${sortActive && this.sortDir === 'asc' ? ' mrd-table__filter-sort-btn--active' : ''}`, onClick: () => this.applySort(col, 'asc') }, "\u25B2 ", format.t('filter_ascending', this.locale)), index$1.h("button", { class: `mrd-table__filter-sort-btn${sortActive && this.sortDir === 'desc' ? ' mrd-table__filter-sort-btn--active' : ''}`, onClick: () => this.applySort(col, 'desc') }, "\u25BC ", format.t('filter_descending', this.locale)))), index$1.h("div", { class: "mrd-table__filter-divider" }), index$1.h("div", { class: "mrd-table__filter-section" }, index$1.h("div", { class: "mrd-table__filter-section-label" }, format.t('filter_section', this.locale)), this.renderFilterEditor(col)), index$1.h("div", { class: "mrd-table__filter-popup-footer" }, index$1.h("button", { class: "mrd-table__filter-btn mrd-table__filter-btn--clear", onClick: () => this.clearFilter() }, format.t('filter_clear', this.locale)), index$1.h("button", { class: "mrd-table__filter-btn mrd-table__filter-btn--apply", onClick: () => this.applyFilter() }, format.t('filter_apply', this.locale)))));
745
- }
746
- // ── Render: footer ────────────────────────────────────────────────────────
747
- renderFooter(rowCount, effectiveTotal) {
748
- const total = this.totalElements;
749
- // Non-paginated mode: show plain row count
750
- if (total === 0) {
751
- const count = rowCount !== null && rowCount !== void 0 ? rowCount : 0;
752
- if (count === 0)
753
- return null;
754
- return (index$1.h("div", { class: "mrd-table__footer" }, count, " ", format.t('table_of', this.locale), " ", count));
755
- }
756
- // Paginated mode: only show once page 0 has loaded (avoids stale total during filter reset)
757
- if (!this.loadedPages.has(0))
758
- return null;
759
- // Use effectiveTotal (derived from actual page lengths) so the counter
760
- // is correct even when the host has not yet updated totalElements.
761
- const displayTotal = effectiveTotal !== null && effectiveTotal !== void 0 ? effectiveTotal : total;
762
- // Compute from/to independently so partial rows at top/bottom are included.
763
- const from = Math.min(Math.floor(this.scrollTop / this.rowHeight) + 1, displayTotal);
764
- const to = Math.min(Math.ceil((this.scrollTop + this.tableHeight) / this.rowHeight), displayTotal);
765
- return (index$1.h("div", { class: "mrd-table__footer" }, from, "\u2013", to, " ", format.t('table_of', this.locale), " ", displayTotal));
766
- }
767
- // ── Render: cell ──────────────────────────────────────────────────────────
768
- renderCell(col, row) {
769
- var _a, _b, _c, _d;
770
- const numericTypes = new Set(['INTEGER', 'DECIMAL', 'PERCENTAGE', 'CURRENCY']);
771
- const dataType = (_b = (_a = col.field) === null || _a === void 0 ? void 0 : _a.dataType) !== null && _b !== void 0 ? _b : '';
772
- const isNumeric = col.type === 'FIELD' && numericTypes.has(dataType);
773
- const isFile = col.type === 'FIELD' && (dataType === 'FILE' || dataType === 'IMAGE');
774
- if (isFile) {
775
- const name = (_d = (_c = col.field) === null || _c === void 0 ? void 0 : _c.name) !== null && _d !== void 0 ? _d : '';
776
- const fileVal = row === null || row === void 0 ? void 0 : row[name];
777
- const href = fileVal === null || fileVal === void 0 ? void 0 : fileVal.href;
778
- const fileName = fileVal === null || fileVal === void 0 ? void 0 : fileVal.fileName;
779
- return (index$1.h("td", { class: "mrd-table__cell" }, href && fileName ? (index$1.h("button", { class: "mrd-table__file-btn", title: fileName, onClick: (e) => {
780
- e.stopPropagation();
781
- this.mrdDownload.emit({ href, fileName });
782
- } }, index$1.h("svg", { class: "mrd-table__file-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, index$1.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" })), format.t('download', this.locale))) : ''));
783
- }
784
- const TEXTBLOCK_MAX = 200;
785
- if (dataType === 'TEXTBLOCK') {
786
- const full = CellRenderer.render(col, row, this.locale);
787
- if (full.length <= TEXTBLOCK_MAX) {
788
- return index$1.h("td", { class: "mrd-table__cell" }, full);
789
- }
790
- const preview = full.slice(0, TEXTBLOCK_MAX) + '…';
791
- return (index$1.h("td", { class: "mrd-table__cell" }, preview, index$1.h("button", { class: "mrd-table__textblock-btn", onClick: (e) => { e.stopPropagation(); this.openTextblockModal(full); }, "aria-label": format.t('textblock_show_more', this.locale) }, "\u22EF")));
792
- }
793
- const value = CellRenderer.render(col, row, this.locale);
794
- return (index$1.h("td", { class: `mrd-table__cell${isNumeric ? ' mrd-table__cell--numeric' : ''}` }, value));
795
- }
796
- // ── Render: totals row ────────────────────────────────────────────────────
797
- renderTotalsRow() {
798
- if (!this.aggregations)
799
- return null;
800
- if (!this.columns.some(c => { var _a; return c.type === 'FIELD' && ((_a = c.field) === null || _a === void 0 ? void 0 : _a.aggregate); }))
801
- return null;
802
- return (index$1.h("tfoot", null, index$1.h("tr", { class: "mrd-table__totals-row" }, this.columns.map(col => {
803
- var _a, _b;
804
- const val = this.renderAggregationValue(col);
805
- const isNumeric = col.type === 'FIELD' && NUMERIC_TYPES.has((_b = (_a = col.field) === null || _a === void 0 ? void 0 : _a.dataType) !== null && _b !== void 0 ? _b : '');
806
- return (index$1.h("td", { class: `mrd-table__totals-cell${isNumeric ? ' mrd-table__totals-cell--numeric' : ''}` }, val));
807
- }))));
808
- }
809
- // ── Render ─────────────────────────────────────────────────────────────────
810
- render() {
811
- var _a, _b, _c;
812
- if (!((_a = this.columns) === null || _a === void 0 ? void 0 : _a.length))
813
- return null;
814
- // ── Non-paginated mode ──────────────────────────────────────────────────
815
- if (this.totalElements === 0) {
816
- return (index$1.h(index$1.Host, null, this.renderToolbar(), index$1.h("div", { class: "mrd-table" }, index$1.h("table", { class: "mrd-table__table" }, index$1.h("thead", null, index$1.h("tr", null, this.columns.map(col => {
817
- var _a, _b, _c, _d;
818
- const name = this.colName(col);
819
- const isFiltered = this.activeFilters.has(name);
820
- const cls = [
821
- 'mrd-table__header',
822
- isFiltered ? 'mrd-table__header--filtered' : '',
823
- this.filterMode ? 'mrd-table__header--sortable' : '',
824
- ].filter(Boolean).join(' ');
825
- return (index$1.h("th", { class: cls, onClick: this.filterMode ? (e) => this.handleFilterOpen(col, e) : undefined }, index$1.h("span", { class: "mrd-table__header-label" }, (_d = (_b = (_a = col.field) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : (_c = col.relation) === null || _c === void 0 ? void 0 : _c.label) !== null && _d !== void 0 ? _d : ''), isFiltered && this.renderFilterIcon()));
826
- }))), index$1.h("tbody", null, (_b = this.rows) === null || _b === void 0 ? void 0 : _b.map((row, i) => (index$1.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) && (index$1.h("p", { class: "mrd-table__empty" }, format.t('no_results', this.locale)))), this.renderFooter((_c = this.rows) === null || _c === void 0 ? void 0 : _c.length), this.renderFilterPopup(), this.renderTextblockModal()));
827
- }
828
- // ── Paginated / virtual-scroll mode ────────────────────────────────────
829
- // Derive the authoritative row count from loaded pages:
830
- // if any loaded page is shorter than pageSize it is the last page,
831
- // so the true total cannot exceed (pageNum * pageSize + pageRows.length).
832
- // This self-corrects without requiring the host to update totalElements.
833
- let effectiveTotal = this.totalElements;
834
- for (const [pageNum, pageRows] of this.loadedPages) {
835
- if (pageRows.length < this.pageSize) {
836
- effectiveTotal = Math.min(effectiveTotal, pageNum * this.pageSize + pageRows.length);
837
- }
838
- }
839
- // Clamp renderEnd to what we actually know exists (-1 when empty)
840
- const clampedEnd = Math.min(this.renderEnd, effectiveTotal - 1);
841
- const colCount = this.columns.length;
842
- const topSpacerHeight = this.renderStart * this.rowHeight;
843
- const bottomSpacerHeight = Math.max(0, (effectiveTotal - 1 - clampedEnd) * this.rowHeight);
844
- const tableStyle = this.colWidths.length > 0
845
- ? { tableLayout: 'fixed' }
846
- : undefined;
847
- const renderedRows = [];
848
- for (let i = this.renderStart; i <= clampedEnd; i++) {
849
- const row = this.getRow(i);
850
- if (row === null) {
851
- renderedRows.push(index$1.h("tr", { class: "mrd-table__row mrd-table__row--loading" }, index$1.h("td", { class: "mrd-table__cell--placeholder", colSpan: colCount }, index$1.h("span", { class: "mrd-table__placeholder-bar" }))));
852
- }
853
- else {
854
- renderedRows.push(index$1.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))));
855
- }
856
- }
857
- return (index$1.h(index$1.Host, null, this.renderToolbar(), index$1.h("div", { class: "mrd-table__scroll", style: { height: `${this.tableHeight}px` }, onScroll: this.handleScroll }, index$1.h("table", { class: "mrd-table__table", style: tableStyle }, index$1.h("thead", null, index$1.h("tr", null, this.columns.map((col, idx) => {
858
- var _a, _b, _c, _d;
859
- const name = this.colName(col);
860
- const isActive = this.sortField === name;
861
- const isFiltered = this.activeFilters.has(name);
862
- const cls = [
863
- 'mrd-table__header',
864
- 'mrd-table__header--sortable',
865
- isActive ? `mrd-table__header--sorted-${this.sortDir}` : '',
866
- isFiltered ? 'mrd-table__header--filtered' : '',
867
- ].filter(Boolean).join(' ');
868
- return (index$1.h("th", { class: cls, style: this.colWidths[idx] ? { width: `${this.colWidths[idx]}px` } : undefined, onClick: (e) => this.filterMode ? this.handleFilterOpen(col, e) : this.handleSortClick(col) }, index$1.h("span", { class: "mrd-table__header-label" }, (_d = (_b = (_a = col.field) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : (_c = col.relation) === null || _c === void 0 ? void 0 : _c.label) !== null && _d !== void 0 ? _d : ''), isActive && (index$1.h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, this.sortDir === 'asc' ? '▲' : '▼')), !isActive && !this.filterMode && (index$1.h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, "\u21C5")), isFiltered && this.renderFilterIcon()));
869
- }))), index$1.h("tbody", null, topSpacerHeight > 0 && (index$1.h("tr", { class: "mrd-table__spacer", style: { height: `${topSpacerHeight}px` } }, index$1.h("td", { colSpan: colCount }))), renderedRows, bottomSpacerHeight > 0 && (index$1.h("tr", { class: "mrd-table__spacer", style: { height: `${bottomSpacerHeight}px` } }, index$1.h("td", { colSpan: colCount })))), this.renderTotalsRow())), effectiveTotal === 0 && this.loadedPages.has(0) && (index$1.h("p", { class: "mrd-table__empty" }, format.t('no_results', this.locale))), effectiveTotal > 0 && this.renderFooter(undefined, effectiveTotal), this.renderFilterPopup(), this.renderTextblockModal()));
870
- }
871
- renderFilterIcon() {
872
- return (index$1.h("span", { class: "mrd-table__filter-icon", "aria-hidden": "true" }, index$1.h("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "currentColor" }, index$1.h("path", { d: "M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" }))));
873
- }
874
- renderTextblockModal() {
875
- if (this.textblockModal === null)
876
- return null;
877
- return (index$1.h("div", { class: "mrd-table__modal-backdrop", onClick: () => this.closeTextblockModal(), role: "dialog", "aria-modal": "true" }, index$1.h("div", { class: "mrd-table__modal", onClick: (e) => e.stopPropagation() }, index$1.h("button", { class: "mrd-table__modal-close", onClick: () => this.closeTextblockModal(), "aria-label": format.t('close', this.locale) }, "\u2715"), index$1.h("p", { class: "mrd-table__modal-text" }, this.textblockModal))));
878
- }
879
- get el() { return index$1.getElement(this); }
880
- static get watchers() { return {
881
- "totalElements": [{
882
- "totalElementsChanged": 0
883
- }]
884
- }; }
885
- };
886
- MrdTable.style = mrdTableScss();
887
-
888
- exports.mrd_table = MrdTable;