@mmlogic/components 0.5.10 → 0.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/mrd-boolean-field_25.cjs.entry.js +144 -29
- package/dist/collection/components/table/mrd-table/mrd-table.js +144 -29
- package/dist/components/mrd-table2.js +1 -1
- package/dist/esm/mrd-boolean-field_25.entry.js +144 -29
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/{p-7da6790e.entry.js → p-eb322d5c.entry.js} +2 -2
- package/dist/types/components/table/mrd-table/mrd-table.d.ts +39 -0
- package/package.json +1 -1
|
@@ -6641,8 +6641,14 @@ const MrdTable = class {
|
|
|
6641
6641
|
this.keydownHandler = null;
|
|
6642
6642
|
this.createPickerClickHandler = null;
|
|
6643
6643
|
this.viewPopoverClickHandler = null;
|
|
6644
|
+
/** Watches the host for gaining a box (e.g. its tab pane being shown) — see componentDidLoad. */
|
|
6645
|
+
this.resizeObserver = null;
|
|
6644
6646
|
/** Guards against scheduling more than one column-width measurement per freeze. */
|
|
6645
6647
|
this.colWidthMeasureScheduled = false;
|
|
6648
|
+
/** True once the header/totals widths have been checked against the current totals row. */
|
|
6649
|
+
this.fixedContentMeasured = false;
|
|
6650
|
+
/** Guards against scheduling more than one grow pass. */
|
|
6651
|
+
this.growScheduled = false;
|
|
6646
6652
|
/** Drag origin of the running column resize (null = no drag in progress). */
|
|
6647
6653
|
this.resizeOrigin = null;
|
|
6648
6654
|
/** True once the user has dragged (or auto-fitted) a column — see colWidthsUserSet usages. */
|
|
@@ -6737,6 +6743,7 @@ const MrdTable = class {
|
|
|
6737
6743
|
this.colWidths = [];
|
|
6738
6744
|
this.colWidthMeasureScheduled = false;
|
|
6739
6745
|
this.colWidthsUserSet = false;
|
|
6746
|
+
this.fixedContentMeasured = false;
|
|
6740
6747
|
}
|
|
6741
6748
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
6742
6749
|
componentWillLoad() {
|
|
@@ -6745,6 +6752,14 @@ const MrdTable = class {
|
|
|
6745
6752
|
}
|
|
6746
6753
|
componentDidLoad() {
|
|
6747
6754
|
this.staleCheckTimer = setInterval(() => this.checkStalePages(), STALE_CHECK_INTERVAL_MS);
|
|
6755
|
+
// A table inside a hidden tab pane renders and loads page 0 without ever having a box, so
|
|
6756
|
+
// the width freeze cannot measure anything. ResizeObserver fires the moment the pane is
|
|
6757
|
+
// shown (the host gains a box) — that is when the freeze gets its chance. componentDidRender
|
|
6758
|
+
// is no help there: showing a sibling tab does not re-render this component.
|
|
6759
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
6760
|
+
this.resizeObserver = new ResizeObserver(() => this.freezeColWidths());
|
|
6761
|
+
this.resizeObserver.observe(this.el);
|
|
6762
|
+
}
|
|
6748
6763
|
}
|
|
6749
6764
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
6750
6765
|
applyDefaultSort(defaultSort) {
|
|
@@ -6772,6 +6787,7 @@ const MrdTable = class {
|
|
|
6772
6787
|
this.colWidths = [];
|
|
6773
6788
|
this.colWidthMeasureScheduled = false;
|
|
6774
6789
|
this.colWidthsUserSet = false;
|
|
6790
|
+
this.fixedContentMeasured = false;
|
|
6775
6791
|
this.scrollTop = 0;
|
|
6776
6792
|
this.renderStart = 0;
|
|
6777
6793
|
// Always fill the visible viewport on init — totalElements may be stale from a
|
|
@@ -6845,9 +6861,15 @@ const MrdTable = class {
|
|
|
6845
6861
|
if (data.total != null)
|
|
6846
6862
|
this.aggregationsTotal = data.total;
|
|
6847
6863
|
this.aggregations = data;
|
|
6864
|
+
// Fresh totals can be wider than the ones the columns were checked against.
|
|
6865
|
+
this.fixedContentMeasured = false;
|
|
6848
6866
|
}
|
|
6849
6867
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
6850
6868
|
disconnectedCallback() {
|
|
6869
|
+
if (this.resizeObserver) {
|
|
6870
|
+
this.resizeObserver.disconnect();
|
|
6871
|
+
this.resizeObserver = null;
|
|
6872
|
+
}
|
|
6851
6873
|
if (this.staleCheckTimer !== null) {
|
|
6852
6874
|
clearInterval(this.staleCheckTimer);
|
|
6853
6875
|
this.staleCheckTimer = null;
|
|
@@ -6870,29 +6892,36 @@ const MrdTable = class {
|
|
|
6870
6892
|
}
|
|
6871
6893
|
}
|
|
6872
6894
|
componentDidRender() {
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6895
|
+
this.freezeColWidths();
|
|
6896
|
+
this.growToFixedContent();
|
|
6897
|
+
}
|
|
6898
|
+
/** Freeze the column widths once the first data page has rendered.
|
|
6899
|
+
*
|
|
6900
|
+
* The measurement reads layout and writes the colWidths @State, which would trigger a
|
|
6901
|
+
* re-render *during* the render cycle if done synchronously from componentDidRender. It is
|
|
6902
|
+
* deferred to a writeTask so the state mutation happens outside the render pass (avoids the
|
|
6903
|
+
* Stencil "state/prop changed during rendering" warning and potential loops).
|
|
6904
|
+
*
|
|
6905
|
+
* NB: the freeze must not depend on the totalElements prop — hosts may omit it and rely on
|
|
6906
|
+
* minKnownTotal, in which case the widths would never be locked and every incoming page
|
|
6907
|
+
* would re-flow all columns. */
|
|
6908
|
+
freezeColWidths() {
|
|
6909
|
+
if (this.colWidths.length !== 0 || this.colWidthMeasureScheduled || this.loadedPages.size === 0)
|
|
6910
|
+
return;
|
|
6911
|
+
this.colWidthMeasureScheduled = true;
|
|
6912
|
+
index.writeTask(() => {
|
|
6913
|
+
var _a;
|
|
6914
|
+
this.colWidthMeasureScheduled = false;
|
|
6915
|
+
// Re-check: state may have changed (e.g. a reset) between scheduling and running.
|
|
6916
|
+
if (this.colWidths.length !== 0)
|
|
6917
|
+
return;
|
|
6918
|
+
const measured = (_a = this.measureContentWidths()) !== null && _a !== void 0 ? _a : this.measureColWidths();
|
|
6919
|
+
// A table inside a hidden tab pane (a tab page that is loaded but not shown) has no
|
|
6920
|
+
// layout at all, so every measurement comes back 0. Locking those would pin every
|
|
6921
|
+
// column at MIN_COL_WIDTH for good; wait for the ResizeObserver to report a real box.
|
|
6922
|
+
if (measured && measured.some(w => w > 0))
|
|
6923
|
+
this.colWidths = measured;
|
|
6924
|
+
});
|
|
6896
6925
|
}
|
|
6897
6926
|
// ── Column resizing (drag the header border, Excel-style) ──────────────────
|
|
6898
6927
|
/** Current rendered header widths, or null when they can't be trusted (header not laid out yet). */
|
|
@@ -6918,6 +6947,38 @@ const MrdTable = class {
|
|
|
6918
6947
|
const content = scrollWidth > clientWidth ? scrollWidth + paddingRight : clientWidth;
|
|
6919
6948
|
return Math.ceil(content + borders);
|
|
6920
6949
|
}
|
|
6950
|
+
/** Width a header cell needs, measured from its children instead of the cell's own
|
|
6951
|
+
* scrollWidth. A header is not just text: it holds the label plus a sort icon (and a filter
|
|
6952
|
+
* icon once the column is filtered), with the drag handle absolutely positioned on top of
|
|
6953
|
+
* the right padding. Summing the in-flow children makes the icons part of the width by
|
|
6954
|
+
* construction — and a child's own rect is never clipped by the header's `overflow: hidden`,
|
|
6955
|
+
* so it stays correct even when the header is already truncated on screen.
|
|
6956
|
+
* Returns 0 when nothing can be measured, so callers fall back to neededWidth(). */
|
|
6957
|
+
neededHeaderWidth(th) {
|
|
6958
|
+
const px = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
|
|
6959
|
+
const size = (v) => px(parseFloat(v || '0'));
|
|
6960
|
+
const style = getComputedStyle(th);
|
|
6961
|
+
const paddingLeft = size(style.paddingLeft);
|
|
6962
|
+
const paddingRight = size(style.paddingRight);
|
|
6963
|
+
const borders = Math.max(0, px(th.offsetWidth) - px(th.clientWidth));
|
|
6964
|
+
let inflow = 0;
|
|
6965
|
+
let handle = 0;
|
|
6966
|
+
Array.from(th.children).forEach(child => {
|
|
6967
|
+
const el = child;
|
|
6968
|
+
const cs = getComputedStyle(el);
|
|
6969
|
+
const width = px(el.getBoundingClientRect().width) + size(cs.marginLeft) + size(cs.marginRight);
|
|
6970
|
+
// The drag handle is absolutely positioned: it takes no room in the flow, it overlays it.
|
|
6971
|
+
if (cs.position === 'absolute')
|
|
6972
|
+
handle = Math.max(handle, width);
|
|
6973
|
+
else
|
|
6974
|
+
inflow += width;
|
|
6975
|
+
});
|
|
6976
|
+
if (inflow <= 0)
|
|
6977
|
+
return 0;
|
|
6978
|
+
// The handle sits over the right padding; only reserve what would stick out past it,
|
|
6979
|
+
// so the label never ends up underneath the grab area.
|
|
6980
|
+
return Math.ceil(inflow + paddingLeft + paddingRight + Math.max(0, handle - paddingRight) + borders);
|
|
6981
|
+
}
|
|
6921
6982
|
/** Width each column needs for its header and its rendered page-0 cells — what the columns
|
|
6922
6983
|
* are locked to, so they never end up narrower than their content.
|
|
6923
6984
|
*
|
|
@@ -6930,14 +6991,63 @@ const MrdTable = class {
|
|
|
6930
6991
|
const ths = this.el.querySelectorAll('.mrd-table__header');
|
|
6931
6992
|
if (ths.length === 0 || ths.length !== this.columns.length)
|
|
6932
6993
|
return null;
|
|
6933
|
-
|
|
6934
|
-
let widest = this.neededWidth(th);
|
|
6994
|
+
const needed = Array.from(ths).map((th, idx) => {
|
|
6995
|
+
let widest = Math.max(this.neededWidth(th), this.neededHeaderWidth(th));
|
|
6935
6996
|
// .mrd-table__cell only matches real data cells — the shimmer/retry rows render a single
|
|
6936
6997
|
// colSpan cell (without that class) which would measure as the full table width.
|
|
6937
6998
|
this.el
|
|
6938
|
-
.querySelectorAll(
|
|
6999
|
+
.querySelectorAll(this.columnCellSelector(idx))
|
|
6939
7000
|
.forEach(cell => { widest = Math.max(widest, this.neededWidth(cell)); });
|
|
6940
|
-
return
|
|
7001
|
+
return widest;
|
|
7002
|
+
});
|
|
7003
|
+
// Nothing has a box: the table is in a hidden tab pane. Report "cannot measure" rather
|
|
7004
|
+
// than clamping a row of zeroes up to MIN_COL_WIDTH, which would look like a real result.
|
|
7005
|
+
if (needed.every(w => w <= 0))
|
|
7006
|
+
return null;
|
|
7007
|
+
return needed.map(w => Math.min(Math.max(w, MIN_COL_WIDTH), MAX_AUTOFIT_WIDTH));
|
|
7008
|
+
}
|
|
7009
|
+
/** Data and totals cells of one column. The totals row is included everywhere the data
|
|
7010
|
+
* cells are: it is fixed, always-visible content, so it must never be clipped. */
|
|
7011
|
+
columnCellSelector(idx) {
|
|
7012
|
+
return `.mrd-table__cell:nth-child(${idx + 1}), .mrd-table__totals-cell:nth-child(${idx + 1})`;
|
|
7013
|
+
}
|
|
7014
|
+
/** Width the header and totals cells of each column need. Null when nothing has a box. */
|
|
7015
|
+
measureFixedContentWidths() {
|
|
7016
|
+
const ths = this.el.querySelectorAll('.mrd-table__header');
|
|
7017
|
+
if (ths.length === 0 || ths.length !== this.columns.length)
|
|
7018
|
+
return null;
|
|
7019
|
+
const needed = Array.from(ths).map((th, idx) => {
|
|
7020
|
+
let widest = Math.max(this.neededWidth(th), this.neededHeaderWidth(th));
|
|
7021
|
+
this.el
|
|
7022
|
+
.querySelectorAll(`.mrd-table__totals-cell:nth-child(${idx + 1})`)
|
|
7023
|
+
.forEach(cell => { widest = Math.max(widest, this.neededWidth(cell)); });
|
|
7024
|
+
return widest;
|
|
7025
|
+
});
|
|
7026
|
+
return needed.every(w => w <= 0) ? null : needed;
|
|
7027
|
+
}
|
|
7028
|
+
/** Widen columns whose header or totals cell does not fit. Data cells may be ellipsised —
|
|
7029
|
+
* that is what the lock is for — but the header and the totals row are fixed content that
|
|
7030
|
+
* is always on screen, so clipping them just hides information with no way to reveal it.
|
|
7031
|
+
*
|
|
7032
|
+
* It runs as a second pass because the totals only arrive with setAggregations(), long
|
|
7033
|
+
* after the widths were frozen on page 0. Grow-only and once per totals change, so it can
|
|
7034
|
+
* never turn into the per-page re-proportioning the lock exists to prevent. Hand-set
|
|
7035
|
+
* widths are left alone. */
|
|
7036
|
+
growToFixedContent() {
|
|
7037
|
+
if (this.colWidths.length === 0 || this.colWidthsUserSet || this.fixedContentMeasured || this.growScheduled)
|
|
7038
|
+
return;
|
|
7039
|
+
this.growScheduled = true;
|
|
7040
|
+
index.writeTask(() => {
|
|
7041
|
+
this.growScheduled = false;
|
|
7042
|
+
if (this.colWidths.length === 0 || this.colWidthsUserSet)
|
|
7043
|
+
return;
|
|
7044
|
+
const needed = this.measureFixedContentWidths();
|
|
7045
|
+
if (!needed)
|
|
7046
|
+
return;
|
|
7047
|
+
this.fixedContentMeasured = true;
|
|
7048
|
+
const grown = this.colWidths.map((w, i) => Math.min(Math.max(w, needed[i]), MAX_AUTOFIT_WIDTH));
|
|
7049
|
+
if (grown.some((w, i) => w !== this.colWidths[i]))
|
|
7050
|
+
this.colWidths = grown;
|
|
6941
7051
|
});
|
|
6942
7052
|
}
|
|
6943
7053
|
/** Re-reads the rendered header widths into colWidths so a drag starts from what is
|
|
@@ -6998,9 +7108,14 @@ const MrdTable = class {
|
|
|
6998
7108
|
return;
|
|
6999
7109
|
// .mrd-table__cell only matches real data cells — the shimmer/retry rows render a single
|
|
7000
7110
|
// colSpan cell (without that class) which would otherwise measure as the full table width.
|
|
7001
|
-
const cells = this.el.querySelectorAll(`.mrd-table__header:nth-child(${idx + 1}), .
|
|
7111
|
+
const cells = this.el.querySelectorAll(`.mrd-table__header:nth-child(${idx + 1}), ${this.columnCellSelector(idx)}`);
|
|
7002
7112
|
let widest = 0;
|
|
7003
|
-
cells.forEach(cell => {
|
|
7113
|
+
cells.forEach(cell => {
|
|
7114
|
+
const needed = cell.classList.contains('mrd-table__header')
|
|
7115
|
+
? Math.max(this.neededWidth(cell), this.neededHeaderWidth(cell))
|
|
7116
|
+
: this.neededWidth(cell);
|
|
7117
|
+
widest = Math.max(widest, needed);
|
|
7118
|
+
});
|
|
7004
7119
|
if (widest > 0)
|
|
7005
7120
|
this.setColWidth(idx, Math.min(widest, MAX_AUTOFIT_WIDTH));
|
|
7006
7121
|
}
|
|
@@ -27,8 +27,14 @@ export class MrdTable {
|
|
|
27
27
|
this.keydownHandler = null;
|
|
28
28
|
this.createPickerClickHandler = null;
|
|
29
29
|
this.viewPopoverClickHandler = null;
|
|
30
|
+
/** Watches the host for gaining a box (e.g. its tab pane being shown) — see componentDidLoad. */
|
|
31
|
+
this.resizeObserver = null;
|
|
30
32
|
/** Guards against scheduling more than one column-width measurement per freeze. */
|
|
31
33
|
this.colWidthMeasureScheduled = false;
|
|
34
|
+
/** True once the header/totals widths have been checked against the current totals row. */
|
|
35
|
+
this.fixedContentMeasured = false;
|
|
36
|
+
/** Guards against scheduling more than one grow pass. */
|
|
37
|
+
this.growScheduled = false;
|
|
32
38
|
/** Drag origin of the running column resize (null = no drag in progress). */
|
|
33
39
|
this.resizeOrigin = null;
|
|
34
40
|
/** True once the user has dragged (or auto-fitted) a column — see colWidthsUserSet usages. */
|
|
@@ -123,6 +129,7 @@ export class MrdTable {
|
|
|
123
129
|
this.colWidths = [];
|
|
124
130
|
this.colWidthMeasureScheduled = false;
|
|
125
131
|
this.colWidthsUserSet = false;
|
|
132
|
+
this.fixedContentMeasured = false;
|
|
126
133
|
}
|
|
127
134
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
128
135
|
componentWillLoad() {
|
|
@@ -131,6 +138,14 @@ export class MrdTable {
|
|
|
131
138
|
}
|
|
132
139
|
componentDidLoad() {
|
|
133
140
|
this.staleCheckTimer = setInterval(() => this.checkStalePages(), STALE_CHECK_INTERVAL_MS);
|
|
141
|
+
// A table inside a hidden tab pane renders and loads page 0 without ever having a box, so
|
|
142
|
+
// the width freeze cannot measure anything. ResizeObserver fires the moment the pane is
|
|
143
|
+
// shown (the host gains a box) — that is when the freeze gets its chance. componentDidRender
|
|
144
|
+
// is no help there: showing a sibling tab does not re-render this component.
|
|
145
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
146
|
+
this.resizeObserver = new ResizeObserver(() => this.freezeColWidths());
|
|
147
|
+
this.resizeObserver.observe(this.el);
|
|
148
|
+
}
|
|
134
149
|
}
|
|
135
150
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
136
151
|
applyDefaultSort(defaultSort) {
|
|
@@ -158,6 +173,7 @@ export class MrdTable {
|
|
|
158
173
|
this.colWidths = [];
|
|
159
174
|
this.colWidthMeasureScheduled = false;
|
|
160
175
|
this.colWidthsUserSet = false;
|
|
176
|
+
this.fixedContentMeasured = false;
|
|
161
177
|
this.scrollTop = 0;
|
|
162
178
|
this.renderStart = 0;
|
|
163
179
|
// Always fill the visible viewport on init — totalElements may be stale from a
|
|
@@ -231,9 +247,15 @@ export class MrdTable {
|
|
|
231
247
|
if (data.total != null)
|
|
232
248
|
this.aggregationsTotal = data.total;
|
|
233
249
|
this.aggregations = data;
|
|
250
|
+
// Fresh totals can be wider than the ones the columns were checked against.
|
|
251
|
+
this.fixedContentMeasured = false;
|
|
234
252
|
}
|
|
235
253
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
236
254
|
disconnectedCallback() {
|
|
255
|
+
if (this.resizeObserver) {
|
|
256
|
+
this.resizeObserver.disconnect();
|
|
257
|
+
this.resizeObserver = null;
|
|
258
|
+
}
|
|
237
259
|
if (this.staleCheckTimer !== null) {
|
|
238
260
|
clearInterval(this.staleCheckTimer);
|
|
239
261
|
this.staleCheckTimer = null;
|
|
@@ -256,29 +278,36 @@ export class MrdTable {
|
|
|
256
278
|
}
|
|
257
279
|
}
|
|
258
280
|
componentDidRender() {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
281
|
+
this.freezeColWidths();
|
|
282
|
+
this.growToFixedContent();
|
|
283
|
+
}
|
|
284
|
+
/** Freeze the column widths once the first data page has rendered.
|
|
285
|
+
*
|
|
286
|
+
* The measurement reads layout and writes the colWidths @State, which would trigger a
|
|
287
|
+
* re-render *during* the render cycle if done synchronously from componentDidRender. It is
|
|
288
|
+
* deferred to a writeTask so the state mutation happens outside the render pass (avoids the
|
|
289
|
+
* Stencil "state/prop changed during rendering" warning and potential loops).
|
|
290
|
+
*
|
|
291
|
+
* NB: the freeze must not depend on the totalElements prop — hosts may omit it and rely on
|
|
292
|
+
* minKnownTotal, in which case the widths would never be locked and every incoming page
|
|
293
|
+
* would re-flow all columns. */
|
|
294
|
+
freezeColWidths() {
|
|
295
|
+
if (this.colWidths.length !== 0 || this.colWidthMeasureScheduled || this.loadedPages.size === 0)
|
|
296
|
+
return;
|
|
297
|
+
this.colWidthMeasureScheduled = true;
|
|
298
|
+
writeTask(() => {
|
|
299
|
+
var _a;
|
|
300
|
+
this.colWidthMeasureScheduled = false;
|
|
301
|
+
// Re-check: state may have changed (e.g. a reset) between scheduling and running.
|
|
302
|
+
if (this.colWidths.length !== 0)
|
|
303
|
+
return;
|
|
304
|
+
const measured = (_a = this.measureContentWidths()) !== null && _a !== void 0 ? _a : this.measureColWidths();
|
|
305
|
+
// A table inside a hidden tab pane (a tab page that is loaded but not shown) has no
|
|
306
|
+
// layout at all, so every measurement comes back 0. Locking those would pin every
|
|
307
|
+
// column at MIN_COL_WIDTH for good; wait for the ResizeObserver to report a real box.
|
|
308
|
+
if (measured && measured.some(w => w > 0))
|
|
309
|
+
this.colWidths = measured;
|
|
310
|
+
});
|
|
282
311
|
}
|
|
283
312
|
// ── Column resizing (drag the header border, Excel-style) ──────────────────
|
|
284
313
|
/** Current rendered header widths, or null when they can't be trusted (header not laid out yet). */
|
|
@@ -304,6 +333,38 @@ export class MrdTable {
|
|
|
304
333
|
const content = scrollWidth > clientWidth ? scrollWidth + paddingRight : clientWidth;
|
|
305
334
|
return Math.ceil(content + borders);
|
|
306
335
|
}
|
|
336
|
+
/** Width a header cell needs, measured from its children instead of the cell's own
|
|
337
|
+
* scrollWidth. A header is not just text: it holds the label plus a sort icon (and a filter
|
|
338
|
+
* icon once the column is filtered), with the drag handle absolutely positioned on top of
|
|
339
|
+
* the right padding. Summing the in-flow children makes the icons part of the width by
|
|
340
|
+
* construction — and a child's own rect is never clipped by the header's `overflow: hidden`,
|
|
341
|
+
* so it stays correct even when the header is already truncated on screen.
|
|
342
|
+
* Returns 0 when nothing can be measured, so callers fall back to neededWidth(). */
|
|
343
|
+
neededHeaderWidth(th) {
|
|
344
|
+
const px = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
|
|
345
|
+
const size = (v) => px(parseFloat(v || '0'));
|
|
346
|
+
const style = getComputedStyle(th);
|
|
347
|
+
const paddingLeft = size(style.paddingLeft);
|
|
348
|
+
const paddingRight = size(style.paddingRight);
|
|
349
|
+
const borders = Math.max(0, px(th.offsetWidth) - px(th.clientWidth));
|
|
350
|
+
let inflow = 0;
|
|
351
|
+
let handle = 0;
|
|
352
|
+
Array.from(th.children).forEach(child => {
|
|
353
|
+
const el = child;
|
|
354
|
+
const cs = getComputedStyle(el);
|
|
355
|
+
const width = px(el.getBoundingClientRect().width) + size(cs.marginLeft) + size(cs.marginRight);
|
|
356
|
+
// The drag handle is absolutely positioned: it takes no room in the flow, it overlays it.
|
|
357
|
+
if (cs.position === 'absolute')
|
|
358
|
+
handle = Math.max(handle, width);
|
|
359
|
+
else
|
|
360
|
+
inflow += width;
|
|
361
|
+
});
|
|
362
|
+
if (inflow <= 0)
|
|
363
|
+
return 0;
|
|
364
|
+
// The handle sits over the right padding; only reserve what would stick out past it,
|
|
365
|
+
// so the label never ends up underneath the grab area.
|
|
366
|
+
return Math.ceil(inflow + paddingLeft + paddingRight + Math.max(0, handle - paddingRight) + borders);
|
|
367
|
+
}
|
|
307
368
|
/** Width each column needs for its header and its rendered page-0 cells — what the columns
|
|
308
369
|
* are locked to, so they never end up narrower than their content.
|
|
309
370
|
*
|
|
@@ -316,14 +377,63 @@ export class MrdTable {
|
|
|
316
377
|
const ths = this.el.querySelectorAll('.mrd-table__header');
|
|
317
378
|
if (ths.length === 0 || ths.length !== this.columns.length)
|
|
318
379
|
return null;
|
|
319
|
-
|
|
320
|
-
let widest = this.neededWidth(th);
|
|
380
|
+
const needed = Array.from(ths).map((th, idx) => {
|
|
381
|
+
let widest = Math.max(this.neededWidth(th), this.neededHeaderWidth(th));
|
|
321
382
|
// .mrd-table__cell only matches real data cells — the shimmer/retry rows render a single
|
|
322
383
|
// colSpan cell (without that class) which would measure as the full table width.
|
|
323
384
|
this.el
|
|
324
|
-
.querySelectorAll(
|
|
385
|
+
.querySelectorAll(this.columnCellSelector(idx))
|
|
325
386
|
.forEach(cell => { widest = Math.max(widest, this.neededWidth(cell)); });
|
|
326
|
-
return
|
|
387
|
+
return widest;
|
|
388
|
+
});
|
|
389
|
+
// Nothing has a box: the table is in a hidden tab pane. Report "cannot measure" rather
|
|
390
|
+
// than clamping a row of zeroes up to MIN_COL_WIDTH, which would look like a real result.
|
|
391
|
+
if (needed.every(w => w <= 0))
|
|
392
|
+
return null;
|
|
393
|
+
return needed.map(w => Math.min(Math.max(w, MIN_COL_WIDTH), MAX_AUTOFIT_WIDTH));
|
|
394
|
+
}
|
|
395
|
+
/** Data and totals cells of one column. The totals row is included everywhere the data
|
|
396
|
+
* cells are: it is fixed, always-visible content, so it must never be clipped. */
|
|
397
|
+
columnCellSelector(idx) {
|
|
398
|
+
return `.mrd-table__cell:nth-child(${idx + 1}), .mrd-table__totals-cell:nth-child(${idx + 1})`;
|
|
399
|
+
}
|
|
400
|
+
/** Width the header and totals cells of each column need. Null when nothing has a box. */
|
|
401
|
+
measureFixedContentWidths() {
|
|
402
|
+
const ths = this.el.querySelectorAll('.mrd-table__header');
|
|
403
|
+
if (ths.length === 0 || ths.length !== this.columns.length)
|
|
404
|
+
return null;
|
|
405
|
+
const needed = Array.from(ths).map((th, idx) => {
|
|
406
|
+
let widest = Math.max(this.neededWidth(th), this.neededHeaderWidth(th));
|
|
407
|
+
this.el
|
|
408
|
+
.querySelectorAll(`.mrd-table__totals-cell:nth-child(${idx + 1})`)
|
|
409
|
+
.forEach(cell => { widest = Math.max(widest, this.neededWidth(cell)); });
|
|
410
|
+
return widest;
|
|
411
|
+
});
|
|
412
|
+
return needed.every(w => w <= 0) ? null : needed;
|
|
413
|
+
}
|
|
414
|
+
/** Widen columns whose header or totals cell does not fit. Data cells may be ellipsised —
|
|
415
|
+
* that is what the lock is for — but the header and the totals row are fixed content that
|
|
416
|
+
* is always on screen, so clipping them just hides information with no way to reveal it.
|
|
417
|
+
*
|
|
418
|
+
* It runs as a second pass because the totals only arrive with setAggregations(), long
|
|
419
|
+
* after the widths were frozen on page 0. Grow-only and once per totals change, so it can
|
|
420
|
+
* never turn into the per-page re-proportioning the lock exists to prevent. Hand-set
|
|
421
|
+
* widths are left alone. */
|
|
422
|
+
growToFixedContent() {
|
|
423
|
+
if (this.colWidths.length === 0 || this.colWidthsUserSet || this.fixedContentMeasured || this.growScheduled)
|
|
424
|
+
return;
|
|
425
|
+
this.growScheduled = true;
|
|
426
|
+
writeTask(() => {
|
|
427
|
+
this.growScheduled = false;
|
|
428
|
+
if (this.colWidths.length === 0 || this.colWidthsUserSet)
|
|
429
|
+
return;
|
|
430
|
+
const needed = this.measureFixedContentWidths();
|
|
431
|
+
if (!needed)
|
|
432
|
+
return;
|
|
433
|
+
this.fixedContentMeasured = true;
|
|
434
|
+
const grown = this.colWidths.map((w, i) => Math.min(Math.max(w, needed[i]), MAX_AUTOFIT_WIDTH));
|
|
435
|
+
if (grown.some((w, i) => w !== this.colWidths[i]))
|
|
436
|
+
this.colWidths = grown;
|
|
327
437
|
});
|
|
328
438
|
}
|
|
329
439
|
/** Re-reads the rendered header widths into colWidths so a drag starts from what is
|
|
@@ -384,9 +494,14 @@ export class MrdTable {
|
|
|
384
494
|
return;
|
|
385
495
|
// .mrd-table__cell only matches real data cells — the shimmer/retry rows render a single
|
|
386
496
|
// colSpan cell (without that class) which would otherwise measure as the full table width.
|
|
387
|
-
const cells = this.el.querySelectorAll(`.mrd-table__header:nth-child(${idx + 1}), .
|
|
497
|
+
const cells = this.el.querySelectorAll(`.mrd-table__header:nth-child(${idx + 1}), ${this.columnCellSelector(idx)}`);
|
|
388
498
|
let widest = 0;
|
|
389
|
-
cells.forEach(cell => {
|
|
499
|
+
cells.forEach(cell => {
|
|
500
|
+
const needed = cell.classList.contains('mrd-table__header')
|
|
501
|
+
? Math.max(this.neededWidth(cell), this.neededHeaderWidth(cell))
|
|
502
|
+
: this.neededWidth(cell);
|
|
503
|
+
widest = Math.max(widest, needed);
|
|
504
|
+
});
|
|
390
505
|
if (widest > 0)
|
|
391
506
|
this.setColWidth(idx, Math.min(widest, MAX_AUTOFIT_WIDTH));
|
|
392
507
|
}
|