@mmlogic/components 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +196 -61
- package/dist/cjs/format-DBr-GTvS.js +308 -0
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/mosterdcomponents.cjs.js +1 -1
- package/dist/cjs/mrd-boolean-field_16.cjs.entry.js +108 -117
- package/dist/cjs/mrd-table.cjs.entry.js +318 -62
- package/dist/collection/components/mrd-field/mrd-field.js +26 -2
- package/dist/collection/components/mrd-file-field/mrd-file-field.js +70 -2
- package/dist/collection/components/mrd-file-field/mrd-file-field.scss +13 -0
- package/dist/collection/components/mrd-form/mrd-form.js +28 -1
- package/dist/collection/components/mrd-image-field/mrd-image-field.js +71 -2
- package/dist/collection/components/mrd-image-field/mrd-image-field.scss +26 -2
- package/dist/collection/components/mrd-table/mrd-table.js +386 -62
- package/dist/collection/components/mrd-table/mrd-table.scss +388 -0
- package/dist/collection/dev/app.js +48 -4
- package/dist/collection/dev/sprites.svg +55 -0
- package/dist/collection/utils/i18n.js +144 -0
- package/dist/components/i18n.js +1 -1
- package/dist/components/mrd-field2.js +1 -1
- package/dist/components/mrd-file-field2.js +1 -1
- package/dist/components/mrd-form.js +1 -1
- package/dist/components/mrd-image-field2.js +1 -1
- package/dist/components/mrd-table.js +1 -1
- package/dist/esm/format-EzhfM0uw.js +299 -0
- package/dist/esm/loader.js +1 -1
- package/dist/esm/mosterdcomponents.js +1 -1
- package/dist/esm/mrd-boolean-field_16.entry.js +82 -91
- package/dist/esm/mrd-table.entry.js +318 -62
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/p-27f6947a.entry.js +1 -0
- package/dist/mosterdcomponents/p-EzhfM0uw.js +1 -0
- package/dist/mosterdcomponents/p-ca5f9919.entry.js +1 -0
- package/dist/types/components/mrd-field/mrd-field.d.ts +5 -0
- package/dist/types/components/mrd-file-field/mrd-file-field.d.ts +10 -0
- package/dist/types/components/mrd-form/mrd-form.d.ts +5 -0
- package/dist/types/components/mrd-image-field/mrd-image-field.d.ts +10 -0
- package/dist/types/components/mrd-table/mrd-table.d.ts +52 -18
- package/dist/types/components.d.ts +53 -3
- package/dist/types/utils/cell-renderer.d.ts +27 -0
- package/package.json +1 -1
- package/dist/cjs/format-CDw-zie_.js +0 -82
- package/dist/esm/format-Dt-aHxkM.js +0 -74
- package/dist/mosterdcomponents/p-2a8cb2eb.entry.js +0 -1
- package/dist/mosterdcomponents/p-Dt-aHxkM.js +0 -1
- package/dist/mosterdcomponents/p-baf08615.entry.js +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var index$1 = require('./index-BPj2cBXs.js');
|
|
4
|
-
var format = require('./format-
|
|
4
|
+
var format = require('./format-DBr-GTvS.js');
|
|
5
5
|
var index = require('./index.cjs.js');
|
|
6
6
|
|
|
7
7
|
class CellRenderer {
|
|
@@ -65,22 +65,29 @@ class CellRenderer {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
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.65rem;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__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__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}`;
|
|
68
|
+
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.65rem;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__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__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__header-filter-btn.sc-mrd-table{display:inline-flex;align-items:center;justify-content:center;margin-left:var(--mrd-space-1);padding:0 3px;background:transparent;border:none;border-radius:3px;cursor:pointer;color:var(--mrd-color-neutral-500);font-size:0.8rem;line-height:1;vertical-align:middle}.mrd-table__header-filter-btn.sc-mrd-table:hover{background:var(--mrd-color-neutral-200);color:var(--mrd-color-neutral-800)}.mrd-table__header-filter-btn--active.sc-mrd-table{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-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__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}`;
|
|
69
69
|
|
|
70
70
|
const BUFFER = 10;
|
|
71
71
|
/** Wacht deze tijd (ms) na het laatste scroll-event voordat pagina's worden
|
|
72
72
|
* aangevraagd. Pagina's die de gebruiker snel voorbij scrollt worden zo geskipt. */
|
|
73
73
|
const REQUEST_DEBOUNCE_MS = 150;
|
|
74
|
+
/** Breedte van de filterpopup in px — voor overflow-correctie. */
|
|
75
|
+
const POPUP_WIDTH = 280;
|
|
76
|
+
const TEXT_TYPES = new Set(['TEXT', 'TEXTBLOCK', 'EMAIL', 'HYPERLINK']);
|
|
77
|
+
const NUMERIC_TYPES = new Set(['INTEGER', 'DECIMAL', 'PERCENTAGE', 'CURRENCY']);
|
|
78
|
+
const DATE_TYPES = new Set(['DATE', 'DATETIME', 'TIME']);
|
|
79
|
+
const NO_FILTER_TYPES = new Set(['FILE', 'IMAGE']);
|
|
74
80
|
const MrdTable = class {
|
|
75
81
|
constructor(hostRef) {
|
|
76
82
|
index$1.registerInstance(this, hostRef);
|
|
77
83
|
this.mrdLoadPage = index$1.createEvent(this, "mrdLoadPage");
|
|
78
84
|
this.mrdRowClick = index$1.createEvent(this, "mrdRowClick");
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
this.mrdAction = index$1.createEvent(this, "mrdAction");
|
|
86
|
+
this.mrdFilter = index$1.createEvent(this, "mrdFilter");
|
|
87
|
+
// ── Non-state internals ────────────────────────────────────────────────────
|
|
81
88
|
this.pendingPages = new Set();
|
|
82
|
-
/** Handle van de actieve debounce-timer. */
|
|
83
89
|
this.debounceTimer = null;
|
|
90
|
+
this.outsideClickHandler = null;
|
|
84
91
|
// ── Props ──────────────────────────────────────────────────────────────────
|
|
85
92
|
this.columns = [];
|
|
86
93
|
/** Direct rows (non-paginated mode, used when totalElements === 0). */
|
|
@@ -97,32 +104,45 @@ const MrdTable = class {
|
|
|
97
104
|
/** Initial sort applied on load, e.g. "timestamp,desc" or "name".
|
|
98
105
|
* Parsed by init() into sortField + sortDir. */
|
|
99
106
|
this.defaultSort = '';
|
|
107
|
+
/** Toolbar action buttons rendered above the table. */
|
|
108
|
+
this.actions = [];
|
|
100
109
|
// ── Internal state ─────────────────────────────────────────────────────────
|
|
101
|
-
/** Pages injected via setPage(). Always replaced by a new Map to trigger re-render. */
|
|
102
110
|
this.loadedPages = new Map();
|
|
103
|
-
/** Pages already requested via mrdLoadPage (to avoid duplicate events). */
|
|
104
111
|
this.requestedPages = new Set();
|
|
105
|
-
/** Absolute index of the first row currently in the render window. */
|
|
106
112
|
this.renderStart = 0;
|
|
107
|
-
/** Absolute index of the last row currently in the render window. */
|
|
108
113
|
this.renderEnd = 0;
|
|
109
|
-
/** Locked column widths (px) — measured after first page renders, then fixed. */
|
|
110
114
|
this.colWidths = [];
|
|
111
|
-
/** Column currently used for sorting (empty = no sort). */
|
|
112
115
|
this.sortField = '';
|
|
113
|
-
/** Sort direction for sortField. */
|
|
114
116
|
this.sortDir = 'asc';
|
|
117
|
+
/** Whether the filter UI is visible on column headers. */
|
|
118
|
+
this.filterMode = false;
|
|
119
|
+
/** Active filters keyed by field name. */
|
|
120
|
+
this.activeFilters = new Map();
|
|
121
|
+
/** Field name of the currently open filter popup (null = closed). */
|
|
122
|
+
this.openFilterCol = null;
|
|
123
|
+
/** Filter state being edited in the open popup. */
|
|
124
|
+
this.pendingFilter = null;
|
|
125
|
+
/** Viewport-relative position for the filter popup. */
|
|
126
|
+
this.popupPos = { top: 0, left: 0 };
|
|
127
|
+
/** Current scroll offset of the scroll container — drives pagination footer. */
|
|
128
|
+
this.scrollTop = 0;
|
|
115
129
|
this.handleScroll = (e) => {
|
|
116
130
|
const scroller = e.currentTarget;
|
|
117
131
|
const scrollTop = scroller.scrollTop;
|
|
118
132
|
const total = this.totalElements;
|
|
119
133
|
const visStart = Math.floor(scrollTop / this.rowHeight);
|
|
120
134
|
const visEnd = Math.min(visStart + this.visibleCount(), total - 1);
|
|
135
|
+
this.scrollTop = scrollTop;
|
|
121
136
|
this.renderStart = Math.max(0, visStart - BUFFER);
|
|
122
137
|
this.renderEnd = Math.min(total - 1, visEnd + BUFFER);
|
|
123
138
|
this.requestPagesForWindow(this.renderStart, this.renderEnd);
|
|
124
139
|
};
|
|
125
140
|
}
|
|
141
|
+
// ── Prop watchers ─────────────────────────────────────────────────────────
|
|
142
|
+
/** Clamp renderEnd when totalElements shrinks (e.g. after a filter is applied). */
|
|
143
|
+
totalElementsChanged(newVal) {
|
|
144
|
+
this.renderEnd = Math.min(this.renderEnd, Math.max(0, newVal - 1));
|
|
145
|
+
}
|
|
126
146
|
// ── Public API ─────────────────────────────────────────────────────────────
|
|
127
147
|
/**
|
|
128
148
|
* Initialise (or reset) the virtual scroll.
|
|
@@ -139,7 +159,6 @@ const MrdTable = class {
|
|
|
139
159
|
this.loadedPages = new Map();
|
|
140
160
|
this.requestedPages = new Set();
|
|
141
161
|
this.colWidths = [];
|
|
142
|
-
// Apply defaultSort prop as the initial sort state.
|
|
143
162
|
if (this.defaultSort) {
|
|
144
163
|
const parts = this.defaultSort.split(',');
|
|
145
164
|
this.sortField = parts[0].trim();
|
|
@@ -149,66 +168,105 @@ const MrdTable = class {
|
|
|
149
168
|
this.sortField = '';
|
|
150
169
|
this.sortDir = 'asc';
|
|
151
170
|
}
|
|
171
|
+
this.scrollTop = 0;
|
|
152
172
|
this.renderStart = 0;
|
|
153
|
-
|
|
154
|
-
//
|
|
173
|
+
// No BUFFER on init — only request what fits the visible area (page 0).
|
|
174
|
+
// BUFFER is applied during scroll to pre-fetch the next page proactively.
|
|
175
|
+
this.renderEnd = Math.max(0, Math.min(this.visibleCount() - 1, this.totalElements - 1));
|
|
155
176
|
const scroller = this.el.querySelector('.mrd-table__scroll');
|
|
156
177
|
if (scroller)
|
|
157
178
|
scroller.scrollTop = 0;
|
|
158
|
-
// Do NOT emit mrdLoadPage here — the host injects page 0 via setPage().
|
|
159
179
|
}
|
|
160
180
|
/**
|
|
161
181
|
* Inject the rows for a given page (0-based).
|
|
162
182
|
* Creates a new Map reference so Stencil detects the state change.
|
|
183
|
+
*
|
|
184
|
+
* When the page contains fewer rows than pageSize it is the last page.
|
|
185
|
+
* renderEnd is clamped immediately so no loading-placeholder rows appear
|
|
186
|
+
* beyond the actual data — without requiring the host to update totalElements.
|
|
163
187
|
*/
|
|
164
188
|
async setPage(pageNumber, rows) {
|
|
189
|
+
if (rows.length < this.pageSize) {
|
|
190
|
+
// lastRowIdx is -1 when the page is empty; clamp renderEnd to -1 so the
|
|
191
|
+
// render loop does not execute and no shimmer rows appear.
|
|
192
|
+
const lastRowIdx = pageNumber * this.pageSize + rows.length - 1;
|
|
193
|
+
this.renderEnd = Math.min(this.renderEnd, lastRowIdx);
|
|
194
|
+
}
|
|
165
195
|
const next = new Map(this.loadedPages);
|
|
166
196
|
next.set(pageNumber, rows);
|
|
167
197
|
this.loadedPages = next;
|
|
168
198
|
}
|
|
169
|
-
// ──
|
|
199
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
200
|
+
disconnectedCallback() {
|
|
201
|
+
if (this.outsideClickHandler) {
|
|
202
|
+
document.removeEventListener('click', this.outsideClickHandler);
|
|
203
|
+
this.outsideClickHandler = null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
componentDidRender() {
|
|
207
|
+
if (this.colWidths.length === 0 && this.loadedPages.size > 0 && this.totalElements > 0) {
|
|
208
|
+
const ths = this.el.querySelectorAll('.mrd-table__header');
|
|
209
|
+
if (ths.length > 0) {
|
|
210
|
+
this.colWidths = Array.from(ths).map(th => th.offsetWidth);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// ── Paging / scroll helpers ────────────────────────────────────────────────
|
|
170
215
|
visibleCount() {
|
|
171
216
|
return Math.ceil(this.tableHeight / this.rowHeight);
|
|
172
217
|
}
|
|
173
|
-
/** Returns the current sort value for use in ?sort= query params. */
|
|
174
218
|
sortParam() {
|
|
175
219
|
if (!this.sortField)
|
|
176
220
|
return '';
|
|
177
221
|
return this.sortDir === 'desc' ? `${this.sortField},desc` : this.sortField;
|
|
178
222
|
}
|
|
179
|
-
/** Called when a header cell is clicked. Toggles direction or sets a new column. */
|
|
180
223
|
colName(col) {
|
|
181
224
|
var _a, _b, _c, _d;
|
|
182
225
|
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 : '';
|
|
183
226
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
// Cancel any pending scroll debounce.
|
|
227
|
+
colDataType(col) {
|
|
228
|
+
var _a, _b;
|
|
229
|
+
if (col.type === 'RELATION')
|
|
230
|
+
return 'RELATION';
|
|
231
|
+
return (_b = (_a = col.field) === null || _a === void 0 ? void 0 : _a.dataType) !== null && _b !== void 0 ? _b : 'TEXT';
|
|
232
|
+
}
|
|
233
|
+
/** Reset pagination state and scroll to top (used after sort or filter change). */
|
|
234
|
+
resetPages() {
|
|
194
235
|
if (this.debounceTimer !== null) {
|
|
195
236
|
clearTimeout(this.debounceTimer);
|
|
196
237
|
this.debounceTimer = null;
|
|
197
238
|
}
|
|
198
239
|
this.pendingPages.clear();
|
|
199
|
-
// Wipe all loaded data so the new sort order is fetched fresh.
|
|
200
240
|
this.loadedPages = new Map();
|
|
201
241
|
this.requestedPages = new Set();
|
|
202
242
|
this.colWidths = [];
|
|
243
|
+
this.scrollTop = 0;
|
|
203
244
|
this.renderStart = 0;
|
|
204
|
-
|
|
245
|
+
// No BUFFER here — totalElements may be stale after a filter change.
|
|
246
|
+
// Only request what is visible; BUFFER kicks in during scroll as usual.
|
|
247
|
+
this.renderEnd = Math.max(0, Math.min(this.visibleCount() - 1, this.totalElements - 1));
|
|
205
248
|
const scroller = this.el.querySelector('.mrd-table__scroll');
|
|
206
249
|
if (scroller)
|
|
207
250
|
scroller.scrollTop = 0;
|
|
208
|
-
|
|
251
|
+
}
|
|
252
|
+
handleSortClick(col) {
|
|
253
|
+
const name = this.colName(col);
|
|
254
|
+
if (this.sortField === name) {
|
|
255
|
+
this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc';
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
this.sortField = name;
|
|
259
|
+
this.sortDir = 'asc';
|
|
260
|
+
}
|
|
261
|
+
this.resetPages();
|
|
262
|
+
this.emitPagesForWindow(this.renderStart, this.renderEnd);
|
|
263
|
+
}
|
|
264
|
+
applySort(col, dir) {
|
|
265
|
+
this.sortField = this.colName(col);
|
|
266
|
+
this.sortDir = dir;
|
|
267
|
+
this.resetPages();
|
|
209
268
|
this.emitPagesForWindow(this.renderStart, this.renderEnd);
|
|
210
269
|
}
|
|
211
|
-
/** Emits mrdLoadPage immediately for all missing pages in [start, end]. */
|
|
212
270
|
emitPagesForWindow(start, end) {
|
|
213
271
|
const firstPage = Math.floor(start / this.pageSize);
|
|
214
272
|
const lastPage = Math.floor(end / this.pageSize);
|
|
@@ -241,13 +299,10 @@ const MrdTable = class {
|
|
|
241
299
|
}
|
|
242
300
|
if (!anyNew)
|
|
243
301
|
return;
|
|
244
|
-
// Reset de timer: wacht tot het scrollen even stopt.
|
|
245
302
|
if (this.debounceTimer !== null)
|
|
246
303
|
clearTimeout(this.debounceTimer);
|
|
247
304
|
this.debounceTimer = setTimeout(() => this.flushPendingPages(), REQUEST_DEBOUNCE_MS);
|
|
248
305
|
}
|
|
249
|
-
/** Emitteert mrdLoadPage alleen voor pagina's die na de debounce-wachttijd
|
|
250
|
-
* nog steeds binnen het huidige render-venster vallen. */
|
|
251
306
|
flushPendingPages() {
|
|
252
307
|
this.debounceTimer = null;
|
|
253
308
|
if (this.pendingPages.size === 0)
|
|
@@ -255,10 +310,8 @@ const MrdTable = class {
|
|
|
255
310
|
const next = new Set(this.requestedPages);
|
|
256
311
|
let changed = false;
|
|
257
312
|
for (const page of this.pendingPages) {
|
|
258
|
-
// Sla over als pagina inmiddels geladen of al aangevraagd is.
|
|
259
313
|
if (this.loadedPages.has(page) || next.has(page))
|
|
260
314
|
continue;
|
|
261
|
-
// Sla over als de pagina buiten het huidige venster is geraakt.
|
|
262
315
|
const pageStart = page * this.pageSize;
|
|
263
316
|
const pageEnd = pageStart + this.pageSize - 1;
|
|
264
317
|
if (pageEnd < this.renderStart || pageStart > this.renderEnd)
|
|
@@ -271,42 +324,236 @@ const MrdTable = class {
|
|
|
271
324
|
if (changed)
|
|
272
325
|
this.requestedPages = next;
|
|
273
326
|
}
|
|
274
|
-
// ──
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
327
|
+
// ── Filter helpers ─────────────────────────────────────────────────────────
|
|
328
|
+
handleFilterToggle() {
|
|
329
|
+
this.filterMode = !this.filterMode;
|
|
330
|
+
if (!this.filterMode)
|
|
331
|
+
this.closeFilterPopup();
|
|
332
|
+
}
|
|
333
|
+
handleFilterOpen(col, e) {
|
|
334
|
+
e.stopPropagation();
|
|
335
|
+
const btn = e.currentTarget;
|
|
336
|
+
const rect = btn.getBoundingClientRect();
|
|
337
|
+
let left = rect.left;
|
|
338
|
+
if (left + POPUP_WIDTH > window.innerWidth - 8)
|
|
339
|
+
left = rect.right - POPUP_WIDTH;
|
|
340
|
+
this.popupPos = { top: rect.bottom + 4, left: Math.max(8, left) };
|
|
341
|
+
const name = this.colName(col);
|
|
342
|
+
const dataType = this.colDataType(col);
|
|
343
|
+
const existing = this.activeFilters.get(name);
|
|
344
|
+
this.pendingFilter = existing ? Object.assign({}, existing) : { field: name, dataType };
|
|
345
|
+
this.openFilterCol = name;
|
|
346
|
+
// Close on outside click — re-register to replace any stale handler
|
|
347
|
+
if (this.outsideClickHandler)
|
|
348
|
+
document.removeEventListener('click', this.outsideClickHandler);
|
|
349
|
+
this.outsideClickHandler = (ev) => {
|
|
350
|
+
const popup = this.el.querySelector('.mrd-table__filter-popup');
|
|
351
|
+
if (popup && !popup.contains(ev.target))
|
|
352
|
+
this.closeFilterPopup();
|
|
353
|
+
};
|
|
354
|
+
document.addEventListener('click', this.outsideClickHandler);
|
|
355
|
+
}
|
|
356
|
+
closeFilterPopup() {
|
|
357
|
+
this.openFilterCol = null;
|
|
358
|
+
this.pendingFilter = null;
|
|
359
|
+
if (this.outsideClickHandler) {
|
|
360
|
+
document.removeEventListener('click', this.outsideClickHandler);
|
|
361
|
+
this.outsideClickHandler = null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
setPending(key, val) {
|
|
365
|
+
this.pendingFilter = Object.assign(Object.assign({}, this.pendingFilter), { [key]: val });
|
|
366
|
+
}
|
|
367
|
+
togglePendingValue(key, checked) {
|
|
368
|
+
var _a, _b;
|
|
369
|
+
const current = (_b = (_a = this.pendingFilter) === null || _a === void 0 ? void 0 : _a.values) !== null && _b !== void 0 ? _b : [];
|
|
370
|
+
this.pendingFilter = Object.assign(Object.assign({}, this.pendingFilter), { values: checked ? [...current, key] : current.filter(k => k !== key) });
|
|
371
|
+
}
|
|
372
|
+
filterHasValue(f) {
|
|
373
|
+
if (f.operator === 'isEmpty' || f.operator === 'isNotEmpty')
|
|
374
|
+
return true;
|
|
375
|
+
if (f.values !== undefined && f.values.length > 0)
|
|
376
|
+
return true;
|
|
377
|
+
if (f.value != null && f.value !== '')
|
|
378
|
+
return true;
|
|
379
|
+
if (typeof f.value === 'boolean')
|
|
380
|
+
return true;
|
|
381
|
+
if (f.from != null && f.from !== '')
|
|
382
|
+
return true;
|
|
383
|
+
if (f.to != null && f.to !== '')
|
|
384
|
+
return true;
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
applyFilter() {
|
|
388
|
+
const f = this.pendingFilter;
|
|
389
|
+
if (!(f === null || f === void 0 ? void 0 : f.field)) {
|
|
390
|
+
this.closeFilterPopup();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const next = new Map(this.activeFilters);
|
|
394
|
+
if (this.filterHasValue(f)) {
|
|
395
|
+
next.set(f.field, f);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
next.delete(f.field);
|
|
399
|
+
}
|
|
400
|
+
this.activeFilters = next;
|
|
401
|
+
this.closeFilterPopup();
|
|
402
|
+
this.mrdFilter.emit({ filters: Array.from(this.activeFilters.values()) });
|
|
403
|
+
if (this.totalElements > 0) {
|
|
404
|
+
this.resetPages();
|
|
405
|
+
this.emitPagesForWindow(this.renderStart, this.renderEnd);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
clearFilter() {
|
|
409
|
+
const name = this.openFilterCol;
|
|
410
|
+
const next = new Map(this.activeFilters);
|
|
411
|
+
if (name)
|
|
412
|
+
next.delete(name);
|
|
413
|
+
this.activeFilters = next;
|
|
414
|
+
this.closeFilterPopup();
|
|
415
|
+
this.mrdFilter.emit({ filters: Array.from(this.activeFilters.values()) });
|
|
416
|
+
if (this.totalElements > 0) {
|
|
417
|
+
this.resetPages();
|
|
418
|
+
this.emitPagesForWindow(this.renderStart, this.renderEnd);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
clearAllFilters() {
|
|
422
|
+
this.activeFilters = new Map();
|
|
423
|
+
this.mrdFilter.emit({ filters: [] });
|
|
424
|
+
if (this.totalElements > 0) {
|
|
425
|
+
this.resetPages();
|
|
426
|
+
this.emitPagesForWindow(this.renderStart, this.renderEnd);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// ── Render: toolbar ────────────────────────────────────────────────────────
|
|
430
|
+
renderToolbar() {
|
|
431
|
+
var _a;
|
|
432
|
+
const filterCount = this.activeFilters.size;
|
|
433
|
+
const hasActions = ((_a = this.actions) === null || _a === void 0 ? void 0 : _a.length) > 0;
|
|
434
|
+
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))))), hasActions && (index$1.h("div", { class: "mrd-table__toolbar-right" }, this.actions.map(a => {
|
|
435
|
+
var _a;
|
|
436
|
+
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
|
|
437
|
+
? index$1.h("svg", { class: "mrd-table__action-icon", "aria-hidden": "true" }, index$1.h("use", { href: a.icon }))
|
|
438
|
+
: a.label, index$1.h("span", { class: "mrd-table__action-tooltip" }, a.label)));
|
|
439
|
+
})))));
|
|
440
|
+
}
|
|
441
|
+
// ── Render: filter popup ───────────────────────────────────────────────────
|
|
442
|
+
renderFilterEditor(col) {
|
|
443
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
444
|
+
const pf = (_a = this.pendingFilter) !== null && _a !== void 0 ? _a : {};
|
|
445
|
+
const dataType = this.colDataType(col);
|
|
446
|
+
if (NO_FILTER_TYPES.has(dataType)) {
|
|
447
|
+
return index$1.h("p", { class: "mrd-table__filter-no-support" }, format.t('filter_no_support', this.locale));
|
|
448
|
+
}
|
|
449
|
+
if (dataType === 'BOOLEAN') {
|
|
450
|
+
return (index$1.h("div", { class: "mrd-table__filter-radio-group" }, [
|
|
451
|
+
{ labelKey: 'filter_all', value: null },
|
|
452
|
+
{ labelKey: 'yes', value: true },
|
|
453
|
+
{ labelKey: 'no', value: false },
|
|
454
|
+
].map(opt => (index$1.h("label", { class: "mrd-table__filter-radio-label" }, index$1.h("input", { type: "radio", name: `bf-${this.openFilterCol}`, checked: pf.value === opt.value, onChange: () => this.setPending('value', opt.value) }), format.t(opt.labelKey, this.locale))))));
|
|
283
455
|
}
|
|
456
|
+
if (dataType === 'LIST') {
|
|
457
|
+
const items = (_c = (_b = col.field) === null || _b === void 0 ? void 0 : _b.listItems) !== null && _c !== void 0 ? _c : [];
|
|
458
|
+
const selected = (_d = pf.values) !== null && _d !== void 0 ? _d : [];
|
|
459
|
+
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)))));
|
|
460
|
+
}
|
|
461
|
+
if (TEXT_TYPES.has(dataType) || dataType === 'RELATION') {
|
|
462
|
+
const op = (_e = pf.operator) !== null && _e !== void 0 ? _e : 'startsWith';
|
|
463
|
+
const noInput = op === 'isEmpty' || op === 'isNotEmpty';
|
|
464
|
+
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) }, [
|
|
465
|
+
{ val: 'startsWith', labelKey: 'filter_starts_with' },
|
|
466
|
+
{ val: 'equals', labelKey: 'filter_equals' },
|
|
467
|
+
{ val: 'isEmpty', labelKey: 'filter_is_empty' },
|
|
468
|
+
{ val: 'isNotEmpty', labelKey: 'filter_is_not_empty' },
|
|
469
|
+
].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) }))));
|
|
470
|
+
}
|
|
471
|
+
if (NUMERIC_TYPES.has(dataType)) {
|
|
472
|
+
const rangeMode = pf.from !== undefined || pf.to !== undefined;
|
|
473
|
+
return (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) })))));
|
|
474
|
+
}
|
|
475
|
+
if (DATE_TYPES.has(dataType)) {
|
|
476
|
+
const inputType = dataType === 'DATE' ? 'date'
|
|
477
|
+
: dataType === 'DATETIME' ? 'datetime-local'
|
|
478
|
+
: 'time';
|
|
479
|
+
const rangeMode = pf.from !== undefined || pf.to !== undefined;
|
|
480
|
+
return (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((_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" }, 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) })))));
|
|
481
|
+
}
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
renderFilterPopup() {
|
|
485
|
+
var _a, _b, _c, _d;
|
|
486
|
+
if (!this.openFilterCol || !this.pendingFilter)
|
|
487
|
+
return null;
|
|
488
|
+
const col = this.columns.find(c => this.colName(c) === this.openFilterCol);
|
|
489
|
+
if (!col)
|
|
490
|
+
return null;
|
|
491
|
+
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;
|
|
492
|
+
const sortActive = this.sortField === this.openFilterCol;
|
|
493
|
+
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)))));
|
|
494
|
+
}
|
|
495
|
+
// ── Render: footer ────────────────────────────────────────────────────────
|
|
496
|
+
renderFooter(rowCount, effectiveTotal) {
|
|
497
|
+
const total = this.totalElements;
|
|
498
|
+
// Non-paginated mode: show plain row count
|
|
499
|
+
if (total === 0) {
|
|
500
|
+
const count = rowCount !== null && rowCount !== void 0 ? rowCount : 0;
|
|
501
|
+
if (count === 0)
|
|
502
|
+
return null;
|
|
503
|
+
return (index$1.h("div", { class: "mrd-table__footer" }, count, " ", format.t('table_of', this.locale), " ", count));
|
|
504
|
+
}
|
|
505
|
+
// Paginated mode: only show once page 0 has loaded (avoids stale total during filter reset)
|
|
506
|
+
if (!this.loadedPages.has(0))
|
|
507
|
+
return null;
|
|
508
|
+
// Use effectiveTotal (derived from actual page lengths) so the counter
|
|
509
|
+
// is correct even when the host has not yet updated totalElements.
|
|
510
|
+
const displayTotal = effectiveTotal !== null && effectiveTotal !== void 0 ? effectiveTotal : total;
|
|
511
|
+
// Compute from/to independently so partial rows at top/bottom are included.
|
|
512
|
+
const from = Math.min(Math.floor(this.scrollTop / this.rowHeight) + 1, displayTotal);
|
|
513
|
+
const to = Math.min(Math.ceil((this.scrollTop + this.tableHeight) / this.rowHeight), displayTotal);
|
|
514
|
+
return (index$1.h("div", { class: "mrd-table__footer" }, from, "\u2013", to, " ", format.t('table_of', this.locale), " ", displayTotal));
|
|
284
515
|
}
|
|
285
516
|
// ── Render ─────────────────────────────────────────────────────────────────
|
|
286
517
|
render() {
|
|
287
|
-
var _a, _b;
|
|
518
|
+
var _a, _b, _c;
|
|
288
519
|
if (!((_a = this.columns) === null || _a === void 0 ? void 0 : _a.length))
|
|
289
520
|
return null;
|
|
290
521
|
const numericTypes = new Set(['INTEGER', 'DECIMAL', 'PERCENTAGE', 'CURRENCY']);
|
|
291
522
|
// ── Non-paginated mode ──────────────────────────────────────────────────
|
|
292
523
|
if (this.totalElements === 0) {
|
|
293
|
-
return (index$1.h(index$1.Host, null, 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 => {
|
|
524
|
+
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 => {
|
|
294
525
|
var _a, _b, _c, _d;
|
|
295
|
-
|
|
526
|
+
const name = this.colName(col);
|
|
527
|
+
const isFiltered = this.activeFilters.has(name);
|
|
528
|
+
return (index$1.h("th", { class: `mrd-table__header${isFiltered ? ' mrd-table__header--filtered' : ''}` }, 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 : ''), this.filterMode && (index$1.h("button", { class: `mrd-table__header-filter-btn${isFiltered ? ' mrd-table__header-filter-btn--active' : ''}`, onClick: (e) => this.handleFilterOpen(col, e) }, "\u25BE"))));
|
|
296
529
|
}))), 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 => {
|
|
297
530
|
var _a, _b;
|
|
298
531
|
const value = CellRenderer.render(col, row, this.locale);
|
|
299
532
|
const isNumeric = col.type === 'FIELD' && numericTypes.has((_b = (_a = col.field) === null || _a === void 0 ? void 0 : _a.dataType) !== null && _b !== void 0 ? _b : '');
|
|
300
533
|
return (index$1.h("td", { class: `mrd-table__cell${isNumeric ? ' mrd-table__cell--numeric' : ''}` }, value));
|
|
301
|
-
})))))), (!this.rows || this.rows.length === 0) && (index$1.h("p", { class: "mrd-table__empty" },
|
|
534
|
+
})))))), (!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()));
|
|
302
535
|
}
|
|
303
536
|
// ── Paginated / virtual-scroll mode ────────────────────────────────────
|
|
304
|
-
|
|
537
|
+
// Derive the authoritative row count from loaded pages:
|
|
538
|
+
// if any loaded page is shorter than pageSize it is the last page,
|
|
539
|
+
// so the true total cannot exceed (pageNum * pageSize + pageRows.length).
|
|
540
|
+
// This self-corrects without requiring the host to update totalElements.
|
|
541
|
+
let effectiveTotal = this.totalElements;
|
|
542
|
+
for (const [pageNum, pageRows] of this.loadedPages) {
|
|
543
|
+
if (pageRows.length < this.pageSize) {
|
|
544
|
+
effectiveTotal = Math.min(effectiveTotal, pageNum * this.pageSize + pageRows.length);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// Clamp renderEnd to what we actually know exists (-1 when empty)
|
|
548
|
+
const clampedEnd = Math.min(this.renderEnd, effectiveTotal - 1);
|
|
305
549
|
const colCount = this.columns.length;
|
|
306
550
|
const topSpacerHeight = this.renderStart * this.rowHeight;
|
|
307
|
-
const bottomSpacerHeight = Math.max(0, (
|
|
551
|
+
const bottomSpacerHeight = Math.max(0, (effectiveTotal - 1 - clampedEnd) * this.rowHeight);
|
|
552
|
+
const tableStyle = this.colWidths.length > 0
|
|
553
|
+
? { tableLayout: 'fixed' }
|
|
554
|
+
: undefined;
|
|
308
555
|
const renderedRows = [];
|
|
309
|
-
for (let i = this.renderStart; i <=
|
|
556
|
+
for (let i = this.renderStart; i <= clampedEnd; i++) {
|
|
310
557
|
const row = this.getRow(i);
|
|
311
558
|
if (row === null) {
|
|
312
559
|
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" }))));
|
|
@@ -320,17 +567,26 @@ const MrdTable = class {
|
|
|
320
567
|
})));
|
|
321
568
|
}
|
|
322
569
|
}
|
|
323
|
-
|
|
324
|
-
? { tableLayout: 'fixed' }
|
|
325
|
-
: undefined;
|
|
326
|
-
return (index$1.h(index$1.Host, null, 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) => {
|
|
570
|
+
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) => {
|
|
327
571
|
var _a, _b, _c, _d;
|
|
328
|
-
const
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
572
|
+
const name = this.colName(col);
|
|
573
|
+
const isActive = this.sortField === name;
|
|
574
|
+
const isFiltered = this.activeFilters.has(name);
|
|
575
|
+
const cls = [
|
|
576
|
+
'mrd-table__header',
|
|
577
|
+
'mrd-table__header--sortable',
|
|
578
|
+
isActive ? `mrd-table__header--sorted-${this.sortDir}` : '',
|
|
579
|
+
isFiltered ? 'mrd-table__header--filtered' : '',
|
|
580
|
+
].filter(Boolean).join(' ');
|
|
581
|
+
return (index$1.h("th", { class: cls, style: this.colWidths[idx] ? { width: `${this.colWidths[idx]}px` } : undefined, onClick: () => 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 : ''), index$1.h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, isActive ? (this.sortDir === 'asc' ? '▲' : '▼') : '⇅'), this.filterMode && (index$1.h("button", { class: `mrd-table__header-filter-btn${isFiltered ? ' mrd-table__header-filter-btn--active' : ''}`, onClick: (e) => { e.stopPropagation(); this.handleFilterOpen(col, e); } }, "\u25BE"))));
|
|
582
|
+
}))), 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 })))))), 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()));
|
|
332
583
|
}
|
|
333
584
|
get el() { return index$1.getElement(this); }
|
|
585
|
+
static get watchers() { return {
|
|
586
|
+
"totalElements": [{
|
|
587
|
+
"totalElementsChanged": 0
|
|
588
|
+
}]
|
|
589
|
+
}; }
|
|
334
590
|
};
|
|
335
591
|
MrdTable.style = mrdTableScss();
|
|
336
592
|
|