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