@mmlogic/components 0.5.8 → 0.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/cjs/mrd-boolean-field_25.cjs.entry.js +62 -12
  2. package/dist/collection/components/fields/mrd-currency-field/mrd-currency-field.js +23 -12
  3. package/dist/collection/components/form/mrd-field/mrd-field.js +6 -1
  4. package/dist/collection/components/table/mrd-table/mrd-table.js +41 -7
  5. package/dist/components/mrd-currency-field2.js +1 -1
  6. package/dist/components/mrd-table2.js +1 -1
  7. package/dist/esm/mrd-boolean-field_25.entry.js +62 -12
  8. package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
  9. package/dist/mosterdcomponents/{p-6f6c8e53.entry.js → p-7da6790e.entry.js} +2 -2
  10. package/dist/types/components/fields/mrd-currency-field/mrd-currency-field.d.ts +11 -3
  11. package/dist/types/components/table/mrd-table/mrd-table.d.ts +13 -0
  12. package/dist/types/components.d.ts +10 -8
  13. package/package.json +1 -1
  14. package/dist/mosterdcomponents/cell-renderer-B8u5BCK-.js.map +0 -1
  15. package/dist/mosterdcomponents/client-layout-Bc42CfJB.js.map +0 -1
  16. package/dist/mosterdcomponents/document-attachments-BufPmMRv.js.map +0 -1
  17. package/dist/mosterdcomponents/field-helpers-CUJsHr1V.js.map +0 -1
  18. package/dist/mosterdcomponents/file-upload-common-DdTDuxEU.js.map +0 -1
  19. package/dist/mosterdcomponents/format-gcecItcD.js.map +0 -1
  20. package/dist/mosterdcomponents/i18n-Ba7_jO9I.js.map +0 -1
  21. package/dist/mosterdcomponents/index-1ayBojyN.js.map +0 -1
  22. package/dist/mosterdcomponents/index-DhffneUF.js.map +0 -1
  23. package/dist/mosterdcomponents/index.esm.js.map +0 -1
  24. package/dist/mosterdcomponents/mosterdcomponents.esm.js.map +0 -1
  25. package/dist/mosterdcomponents/purify.es-_duHfPgC.js.map +0 -1
  26. package/dist/mosterdcomponents/query-params-BZbQoeYF.js.map +0 -1
  27. package/dist/mosterdcomponents/quill-C9pgw_k-.js.map +0 -1
  28. package/dist/mosterdcomponents/validation-CjjTt66G.js.map +0 -1
@@ -114,7 +114,10 @@ const MrdCurrencyField = class {
114
114
  this.mrdBlur = index.createEvent(this, "mrdBlur");
115
115
  this.name = '';
116
116
  this.label = '';
117
- this.value = { amount: null, currency: 'EUR' };
117
+ /** Accepts a partial object on the way in (hosts pre-fill the currency from the layout's
118
+ * currencyCode before an amount exists); on the way out only null or a complete object
119
+ * is ever emitted — see handleAmountBlur. */
120
+ this.value = null;
118
121
  this.required = false;
119
122
  this.disabled = false;
120
123
  this.locale = navigator.language;
@@ -141,14 +144,15 @@ const MrdCurrencyField = class {
141
144
  const parsed = cellRenderer.parseLocalizedNumber(raw, this.locale);
142
145
  this.error = requiredError(raw, this.required, this.locale);
143
146
  this.amountDisplay = parsed !== null ? this.formatAmount(parsed) : '';
144
- const val = { amount: parsed, currency: this.currency };
147
+ const val = this.currentValue(parsed);
145
148
  this.mrdChange.emit({ name: this.name, value: val });
146
149
  this.mrdBlur.emit({ name: this.name, value: val });
147
150
  };
148
151
  this.handleCurrencyChange = (e) => {
149
152
  this.currency = e.target.value;
150
153
  const parsed = cellRenderer.parseLocalizedNumber(this.amountDisplay, this.locale);
151
- this.mrdChange.emit({ name: this.name, value: { amount: parsed, currency: this.currency } });
154
+ // Picking a currency for an empty amount does not make the field filled in.
155
+ this.mrdChange.emit({ name: this.name, value: this.currentValue(parsed) });
152
156
  };
153
157
  }
154
158
  componentWillLoad() {
@@ -186,9 +190,16 @@ const MrdCurrencyField = class {
186
190
  formatAmount(amount) {
187
191
  return cellRenderer.formatNumber(amount, this.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
188
192
  }
193
+ /** Emit either a complete value or nothing at all. A half-filled
194
+ * { amount: null, currency } reaches the API as a currency object without an amount,
195
+ * which it cannot store — and it makes clearing an optional amount impossible, because
196
+ * the field would never report itself as empty (FINDING-0141 / TASK-0234). */
197
+ currentValue(amount) {
198
+ return amount === null ? null : { amount, currency: this.currency };
199
+ }
189
200
  render() {
190
201
  const hasError = !!this.error;
191
- return (index.h(index.Host, { key: '43ef1fe660c4eaef025203544916b077975b6aa3' }, index.h("div", { key: '00b8e79182c8e2a353d57ad5c341024caf00d4ea', class: "mrd-currency-field" }, index.h(FieldLabel, { key: '96835c311e0d490864c484b818f1dcc623f488b2', base: "mrd-currency-field", label: this.label, required: this.required }), index.h("div", { key: '80317cfe562af00cc4cbf9aabf4bac2126cf6fe5', class: "mrd-currency-field__row" }, index.h("input", { key: '1983ef1e4d19e0fc529cf5e8be7d287fea36620a', class: controlClass('mrd-currency-field', hasError, 'amount'), type: "text", inputMode: "decimal", name: `${this.name}_amount`, value: this.amountDisplay, placeholder: this.formatAmount(0), required: this.required, disabled: this.disabled, onInput: this.handleAmountInput, onFocus: this.handleAmountFocus, onBlur: this.handleAmountBlur }), index.h("select", { key: 'b5fe3f03590bebaa564d76bc5fcb47c327eacece', class: "mrd-currency-field__currency", ref: el => (this.selectEl = el), disabled: this.disabled, onChange: this.handleCurrencyChange }, this.currencyOptions().map(c => (index.h("option", { key: c, value: c, selected: c === this.currency }, c))))), index.h(FieldError, { key: '1a128b26293170d4137f787622d4e89a23e62c0b', base: "mrd-currency-field", error: this.error }))));
202
+ return (index.h(index.Host, { key: '9529c8ff013107056ea549f70f10016dec00cff6' }, index.h("div", { key: 'eef1f1400cc9b1f87353413f3f476b028bf3ecbf', class: "mrd-currency-field" }, index.h(FieldLabel, { key: '76004f615bda7669814d4b5f55c4ec54ae078e12', base: "mrd-currency-field", label: this.label, required: this.required }), index.h("div", { key: '03a2ec6ada9b2e185e247a228225aed3b67a5bec', class: "mrd-currency-field__row" }, index.h("input", { key: 'ee9004f4cc306c8a9f417812b573e000c055c09c', class: controlClass('mrd-currency-field', hasError, 'amount'), type: "text", inputMode: "decimal", name: `${this.name}_amount`, value: this.amountDisplay, placeholder: this.formatAmount(0), required: this.required, disabled: this.disabled, onInput: this.handleAmountInput, onFocus: this.handleAmountFocus, onBlur: this.handleAmountBlur }), index.h("select", { key: '06547e3716881b3c574c6355eef56a92983470bb', class: "mrd-currency-field__currency", ref: el => (this.selectEl = el), disabled: this.disabled, onChange: this.handleCurrencyChange }, this.currencyOptions().map(c => (index.h("option", { key: c, value: c, selected: c === this.currency }, c))))), index.h(FieldError, { key: '4cbab3a887fece60ae31baccb24e3b68ecf1b947', base: "mrd-currency-field", error: this.error }))));
192
203
  }
193
204
  static get watchers() { return {
194
205
  "value": [{
@@ -4165,7 +4176,12 @@ const MrdField = class {
4165
4176
  case clientLayout.ClientLayoutItemFieldDataType.PERCENTAGE:
4166
4177
  return (index.h("mrd-number-field", Object.assign({}, commonProps, { value: (_f = displayValue) !== null && _f !== void 0 ? _f : null, dataType: item.dataType, decimalPrecision: (_g = item.decimalPrecision) !== null && _g !== void 0 ? _g : 2, placeholder: (_h = item.placeholder) !== null && _h !== void 0 ? _h : '' })));
4167
4178
  case clientLayout.ClientLayoutItemFieldDataType.CURRENCY:
4168
- return (index.h("mrd-currency-field", Object.assign({}, commonProps, { value: (_j = displayValue) !== null && _j !== void 0 ? _j : { amount: null, currency: (_k = item.currencyCode) !== null && _k !== void 0 ? _k : 'EUR' } })));
4179
+ return (index.h("mrd-currency-field", Object.assign({}, commonProps, {
4180
+ // Input-only fallback: it preselects the layout's currencyCode in the dropdown
4181
+ // before an amount exists. The field never emits this shape back — an empty
4182
+ // amount emits null (FINDING-0141).
4183
+ value: (_j = displayValue) !== null && _j !== void 0 ? _j : { amount: null, currency: (_k = item.currencyCode) !== null && _k !== void 0 ? _k : 'EUR' }
4184
+ })));
4169
4185
  case clientLayout.ClientLayoutItemFieldDataType.BOOLEAN:
4170
4186
  return (index.h("mrd-boolean-field", Object.assign({}, commonProps, { value: (_l = displayValue) !== null && _l !== void 0 ? _l : false })));
4171
4187
  case clientLayout.ClientLayoutItemFieldDataType.DATE:
@@ -6867,11 +6883,12 @@ const MrdTable = class {
6867
6883
  this.loadedPages.size > 0) {
6868
6884
  this.colWidthMeasureScheduled = true;
6869
6885
  index.writeTask(() => {
6886
+ var _a;
6870
6887
  this.colWidthMeasureScheduled = false;
6871
6888
  // Re-check: state may have changed (e.g. a reset) between scheduling and running.
6872
6889
  if (this.colWidths.length !== 0)
6873
6890
  return;
6874
- const measured = this.measureColWidths();
6891
+ const measured = (_a = this.measureContentWidths()) !== null && _a !== void 0 ? _a : this.measureColWidths();
6875
6892
  if (measured)
6876
6893
  this.colWidths = measured;
6877
6894
  });
@@ -6885,6 +6902,44 @@ const MrdTable = class {
6885
6902
  return null;
6886
6903
  return Array.from(ths).map(th => Math.round(th.getBoundingClientRect().width));
6887
6904
  }
6905
+ /** Width one cell needs to show its content in full, borders included.
6906
+ * Read from scrollWidth rather than the laid-out box: cells clip with `overflow: hidden`,
6907
+ * so a squeezed cell reports the width it *was given*, not the width it needs. */
6908
+ neededWidth(cell) {
6909
+ // The layout metrics are absent in the mock DOM the specs run on; treat them as 0
6910
+ // rather than letting a NaN poison the Math.max chain the callers build.
6911
+ const px = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
6912
+ const clientWidth = px(cell.clientWidth);
6913
+ const scrollWidth = px(cell.scrollWidth);
6914
+ const paddingRight = px(parseFloat(getComputedStyle(cell).paddingRight || '0'));
6915
+ const borders = Math.max(0, px(cell.offsetWidth) - clientWidth);
6916
+ // scrollWidth drops the right padding once the content overflows the clipped box;
6917
+ // when it fits, clientWidth is already the full padded box.
6918
+ const content = scrollWidth > clientWidth ? scrollWidth + paddingRight : clientWidth;
6919
+ return Math.ceil(content + borders);
6920
+ }
6921
+ /** Width each column needs for its header and its rendered page-0 cells — what the columns
6922
+ * are locked to, so they never end up narrower than their content.
6923
+ *
6924
+ * Deliberately not the laid-out widths: with many columns the browser squeezes them to fit
6925
+ * the container instead of overflowing (the cells clip, so they no longer resist), which
6926
+ * cost the horizontal scrollbar. Locking the needed widths makes the summed table width
6927
+ * exceed the container again, and `.mrd-table__scroll` scrolls. Capped per column at
6928
+ * MAX_AUTOFIT_WIDTH so one outlier value can't blow up the layout — same cap as auto-fit. */
6929
+ measureContentWidths() {
6930
+ const ths = this.el.querySelectorAll('.mrd-table__header');
6931
+ if (ths.length === 0 || ths.length !== this.columns.length)
6932
+ return null;
6933
+ return Array.from(ths).map((th, idx) => {
6934
+ let widest = this.neededWidth(th);
6935
+ // .mrd-table__cell only matches real data cells — the shimmer/retry rows render a single
6936
+ // colSpan cell (without that class) which would measure as the full table width.
6937
+ this.el
6938
+ .querySelectorAll(`.mrd-table__cell:nth-child(${idx + 1})`)
6939
+ .forEach(cell => { widest = Math.max(widest, this.neededWidth(cell)); });
6940
+ return Math.min(Math.max(widest, MIN_COL_WIDTH), MAX_AUTOFIT_WIDTH);
6941
+ });
6942
+ }
6888
6943
  /** Re-reads the rendered header widths into colWidths so a drag starts from what is
6889
6944
  * actually on screen — and locks the columns if they weren't locked yet.
6890
6945
  *
@@ -6945,12 +7000,7 @@ const MrdTable = class {
6945
7000
  // colSpan cell (without that class) which would otherwise measure as the full table width.
6946
7001
  const cells = this.el.querySelectorAll(`.mrd-table__header:nth-child(${idx + 1}), .mrd-table__cell:nth-child(${idx + 1})`);
6947
7002
  let widest = 0;
6948
- cells.forEach(cell => {
6949
- // scrollWidth is the untruncated content width; cells clip with overflow: hidden.
6950
- const style = getComputedStyle(cell);
6951
- const padding = parseFloat(style.paddingLeft || '0') + parseFloat(style.paddingRight || '0');
6952
- widest = Math.max(widest, cell.scrollWidth + padding);
6953
- });
7003
+ cells.forEach(cell => { widest = Math.max(widest, this.neededWidth(cell)); });
6954
7004
  if (widest > 0)
6955
7005
  this.setColWidth(idx, Math.min(widest, MAX_AUTOFIT_WIDTH));
6956
7006
  }
@@ -10,7 +10,10 @@ export class MrdCurrencyField {
10
10
  constructor() {
11
11
  this.name = '';
12
12
  this.label = '';
13
- this.value = { amount: null, currency: 'EUR' };
13
+ /** Accepts a partial object on the way in (hosts pre-fill the currency from the layout's
14
+ * currencyCode before an amount exists); on the way out only null or a complete object
15
+ * is ever emitted — see handleAmountBlur. */
16
+ this.value = null;
14
17
  this.required = false;
15
18
  this.disabled = false;
16
19
  this.locale = navigator.language;
@@ -37,14 +40,15 @@ export class MrdCurrencyField {
37
40
  const parsed = parseLocalizedNumber(raw, this.locale);
38
41
  this.error = requiredError(raw, this.required, this.locale);
39
42
  this.amountDisplay = parsed !== null ? this.formatAmount(parsed) : '';
40
- const val = { amount: parsed, currency: this.currency };
43
+ const val = this.currentValue(parsed);
41
44
  this.mrdChange.emit({ name: this.name, value: val });
42
45
  this.mrdBlur.emit({ name: this.name, value: val });
43
46
  };
44
47
  this.handleCurrencyChange = (e) => {
45
48
  this.currency = e.target.value;
46
49
  const parsed = parseLocalizedNumber(this.amountDisplay, this.locale);
47
- this.mrdChange.emit({ name: this.name, value: { amount: parsed, currency: this.currency } });
50
+ // Picking a currency for an empty amount does not make the field filled in.
51
+ this.mrdChange.emit({ name: this.name, value: this.currentValue(parsed) });
48
52
  };
49
53
  }
50
54
  componentWillLoad() {
@@ -82,9 +86,16 @@ export class MrdCurrencyField {
82
86
  formatAmount(amount) {
83
87
  return formatNumber(amount, this.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
84
88
  }
89
+ /** Emit either a complete value or nothing at all. A half-filled
90
+ * { amount: null, currency } reaches the API as a currency object without an amount,
91
+ * which it cannot store — and it makes clearing an optional amount impossible, because
92
+ * the field would never report itself as empty (FINDING-0141 / TASK-0234). */
93
+ currentValue(amount) {
94
+ return amount === null ? null : { amount, currency: this.currency };
95
+ }
85
96
  render() {
86
97
  const hasError = !!this.error;
87
- return (h(Host, { key: '43ef1fe660c4eaef025203544916b077975b6aa3' }, h("div", { key: '00b8e79182c8e2a353d57ad5c341024caf00d4ea', class: "mrd-currency-field" }, h(FieldLabel, { key: '96835c311e0d490864c484b818f1dcc623f488b2', base: "mrd-currency-field", label: this.label, required: this.required }), h("div", { key: '80317cfe562af00cc4cbf9aabf4bac2126cf6fe5', class: "mrd-currency-field__row" }, h("input", { key: '1983ef1e4d19e0fc529cf5e8be7d287fea36620a', class: controlClass('mrd-currency-field', hasError, 'amount'), type: "text", inputMode: "decimal", name: `${this.name}_amount`, value: this.amountDisplay, placeholder: this.formatAmount(0), required: this.required, disabled: this.disabled, onInput: this.handleAmountInput, onFocus: this.handleAmountFocus, onBlur: this.handleAmountBlur }), h("select", { key: 'b5fe3f03590bebaa564d76bc5fcb47c327eacece', class: "mrd-currency-field__currency", ref: el => (this.selectEl = el), disabled: this.disabled, onChange: this.handleCurrencyChange }, this.currencyOptions().map(c => (h("option", { key: c, value: c, selected: c === this.currency }, c))))), h(FieldError, { key: '1a128b26293170d4137f787622d4e89a23e62c0b', base: "mrd-currency-field", error: this.error }))));
98
+ return (h(Host, { key: '9529c8ff013107056ea549f70f10016dec00cff6' }, h("div", { key: 'eef1f1400cc9b1f87353413f3f476b028bf3ecbf', class: "mrd-currency-field" }, h(FieldLabel, { key: '76004f615bda7669814d4b5f55c4ec54ae078e12', base: "mrd-currency-field", label: this.label, required: this.required }), h("div", { key: '03a2ec6ada9b2e185e247a228225aed3b67a5bec', class: "mrd-currency-field__row" }, h("input", { key: 'ee9004f4cc306c8a9f417812b573e000c055c09c', class: controlClass('mrd-currency-field', hasError, 'amount'), type: "text", inputMode: "decimal", name: `${this.name}_amount`, value: this.amountDisplay, placeholder: this.formatAmount(0), required: this.required, disabled: this.disabled, onInput: this.handleAmountInput, onFocus: this.handleAmountFocus, onBlur: this.handleAmountBlur }), h("select", { key: '06547e3716881b3c574c6355eef56a92983470bb', class: "mrd-currency-field__currency", ref: el => (this.selectEl = el), disabled: this.disabled, onChange: this.handleCurrencyChange }, this.currencyOptions().map(c => (h("option", { key: c, value: c, selected: c === this.currency }, c))))), h(FieldError, { key: '4cbab3a887fece60ae31baccb24e3b68ecf1b947', base: "mrd-currency-field", error: this.error }))));
88
99
  }
89
100
  static get is() { return "mrd-currency-field"; }
90
101
  static get encapsulation() { return "scoped"; }
@@ -144,8 +155,8 @@ export class MrdCurrencyField {
144
155
  "type": "unknown",
145
156
  "mutable": false,
146
157
  "complexType": {
147
- "original": "CurrencyValue",
148
- "resolved": "CurrencyValue",
158
+ "original": "CurrencyValue | null",
159
+ "resolved": "CurrencyValue | null",
149
160
  "references": {
150
161
  "CurrencyValue": {
151
162
  "location": "import",
@@ -159,11 +170,11 @@ export class MrdCurrencyField {
159
170
  "optional": false,
160
171
  "docs": {
161
172
  "tags": [],
162
- "text": ""
173
+ "text": "Accepts a partial object on the way in (hosts pre-fill the currency from the layout's\ncurrencyCode before an amount exists); on the way out only null or a complete object\nis ever emitted \u2014 see handleAmountBlur."
163
174
  },
164
175
  "getter": false,
165
176
  "setter": false,
166
- "defaultValue": "{ amount: null, currency: 'EUR' }"
177
+ "defaultValue": "null"
167
178
  },
168
179
  "required": {
169
180
  "type": "boolean",
@@ -246,8 +257,8 @@ export class MrdCurrencyField {
246
257
  "text": ""
247
258
  },
248
259
  "complexType": {
249
- "original": "{ name: string; value: CurrencyValue }",
250
- "resolved": "{ name: string; value: CurrencyValue; }",
260
+ "original": "{ name: string; value: CurrencyValue | null }",
261
+ "resolved": "{ name: string; value: CurrencyValue | null; }",
251
262
  "references": {
252
263
  "CurrencyValue": {
253
264
  "location": "import",
@@ -268,8 +279,8 @@ export class MrdCurrencyField {
268
279
  "text": ""
269
280
  },
270
281
  "complexType": {
271
- "original": "{ name: string; value: CurrencyValue }",
272
- "resolved": "{ name: string; value: CurrencyValue; }",
282
+ "original": "{ name: string; value: CurrencyValue | null }",
283
+ "resolved": "{ name: string; value: CurrencyValue | null; }",
273
284
  "references": {
274
285
  "CurrencyValue": {
275
286
  "location": "import",
@@ -132,7 +132,12 @@ export class MrdField {
132
132
  case ClientLayoutItemFieldDataType.PERCENTAGE:
133
133
  return (h("mrd-number-field", Object.assign({}, commonProps, { value: (_f = displayValue) !== null && _f !== void 0 ? _f : null, dataType: item.dataType, decimalPrecision: (_g = item.decimalPrecision) !== null && _g !== void 0 ? _g : 2, placeholder: (_h = item.placeholder) !== null && _h !== void 0 ? _h : '' })));
134
134
  case ClientLayoutItemFieldDataType.CURRENCY:
135
- return (h("mrd-currency-field", Object.assign({}, commonProps, { value: (_j = displayValue) !== null && _j !== void 0 ? _j : { amount: null, currency: (_k = item.currencyCode) !== null && _k !== void 0 ? _k : 'EUR' } })));
135
+ return (h("mrd-currency-field", Object.assign({}, commonProps, {
136
+ // Input-only fallback: it preselects the layout's currencyCode in the dropdown
137
+ // before an amount exists. The field never emits this shape back — an empty
138
+ // amount emits null (FINDING-0141).
139
+ value: (_j = displayValue) !== null && _j !== void 0 ? _j : { amount: null, currency: (_k = item.currencyCode) !== null && _k !== void 0 ? _k : 'EUR' }
140
+ })));
136
141
  case ClientLayoutItemFieldDataType.BOOLEAN:
137
142
  return (h("mrd-boolean-field", Object.assign({}, commonProps, { value: (_l = displayValue) !== null && _l !== void 0 ? _l : false })));
138
143
  case ClientLayoutItemFieldDataType.DATE:
@@ -269,11 +269,12 @@ export class MrdTable {
269
269
  this.loadedPages.size > 0) {
270
270
  this.colWidthMeasureScheduled = true;
271
271
  writeTask(() => {
272
+ var _a;
272
273
  this.colWidthMeasureScheduled = false;
273
274
  // Re-check: state may have changed (e.g. a reset) between scheduling and running.
274
275
  if (this.colWidths.length !== 0)
275
276
  return;
276
- const measured = this.measureColWidths();
277
+ const measured = (_a = this.measureContentWidths()) !== null && _a !== void 0 ? _a : this.measureColWidths();
277
278
  if (measured)
278
279
  this.colWidths = measured;
279
280
  });
@@ -287,6 +288,44 @@ export class MrdTable {
287
288
  return null;
288
289
  return Array.from(ths).map(th => Math.round(th.getBoundingClientRect().width));
289
290
  }
291
+ /** Width one cell needs to show its content in full, borders included.
292
+ * Read from scrollWidth rather than the laid-out box: cells clip with `overflow: hidden`,
293
+ * so a squeezed cell reports the width it *was given*, not the width it needs. */
294
+ neededWidth(cell) {
295
+ // The layout metrics are absent in the mock DOM the specs run on; treat them as 0
296
+ // rather than letting a NaN poison the Math.max chain the callers build.
297
+ const px = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
298
+ const clientWidth = px(cell.clientWidth);
299
+ const scrollWidth = px(cell.scrollWidth);
300
+ const paddingRight = px(parseFloat(getComputedStyle(cell).paddingRight || '0'));
301
+ const borders = Math.max(0, px(cell.offsetWidth) - clientWidth);
302
+ // scrollWidth drops the right padding once the content overflows the clipped box;
303
+ // when it fits, clientWidth is already the full padded box.
304
+ const content = scrollWidth > clientWidth ? scrollWidth + paddingRight : clientWidth;
305
+ return Math.ceil(content + borders);
306
+ }
307
+ /** Width each column needs for its header and its rendered page-0 cells — what the columns
308
+ * are locked to, so they never end up narrower than their content.
309
+ *
310
+ * Deliberately not the laid-out widths: with many columns the browser squeezes them to fit
311
+ * the container instead of overflowing (the cells clip, so they no longer resist), which
312
+ * cost the horizontal scrollbar. Locking the needed widths makes the summed table width
313
+ * exceed the container again, and `.mrd-table__scroll` scrolls. Capped per column at
314
+ * MAX_AUTOFIT_WIDTH so one outlier value can't blow up the layout — same cap as auto-fit. */
315
+ measureContentWidths() {
316
+ const ths = this.el.querySelectorAll('.mrd-table__header');
317
+ if (ths.length === 0 || ths.length !== this.columns.length)
318
+ return null;
319
+ return Array.from(ths).map((th, idx) => {
320
+ let widest = this.neededWidth(th);
321
+ // .mrd-table__cell only matches real data cells — the shimmer/retry rows render a single
322
+ // colSpan cell (without that class) which would measure as the full table width.
323
+ this.el
324
+ .querySelectorAll(`.mrd-table__cell:nth-child(${idx + 1})`)
325
+ .forEach(cell => { widest = Math.max(widest, this.neededWidth(cell)); });
326
+ return Math.min(Math.max(widest, MIN_COL_WIDTH), MAX_AUTOFIT_WIDTH);
327
+ });
328
+ }
290
329
  /** Re-reads the rendered header widths into colWidths so a drag starts from what is
291
330
  * actually on screen — and locks the columns if they weren't locked yet.
292
331
  *
@@ -347,12 +386,7 @@ export class MrdTable {
347
386
  // colSpan cell (without that class) which would otherwise measure as the full table width.
348
387
  const cells = this.el.querySelectorAll(`.mrd-table__header:nth-child(${idx + 1}), .mrd-table__cell:nth-child(${idx + 1})`);
349
388
  let widest = 0;
350
- cells.forEach(cell => {
351
- // scrollWidth is the untruncated content width; cells clip with overflow: hidden.
352
- const style = getComputedStyle(cell);
353
- const padding = parseFloat(style.paddingLeft || '0') + parseFloat(style.paddingRight || '0');
354
- widest = Math.max(widest, cell.scrollWidth + padding);
355
- });
389
+ cells.forEach(cell => { widest = Math.max(widest, this.neededWidth(cell)); });
356
390
  if (widest > 0)
357
391
  this.setColWidth(idx, Math.min(widest, MAX_AUTOFIT_WIDTH));
358
392
  }
@@ -1 +1 @@
1
- import{proxyCustomElement as r,HTMLElement as e,createEvent as i,h as d,Host as c,transformTag as o}from"@stencil/core/internal/client";import{p as t,f as a}from"./format.js";import{r as s,F as n,c as l,a as m}from"./field-helpers.js";const u=["EUR","USD","GBP","CHF","JPY","CNY","AUD","CAD","SEK","NOK","DKK","NZD","SGD","HKD","PLN","CZK","HUF","MXN","BRL","INR","TRY","ZAR","AED","SAR"],h=r(class extends e{constructor(r){super(),!1!==r&&this.__registerHost(),this.mrdChange=i(this,"mrdChange",7),this.mrdBlur=i(this,"mrdBlur",7),this.name="",this.label="",this.value={amount:null,currency:"EUR"},this.required=!1,this.disabled=!1,this.locale=navigator.language,this.amountDisplay="",this.currency="EUR",this.error="",this.focused=!1,this.handleAmountInput=r=>{this.amountDisplay=r.target.value},this.handleAmountFocus=()=>{this.focused=!0;const r=t(this.amountDisplay,this.locale);null!==r&&(this.amountDisplay=a(r,this.locale,{useGrouping:!1,maximumFractionDigits:20}))},this.handleAmountBlur=r=>{this.focused=!1;const e=r.target.value,i=t(e,this.locale);this.error=s(e,this.required,this.locale),this.amountDisplay=null!==i?this.formatAmount(i):"";const d={amount:i,currency:this.currency};this.mrdChange.emit({name:this.name,value:d}),this.mrdBlur.emit({name:this.name,value:d})},this.handleCurrencyChange=r=>{this.currency=r.target.value;const e=t(this.amountDisplay,this.locale);this.mrdChange.emit({name:this.name,value:{amount:e,currency:this.currency}})}}componentWillLoad(){this.syncFromValue()}componentDidRender(){this.selectEl&&this.selectEl.value!==this.currency&&(this.selectEl.value=this.currency)}currencyOptions(){return u.includes(this.currency)?u:[this.currency,...u]}valueChanged(){this.focused||this.syncFromValue()}localeChanged(){this.focused||this.syncFromValue()}syncFromValue(){const r=this.value;this.currency=(null==r?void 0:r.currency)||"EUR",this.amountDisplay=null!=(null==r?void 0:r.amount)?this.formatAmount(r.amount):""}formatAmount(r){return a(r,this.locale,{minimumFractionDigits:2,maximumFractionDigits:2})}render(){const r=!!this.error;return d(c,{key:"43ef1fe660c4eaef025203544916b077975b6aa3"},d("div",{key:"00b8e79182c8e2a353d57ad5c341024caf00d4ea",class:"mrd-currency-field"},d(n,{key:"96835c311e0d490864c484b818f1dcc623f488b2",base:"mrd-currency-field",label:this.label,required:this.required}),d("div",{key:"80317cfe562af00cc4cbf9aabf4bac2126cf6fe5",class:"mrd-currency-field__row"},d("input",{key:"1983ef1e4d19e0fc529cf5e8be7d287fea36620a",class:l("mrd-currency-field",r,"amount"),type:"text",inputMode:"decimal",name:`${this.name}_amount`,value:this.amountDisplay,placeholder:this.formatAmount(0),required:this.required,disabled:this.disabled,onInput:this.handleAmountInput,onFocus:this.handleAmountFocus,onBlur:this.handleAmountBlur}),d("select",{key:"b5fe3f03590bebaa564d76bc5fcb47c327eacece",class:"mrd-currency-field__currency",ref:r=>this.selectEl=r,disabled:this.disabled,onChange:this.handleCurrencyChange},this.currencyOptions().map((r=>d("option",{key:r,value:r,selected:r===this.currency},r))))),d(m,{key:"1a128b26293170d4137f787622d4e89a23e62c0b",base:"mrd-currency-field",error:this.error})))}static get watchers(){return{value:[{valueChanged:0}],locale:[{localeChanged:0}]}}static get style(){return'.sc-mrd-currency-field-h{display:block}.mrd-currency-field.sc-mrd-currency-field{display:flex;flex-direction:column;gap:var(--mrd-space-1);width:100%}.mrd-currency-field__label.sc-mrd-currency-field{display:block;font-family:var(--mrd-font-family);font-size:var(--mrd-label-font-size);font-weight:var(--mrd-label-font-weight);color:var(--mrd-label-color)}.mrd-currency-field__label--required.sc-mrd-currency-field::after{content:" *";color:var(--mrd-color-danger)}.mrd-currency-field__row.sc-mrd-currency-field{display:flex;gap:var(--mrd-space-2);align-items:stretch}.mrd-currency-field__amount.sc-mrd-currency-field{display:block;width:100%;height:var(--mrd-input-height);padding:var(--mrd-input-padding-y) var(--mrd-input-padding-x);font-family:var(--mrd-font-family);font-size:var(--mrd-font-size-base);color:var(--mrd-input-color);background-color:var(--mrd-input-bg);border:var(--mrd-border-width) solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);transition:border-color var(--mrd-transition), box-shadow var(--mrd-transition);outline:none;appearance:none;box-sizing:border-box}.mrd-currency-field__amount.sc-mrd-currency-field::placeholder{color:var(--mrd-input-placeholder-color)}.mrd-currency-field__amount.sc-mrd-currency-field:focus{border-color:var(--mrd-border-color-focus);box-shadow:var(--mrd-shadow-focus)}.mrd-currency-field__amount.sc-mrd-currency-field:disabled{background-color:var(--mrd-input-bg-disabled);cursor:not-allowed;opacity:0.7}.mrd-currency-field__amount--error.sc-mrd-currency-field{border-color:var(--mrd-border-color-error)}.mrd-currency-field__amount--error.sc-mrd-currency-field:focus{box-shadow:var(--mrd-shadow-focus-error)}.mrd-currency-field__amount.sc-mrd-currency-field{flex:1;text-align:right}.mrd-currency-field__currency.sc-mrd-currency-field{width:90px;height:var(--mrd-input-height);padding:var(--mrd-input-padding-y) var(--mrd-space-2);font-family:var(--mrd-font-family);font-size:var(--mrd-font-size-sm);font-weight:var(--mrd-font-weight-medium);color:var(--mrd-input-color);background-color:var(--mrd-color-neutral-50);border:var(--mrd-border-width) solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);outline:none;cursor:pointer;box-sizing:border-box;appearance:none;text-align:center}.mrd-currency-field__currency.sc-mrd-currency-field:focus{border-color:var(--mrd-border-color-focus);box-shadow:var(--mrd-shadow-focus)}.mrd-currency-field__currency.sc-mrd-currency-field:disabled{background-color:var(--mrd-input-bg-disabled);cursor:not-allowed;opacity:0.7}.mrd-currency-field__error.sc-mrd-currency-field{font-family:var(--mrd-font-family);font-size:var(--mrd-error-font-size);color:var(--mrd-error-color)}'}},[2,"mrd-currency-field",{name:[1],label:[1],value:[16],required:[4],disabled:[4],locale:[1],amountDisplay:[32],currency:[32],error:[32]},void 0,{value:[{valueChanged:0}],locale:[{localeChanged:0}]}]);function f(){"undefined"!=typeof customElements&&["mrd-currency-field"].forEach((r=>{"mrd-currency-field"===r&&(customElements.get(o(r))||customElements.define(o(r),h))}))}export{h as M,f as d}
1
+ import{proxyCustomElement as r,HTMLElement as e,createEvent as i,h as c,Host as d,transformTag as o}from"@stencil/core/internal/client";import{p as t,f as a}from"./format.js";import{r as s,F as n,c as l,a as u}from"./field-helpers.js";const m=["EUR","USD","GBP","CHF","JPY","CNY","AUD","CAD","SEK","NOK","DKK","NZD","SGD","HKD","PLN","CZK","HUF","MXN","BRL","INR","TRY","ZAR","AED","SAR"],h=r(class extends e{constructor(r){super(),!1!==r&&this.__registerHost(),this.mrdChange=i(this,"mrdChange",7),this.mrdBlur=i(this,"mrdBlur",7),this.name="",this.label="",this.value=null,this.required=!1,this.disabled=!1,this.locale=navigator.language,this.amountDisplay="",this.currency="EUR",this.error="",this.focused=!1,this.handleAmountInput=r=>{this.amountDisplay=r.target.value},this.handleAmountFocus=()=>{this.focused=!0;const r=t(this.amountDisplay,this.locale);null!==r&&(this.amountDisplay=a(r,this.locale,{useGrouping:!1,maximumFractionDigits:20}))},this.handleAmountBlur=r=>{this.focused=!1;const e=r.target.value,i=t(e,this.locale);this.error=s(e,this.required,this.locale),this.amountDisplay=null!==i?this.formatAmount(i):"";const c=this.currentValue(i);this.mrdChange.emit({name:this.name,value:c}),this.mrdBlur.emit({name:this.name,value:c})},this.handleCurrencyChange=r=>{this.currency=r.target.value;const e=t(this.amountDisplay,this.locale);this.mrdChange.emit({name:this.name,value:this.currentValue(e)})}}componentWillLoad(){this.syncFromValue()}componentDidRender(){this.selectEl&&this.selectEl.value!==this.currency&&(this.selectEl.value=this.currency)}currencyOptions(){return m.includes(this.currency)?m:[this.currency,...m]}valueChanged(){this.focused||this.syncFromValue()}localeChanged(){this.focused||this.syncFromValue()}syncFromValue(){const r=this.value;this.currency=(null==r?void 0:r.currency)||"EUR",this.amountDisplay=null!=(null==r?void 0:r.amount)?this.formatAmount(r.amount):""}formatAmount(r){return a(r,this.locale,{minimumFractionDigits:2,maximumFractionDigits:2})}currentValue(r){return null===r?null:{amount:r,currency:this.currency}}render(){const r=!!this.error;return c(d,{key:"9529c8ff013107056ea549f70f10016dec00cff6"},c("div",{key:"eef1f1400cc9b1f87353413f3f476b028bf3ecbf",class:"mrd-currency-field"},c(n,{key:"76004f615bda7669814d4b5f55c4ec54ae078e12",base:"mrd-currency-field",label:this.label,required:this.required}),c("div",{key:"03a2ec6ada9b2e185e247a228225aed3b67a5bec",class:"mrd-currency-field__row"},c("input",{key:"ee9004f4cc306c8a9f417812b573e000c055c09c",class:l("mrd-currency-field",r,"amount"),type:"text",inputMode:"decimal",name:`${this.name}_amount`,value:this.amountDisplay,placeholder:this.formatAmount(0),required:this.required,disabled:this.disabled,onInput:this.handleAmountInput,onFocus:this.handleAmountFocus,onBlur:this.handleAmountBlur}),c("select",{key:"06547e3716881b3c574c6355eef56a92983470bb",class:"mrd-currency-field__currency",ref:r=>this.selectEl=r,disabled:this.disabled,onChange:this.handleCurrencyChange},this.currencyOptions().map((r=>c("option",{key:r,value:r,selected:r===this.currency},r))))),c(u,{key:"4cbab3a887fece60ae31baccb24e3b68ecf1b947",base:"mrd-currency-field",error:this.error})))}static get watchers(){return{value:[{valueChanged:0}],locale:[{localeChanged:0}]}}static get style(){return'.sc-mrd-currency-field-h{display:block}.mrd-currency-field.sc-mrd-currency-field{display:flex;flex-direction:column;gap:var(--mrd-space-1);width:100%}.mrd-currency-field__label.sc-mrd-currency-field{display:block;font-family:var(--mrd-font-family);font-size:var(--mrd-label-font-size);font-weight:var(--mrd-label-font-weight);color:var(--mrd-label-color)}.mrd-currency-field__label--required.sc-mrd-currency-field::after{content:" *";color:var(--mrd-color-danger)}.mrd-currency-field__row.sc-mrd-currency-field{display:flex;gap:var(--mrd-space-2);align-items:stretch}.mrd-currency-field__amount.sc-mrd-currency-field{display:block;width:100%;height:var(--mrd-input-height);padding:var(--mrd-input-padding-y) var(--mrd-input-padding-x);font-family:var(--mrd-font-family);font-size:var(--mrd-font-size-base);color:var(--mrd-input-color);background-color:var(--mrd-input-bg);border:var(--mrd-border-width) solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);transition:border-color var(--mrd-transition), box-shadow var(--mrd-transition);outline:none;appearance:none;box-sizing:border-box}.mrd-currency-field__amount.sc-mrd-currency-field::placeholder{color:var(--mrd-input-placeholder-color)}.mrd-currency-field__amount.sc-mrd-currency-field:focus{border-color:var(--mrd-border-color-focus);box-shadow:var(--mrd-shadow-focus)}.mrd-currency-field__amount.sc-mrd-currency-field:disabled{background-color:var(--mrd-input-bg-disabled);cursor:not-allowed;opacity:0.7}.mrd-currency-field__amount--error.sc-mrd-currency-field{border-color:var(--mrd-border-color-error)}.mrd-currency-field__amount--error.sc-mrd-currency-field:focus{box-shadow:var(--mrd-shadow-focus-error)}.mrd-currency-field__amount.sc-mrd-currency-field{flex:1;text-align:right}.mrd-currency-field__currency.sc-mrd-currency-field{width:90px;height:var(--mrd-input-height);padding:var(--mrd-input-padding-y) var(--mrd-space-2);font-family:var(--mrd-font-family);font-size:var(--mrd-font-size-sm);font-weight:var(--mrd-font-weight-medium);color:var(--mrd-input-color);background-color:var(--mrd-color-neutral-50);border:var(--mrd-border-width) solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);outline:none;cursor:pointer;box-sizing:border-box;appearance:none;text-align:center}.mrd-currency-field__currency.sc-mrd-currency-field:focus{border-color:var(--mrd-border-color-focus);box-shadow:var(--mrd-shadow-focus)}.mrd-currency-field__currency.sc-mrd-currency-field:disabled{background-color:var(--mrd-input-bg-disabled);cursor:not-allowed;opacity:0.7}.mrd-currency-field__error.sc-mrd-currency-field{font-family:var(--mrd-font-family);font-size:var(--mrd-error-font-size);color:var(--mrd-error-color)}'}},[2,"mrd-currency-field",{name:[1],label:[1],value:[16],required:[4],disabled:[4],locale:[1],amountDisplay:[32],currency:[32],error:[32]},void 0,{value:[{valueChanged:0}],locale:[{localeChanged:0}]}]);function f(){"undefined"!=typeof customElements&&["mrd-currency-field"].forEach((r=>{"mrd-currency-field"===r&&(customElements.get(o(r))||customElements.define(o(r),h))}))}export{h as M,f as d}