@mmlogic/components 0.5.5 → 0.5.7

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 (84) hide show
  1. package/dist/cjs/{cell-renderer-Mx3pwONr.js → cell-renderer-pwUGEDql.js} +8 -261
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. package/dist/cjs/mosterdcomponents.cjs.js +1 -1
  4. package/dist/cjs/mrd-boolean-field_25.cjs.entry.js +234 -262
  5. package/dist/cjs/mrd-merge-view.cjs.entry.js +3 -3
  6. package/dist/collection/components/fields/mrd-date-field/mrd-date-field.css +4 -55
  7. package/dist/collection/components/fields/mrd-date-field/mrd-date-field.js +8 -118
  8. package/dist/collection/components/fields/mrd-datetime-field/mrd-datetime-field.css +4 -55
  9. package/dist/collection/components/fields/mrd-datetime-field/mrd-datetime-field.js +15 -117
  10. package/dist/collection/components/fields/mrd-email-field/mrd-email-field.js +1 -1
  11. package/dist/collection/components/fields/mrd-file-field/mrd-file-field.js +1 -1
  12. package/dist/collection/components/fields/mrd-image-field/mrd-image-field.js +1 -1
  13. package/dist/collection/components/fields/mrd-longtext-field/mrd-longtext-field.js +1 -1
  14. package/dist/collection/components/fields/mrd-number-field/mrd-number-field.js +1 -1
  15. package/dist/collection/components/fields/mrd-secret-field/mrd-secret-field.js +2 -2
  16. package/dist/collection/components/fields/mrd-text-field/mrd-text-field.js +1 -1
  17. package/dist/collection/components/fields/mrd-textarea-field/mrd-textarea-field.js +1 -1
  18. package/dist/collection/components/fields/mrd-time-field/mrd-time-field.js +1 -1
  19. package/dist/collection/components/form/mrd-field/mrd-field.js +11 -11
  20. package/dist/collection/components/invoice/mrd-invoice-view/mrd-invoice-view.js +1 -1
  21. package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.js +1 -1
  22. package/dist/collection/components/merge/mrd-merge-view/mrd-merge-view.js +2 -2
  23. package/dist/collection/components/table/mrd-table/mrd-table.css +52 -22
  24. package/dist/collection/components/table/mrd-table/mrd-table.js +175 -45
  25. package/dist/collection/components/table/mrd-table/table-parts.js +6 -3
  26. package/dist/collection/components/table/mrd-table/virtual-scroll.js +8 -3
  27. package/dist/collection/utils/format.js +0 -160
  28. package/dist/collection/utils/i18n.js +8 -96
  29. package/dist/components/cell-renderer.js +1 -1
  30. package/dist/components/format.js +1 -1
  31. package/dist/components/i18n.js +1 -1
  32. package/dist/components/mrd-date-field2.js +1 -1
  33. package/dist/components/mrd-datetime-field2.js +1 -1
  34. package/dist/components/mrd-document-list2.js +1 -1
  35. package/dist/components/mrd-document-view2.js +1 -1
  36. package/dist/components/mrd-email-field2.js +1 -1
  37. package/dist/components/mrd-email-view2.js +1 -1
  38. package/dist/components/mrd-field2.js +1 -1
  39. package/dist/components/mrd-file-field2.js +1 -1
  40. package/dist/components/mrd-image-field2.js +1 -1
  41. package/dist/components/mrd-invoice-view2.js +1 -1
  42. package/dist/components/mrd-layout-section.js +1 -1
  43. package/dist/components/mrd-longtext-field2.js +1 -1
  44. package/dist/components/mrd-merge-view.js +1 -1
  45. package/dist/components/mrd-number-field2.js +1 -1
  46. package/dist/components/mrd-secret-field2.js +1 -1
  47. package/dist/components/mrd-table2.js +1 -1
  48. package/dist/components/mrd-text-field2.js +1 -1
  49. package/dist/components/mrd-textarea-field2.js +1 -1
  50. package/dist/components/mrd-time-field2.js +1 -1
  51. package/dist/esm/{cell-renderer-UQOYlDv7.js → cell-renderer-B8rJpnq8.js} +9 -257
  52. package/dist/esm/loader.js +1 -1
  53. package/dist/esm/mosterdcomponents.js +1 -1
  54. package/dist/esm/mrd-boolean-field_25.entry.js +234 -262
  55. package/dist/esm/mrd-merge-view.entry.js +3 -3
  56. package/dist/mosterdcomponents/cell-renderer-B8u5BCK-.js.map +1 -0
  57. package/dist/mosterdcomponents/client-layout-Bc42CfJB.js.map +1 -0
  58. package/dist/mosterdcomponents/document-attachments-BufPmMRv.js.map +1 -0
  59. package/dist/mosterdcomponents/field-helpers-CUJsHr1V.js.map +1 -0
  60. package/dist/mosterdcomponents/file-upload-common-DdTDuxEU.js.map +1 -0
  61. package/dist/mosterdcomponents/format-gcecItcD.js.map +1 -0
  62. package/dist/mosterdcomponents/i18n-Ba7_jO9I.js.map +1 -0
  63. package/dist/mosterdcomponents/index-1ayBojyN.js.map +1 -0
  64. package/dist/mosterdcomponents/index-DhffneUF.js.map +1 -0
  65. package/dist/mosterdcomponents/index.esm.js.map +1 -0
  66. package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
  67. package/dist/mosterdcomponents/mosterdcomponents.esm.js.map +1 -0
  68. package/dist/mosterdcomponents/p-5614d8f5.entry.js +3 -0
  69. package/dist/mosterdcomponents/{p-d31b71d9.entry.js → p-e540de6a.entry.js} +1 -1
  70. package/dist/mosterdcomponents/p-rTM1tU_M.js +1 -0
  71. package/dist/mosterdcomponents/purify.es-_duHfPgC.js.map +1 -0
  72. package/dist/mosterdcomponents/query-params-BZbQoeYF.js.map +1 -0
  73. package/dist/mosterdcomponents/quill-C9pgw_k-.js.map +1 -0
  74. package/dist/mosterdcomponents/validation-CjjTt66G.js.map +1 -0
  75. package/dist/types/components/fields/mrd-date-field/mrd-date-field.d.ts +1 -22
  76. package/dist/types/components/fields/mrd-datetime-field/mrd-datetime-field.d.ts +2 -21
  77. package/dist/types/components/table/mrd-table/mrd-table.d.ts +47 -5
  78. package/dist/types/components/table/mrd-table/table-parts.d.ts +0 -2
  79. package/dist/types/components/table/mrd-table/virtual-scroll.d.ts +6 -1
  80. package/dist/types/components.d.ts +0 -26
  81. package/dist/types/utils/format.d.ts +0 -34
  82. package/package.json +1 -1
  83. package/dist/mosterdcomponents/p-8cc374e0.entry.js +0 -3
  84. package/dist/mosterdcomponents/p-CF--ScWr.js +0 -1
@@ -9,6 +9,10 @@ import { REQUEST_DEBOUNCE_MS, REQUEST_TIMEOUT_MS, MAX_RETRY_ATTEMPTS, visibleCou
9
9
  const STALE_CHECK_INTERVAL_MS = 2000;
10
10
  /** Breedte van de filterpopup in px — voor overflow-correctie. */
11
11
  const POPUP_WIDTH = 280;
12
+ /** Smallest column width (px) a user can drag a column down to. */
13
+ const MIN_COL_WIDTH = 48;
14
+ /** Upper bound (px) for a double-click auto-fit, so one huge value can't blow up the layout. */
15
+ const MAX_AUTOFIT_WIDTH = 600;
12
16
  export class MrdTable {
13
17
  constructor() {
14
18
  // ── Non-state internals ────────────────────────────────────────────────────
@@ -25,6 +29,13 @@ export class MrdTable {
25
29
  this.viewPopoverClickHandler = null;
26
30
  /** Guards against scheduling more than one column-width measurement per freeze. */
27
31
  this.colWidthMeasureScheduled = false;
32
+ /** Drag origin of the running column resize (null = no drag in progress). */
33
+ this.resizeOrigin = null;
34
+ /** True once the user has dragged (or auto-fitted) a column — see colWidthsUserSet usages. */
35
+ this.colWidthsUserSet = false;
36
+ /** Set once the host drives this table through init()/setPage(). Never cleared: it has to
37
+ * survive resetPages(), which empties loadedPages — see the isPaginated getter. */
38
+ this.paginationActivated = false;
28
39
  // ── Props ──────────────────────────────────────────────────────────────────
29
40
  /** The VIEW or RELATED_VIEW layout item. Contains view config, dataClass, fromClass, actions etc. */
30
41
  this.item = null;
@@ -54,10 +65,10 @@ export class MrdTable {
54
65
  this.renderStart = 0;
55
66
  this.renderEnd = 0;
56
67
  this.colWidths = [];
68
+ /** Index of the column currently being dragged by its resize handle (null = not resizing). */
69
+ this.resizingCol = null;
57
70
  this.sortField = '';
58
71
  this.sortDir = 'asc';
59
- /** Whether the filter UI is visible on column headers. */
60
- this.filterMode = false;
61
72
  /** Active filters keyed by field name. */
62
73
  this.activeFilters = new Map();
63
74
  /** Field name of the currently open filter popup (null = closed). */
@@ -94,15 +105,24 @@ export class MrdTable {
94
105
  };
95
106
  }
96
107
  // ── Prop watchers ─────────────────────────────────────────────────────────
97
- /** Clamp renderEnd when totalElements shrinks (e.g. after a filter is applied). */
108
+ /** Clamp renderEnd when totalElements shrinks (e.g. after a filter is applied).
109
+ * 0 means "unknown" (hosts that rely on minKnownTotal never set it, and some reset it
110
+ * while a filter is being applied) — clamping to it would collapse the window to a
111
+ * single row, so leave the window alone and let the render pass clamp it. */
98
112
  totalElementsChanged(newVal) {
99
- this.renderEnd = Math.min(this.renderEnd, Math.max(0, newVal - 1));
113
+ if (newVal > 0)
114
+ this.renderEnd = Math.min(this.renderEnd, newVal - 1);
100
115
  }
101
116
  /** Reset to primary view when the item prop is replaced from outside. */
102
117
  itemChanged(newVal) {
103
118
  var _a, _b;
104
119
  this.activeViewIdx = 0;
105
120
  this.applyDefaultSort((_b = (_a = newVal === null || newVal === void 0 ? void 0 : newVal.view) === null || _a === void 0 ? void 0 : _a.defaultSort) !== null && _b !== void 0 ? _b : '');
121
+ // The columns getter derives from item/activeViewIdx: locked widths belong to the old
122
+ // column set and would otherwise be applied by index to the new one.
123
+ this.colWidths = [];
124
+ this.colWidthMeasureScheduled = false;
125
+ this.colWidthsUserSet = false;
106
126
  }
107
127
  // ── Lifecycle ──────────────────────────────────────────────────────────────
108
128
  componentWillLoad() {
@@ -137,6 +157,7 @@ export class MrdTable {
137
157
  this.retryAttempts.clear();
138
158
  this.colWidths = [];
139
159
  this.colWidthMeasureScheduled = false;
160
+ this.colWidthsUserSet = false;
140
161
  this.scrollTop = 0;
141
162
  this.renderStart = 0;
142
163
  // Always fill the visible viewport on init — totalElements may be stale from a
@@ -149,6 +170,7 @@ export class MrdTable {
149
170
  this.aggregationsTotal = null;
150
171
  this.aggregationsPending = true;
151
172
  this.minKnownTotal = 0;
173
+ this.paginationActivated = true;
152
174
  // Always request page 0 — totalElements may be unknown (0) on first load.
153
175
  this.emitLoadPage(0);
154
176
  this.requestedPages = new Set([0]);
@@ -165,6 +187,7 @@ export class MrdTable {
165
187
  * detection even when rows.length === pageSize (exact multiple of page size).
166
188
  */
167
189
  async setPage(pageNumber, rows, hasNext) {
190
+ this.paginationActivated = true;
168
191
  const isLastPage = hasNext !== undefined ? !hasNext : rows.length < this.pageSize;
169
192
  if (isLastPage) {
170
193
  // lastRowIdx is -1 when the page is empty; clamp renderEnd to -1 so the
@@ -181,6 +204,13 @@ export class MrdTable {
181
204
  const next = new Map(this.loadedPages);
182
205
  next.set(pageNumber, rows);
183
206
  this.loadedPages = next;
207
+ // Repair a window that renders nothing. renderEnd is only ever clamped *down* (here and
208
+ // in totalElementsChanged), so a window computed while the total was still unknown stays
209
+ // collapsed and the rows sit in loadedPages without ever reaching the screen until the
210
+ // user scrolls. Stays collapsed when the data really is empty (minKnownTotal 0).
211
+ if (this.renderEnd < this.renderStart) {
212
+ this.renderEnd = Math.min(this.renderStart + this.visibleCount() - 1, this.minKnownTotal - 1);
213
+ }
184
214
  this.requestedAt.delete(pageNumber);
185
215
  this.retryAttempts.delete(pageNumber);
186
216
  if (this.failedPages.has(pageNumber)) {
@@ -231,23 +261,118 @@ export class MrdTable {
231
261
  // re-render *during* the render cycle if done synchronously here. Defer it to a
232
262
  // writeTask so the state mutation happens outside the render pass (avoids the
233
263
  // Stencil "state/prop changed during rendering" warning and potential loops).
264
+ // NB: the freeze must not depend on the totalElements prop — hosts may omit it
265
+ // and rely on minKnownTotal, in which case the widths would never be locked and
266
+ // every incoming page would re-flow all columns.
234
267
  if (this.colWidths.length === 0 &&
235
268
  !this.colWidthMeasureScheduled &&
236
- this.loadedPages.size > 0 &&
237
- this.totalElements > 0) {
269
+ this.loadedPages.size > 0) {
238
270
  this.colWidthMeasureScheduled = true;
239
271
  writeTask(() => {
240
272
  this.colWidthMeasureScheduled = false;
241
273
  // Re-check: state may have changed (e.g. a reset) between scheduling and running.
242
274
  if (this.colWidths.length !== 0)
243
275
  return;
244
- const ths = this.el.querySelectorAll('.mrd-table__header');
245
- if (ths.length > 0) {
246
- this.colWidths = Array.from(ths).map(th => th.offsetWidth);
247
- }
276
+ const measured = this.measureColWidths();
277
+ if (measured)
278
+ this.colWidths = measured;
248
279
  });
249
280
  }
250
281
  }
282
+ // ── Column resizing (drag the header border, Excel-style) ──────────────────
283
+ /** Current rendered header widths, or null when they can't be trusted (header not laid out yet). */
284
+ measureColWidths() {
285
+ const ths = this.el.querySelectorAll('.mrd-table__header');
286
+ if (ths.length === 0 || ths.length !== this.columns.length)
287
+ return null;
288
+ return Array.from(ths).map(th => Math.round(th.getBoundingClientRect().width));
289
+ }
290
+ /** Locks the columns at their current rendered widths if they aren't locked yet. */
291
+ ensureColWidths() {
292
+ if (this.colWidths.length === this.columns.length)
293
+ return true;
294
+ const measured = this.measureColWidths();
295
+ if (!measured)
296
+ return false;
297
+ this.colWidths = measured;
298
+ return true;
299
+ }
300
+ setColWidth(idx, width) {
301
+ const next = [...this.colWidths];
302
+ next[idx] = Math.max(MIN_COL_WIDTH, Math.round(width));
303
+ this.colWidths = next;
304
+ this.colWidthsUserSet = true;
305
+ }
306
+ handleResizeStart(e, idx) {
307
+ var _a, _b;
308
+ // Stop the header's own click/sort handling; the drag owns this gesture.
309
+ e.preventDefault();
310
+ e.stopPropagation();
311
+ if (!this.ensureColWidths())
312
+ return;
313
+ this.resizeOrigin = { idx, startX: e.clientX, startWidth: this.colWidths[idx] };
314
+ this.resizingCol = idx;
315
+ // Pointer capture keeps move/up events on the handle even when the cursor runs
316
+ // ahead of it — no document-level listeners to register and clean up.
317
+ (_b = (_a = e.currentTarget).setPointerCapture) === null || _b === void 0 ? void 0 : _b.call(_a, e.pointerId);
318
+ }
319
+ handleResizeMove(e) {
320
+ if (!this.resizeOrigin)
321
+ return;
322
+ const { idx, startX, startWidth } = this.resizeOrigin;
323
+ this.setColWidth(idx, startWidth + (e.clientX - startX));
324
+ }
325
+ handleResizeEnd(e) {
326
+ var _a, _b;
327
+ if (!this.resizeOrigin)
328
+ return;
329
+ this.resizeOrigin = null;
330
+ this.resizingCol = null;
331
+ (_b = (_a = e.currentTarget).releasePointerCapture) === null || _b === void 0 ? void 0 : _b.call(_a, e.pointerId);
332
+ }
333
+ /** Double-click on the handle: widen the column to fit its widest *rendered* cell.
334
+ * Only rows currently in the virtual-scroll window are measured — off-screen pages
335
+ * aren't in the DOM at all, so this is "fit what you can see", not "fit all data". */
336
+ handleResizeAutoFit(e, idx) {
337
+ e.preventDefault();
338
+ e.stopPropagation();
339
+ if (!this.ensureColWidths())
340
+ return;
341
+ // .mrd-table__cell only matches real data cells — the shimmer/retry rows render a single
342
+ // colSpan cell (without that class) which would otherwise measure as the full table width.
343
+ const cells = this.el.querySelectorAll(`.mrd-table__header:nth-child(${idx + 1}), .mrd-table__cell:nth-child(${idx + 1})`);
344
+ let widest = 0;
345
+ cells.forEach(cell => {
346
+ // scrollWidth is the untruncated content width; cells clip with overflow: hidden.
347
+ const style = getComputedStyle(cell);
348
+ const padding = parseFloat(style.paddingLeft || '0') + parseFloat(style.paddingRight || '0');
349
+ widest = Math.max(widest, cell.scrollWidth + padding);
350
+ });
351
+ if (widest > 0)
352
+ this.setColWidth(idx, Math.min(widest, MAX_AUTOFIT_WIDTH));
353
+ }
354
+ /** Inline table style once the column widths are locked, undefined while still auto-layout.
355
+ * Once locked the table gets an explicit pixel width (the sum of the columns) instead of
356
+ * the stylesheet's `min-width: max-content`. max-content is re-derived from the cell
357
+ * contents, so every page that scrolled in with a longer value widened the table and
358
+ * re-proportioned all columns — the jitter this replaces.
359
+ * `minWidth: 100%` keeps an auto-measured table stretched to its container, but is dropped
360
+ * once the user resizes by hand: with it, the space freed by narrowing a column is
361
+ * redistributed over all columns, so a drag would partly spring back. */
362
+ lockedTableStyle() {
363
+ if (this.colWidths.length === 0)
364
+ return undefined;
365
+ const total = this.colWidths.reduce((sum, w) => sum + (w || 0), 0);
366
+ return Object.assign({ tableLayout: 'fixed', width: `${total}px` }, (this.colWidthsUserSet ? {} : { minWidth: '100%' }));
367
+ }
368
+ /** The resize handle rendered in the right edge of every header cell. */
369
+ renderColResizer(idx) {
370
+ const cls = 'mrd-table__col-resizer' + (this.resizingCol === idx ? ' mrd-table__col-resizer--active' : '');
371
+ return (h("span", { class: cls, role: "separator", "aria-orientation": "vertical", title: t('column_resize', this.locale), "on-pointerdown": (e) => this.handleResizeStart(e, idx), "on-pointermove": (e) => this.handleResizeMove(e), "on-pointerup": (e) => this.handleResizeEnd(e), "on-pointercancel": (e) => this.handleResizeEnd(e), "on-dblclick": (e) => this.handleResizeAutoFit(e, idx),
372
+ // The click that follows the drag targets this handle; swallow it so it never
373
+ // bubbles to the header and triggers a sort or the filter popup.
374
+ onClick: (e) => e.stopPropagation() }));
375
+ }
251
376
  // ── Paging / scroll helpers ────────────────────────────────────────────────
252
377
  visibleCount() {
253
378
  return visibleCount(this.tableHeight, this.rowHeight);
@@ -331,6 +456,17 @@ export class MrdTable {
331
456
  activeFilters: this.activeFilters.values(),
332
457
  });
333
458
  }
459
+ /** True when the table is in paginated / virtual-scroll mode rather than driven by the
460
+ * rows prop. Mirrors the branch taken in render().
461
+ *
462
+ * The filter handlers gate their reload on this. They used to test minKnownTotal, which is
463
+ * 0 after a filter returned no rows at all — so clearing that filter reloaded nothing and
464
+ * the table stayed empty. Hosts that omit totalElements have the same problem, hence the
465
+ * sticky flag rather than loadedPages.size: resetPages() clears the loaded pages, and the
466
+ * mode must not flip back to the rows prop (and its "no results") while page 0 is in flight. */
467
+ get isPaginated() {
468
+ return this.totalElements > 0 || this.paginationActivated;
469
+ }
334
470
  get columns() {
335
471
  var _a, _b, _c;
336
472
  return ((_c = (_b = (_a = this.allViews[this.activeViewIdx]) === null || _a === void 0 ? void 0 : _a.view) === null || _b === void 0 ? void 0 : _b.values) !== null && _c !== void 0 ? _c : []);
@@ -412,8 +548,13 @@ export class MrdTable {
412
548
  this.failedPages = new Set();
413
549
  this.requestedAt.clear();
414
550
  this.retryAttempts.clear();
415
- this.colWidths = [];
416
- this.colWidthMeasureScheduled = false;
551
+ // A sort or filter change keeps the same columns, so hand-set widths survive it —
552
+ // re-measuring would throw away what the user just dragged. Auto-measured widths are
553
+ // dropped so they can be re-derived from the new first page.
554
+ if (!this.colWidthsUserSet) {
555
+ this.colWidths = [];
556
+ this.colWidthMeasureScheduled = false;
557
+ }
417
558
  this.scrollTop = 0;
418
559
  this.renderStart = 0;
419
560
  this.minKnownTotal = 0;
@@ -424,18 +565,8 @@ export class MrdTable {
424
565
  if (scroller)
425
566
  scroller.scrollTop = 0;
426
567
  }
427
- handleSortClick(col) {
428
- const name = this.colName(col);
429
- if (this.sortField === name) {
430
- this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc';
431
- }
432
- else {
433
- this.sortField = name;
434
- this.sortDir = 'asc';
435
- }
436
- this.resetPages();
437
- this.emitPagesForWindow(this.renderStart, this.renderEnd);
438
- }
568
+ /** Sorting runs through the column popup's sort section (applySort); there is no
569
+ * separate click-to-sort mode any more. */
439
570
  applySort(col, dir) {
440
571
  this.sortField = this.colName(col);
441
572
  this.sortDir = dir;
@@ -541,11 +672,6 @@ export class MrdTable {
541
672
  this.requestedPages = requested;
542
673
  }
543
674
  // ── Filter helpers ─────────────────────────────────────────────────────────
544
- handleFilterToggle() {
545
- this.filterMode = !this.filterMode;
546
- if (!this.filterMode)
547
- this.closeFilterPopup();
548
- }
549
675
  handleFilterOpen(col, e) {
550
676
  e.stopPropagation();
551
677
  const btn = e.currentTarget;
@@ -652,7 +778,7 @@ export class MrdTable {
652
778
  this.aggregations = null;
653
779
  this.aggregationsTotal = null;
654
780
  this.aggregationsPending = true;
655
- if (this.totalElements > 0 || this.minKnownTotal > 0) {
781
+ if (this.isPaginated) {
656
782
  this.resetPages();
657
783
  this.emitPagesForWindow(this.renderStart, this.renderEnd);
658
784
  }
@@ -667,7 +793,7 @@ export class MrdTable {
667
793
  this.aggregations = null;
668
794
  this.aggregationsTotal = null;
669
795
  this.aggregationsPending = true;
670
- if (this.totalElements > 0 || this.minKnownTotal > 0) {
796
+ if (this.isPaginated) {
671
797
  this.resetPages();
672
798
  this.emitPagesForWindow(this.renderStart, this.renderEnd);
673
799
  }
@@ -677,7 +803,7 @@ export class MrdTable {
677
803
  this.aggregations = null;
678
804
  this.aggregationsTotal = null;
679
805
  this.aggregationsPending = true;
680
- if (this.totalElements > 0 || this.minKnownTotal > 0) {
806
+ if (this.isPaginated) {
681
807
  this.resetPages();
682
808
  this.emitPagesForWindow(this.renderStart, this.renderEnd);
683
809
  }
@@ -713,8 +839,7 @@ export class MrdTable {
713
839
  // ── Render: toolbar ────────────────────────────────────────────────────────
714
840
  renderToolbar() {
715
841
  var _a;
716
- return (h(Toolbar, { locale: this.locale, filterMode: this.filterMode, filterCount: this.activeFilters.size, actions: this.tableActions, views: this.allViews, activeViewIdx: this.activeViewIdx, viewPopoverOpen: this.viewPopoverOpen, createPickerOpen: this.createPickerOpen, createTypes: (_a = this.item) === null || _a === void 0 ? void 0 : _a.createTypes, cb: {
717
- onFilterToggle: () => this.handleFilterToggle(),
842
+ return (h(Toolbar, { locale: this.locale, filterCount: this.activeFilters.size, actions: this.tableActions, views: this.allViews, activeViewIdx: this.activeViewIdx, viewPopoverOpen: this.viewPopoverOpen, createPickerOpen: this.createPickerOpen, createTypes: (_a = this.item) === null || _a === void 0 ? void 0 : _a.createTypes, cb: {
718
843
  onClearAllFilters: () => this.clearAllFilters(),
719
844
  onViewSwitch: idx => this.handleViewSwitch(idx),
720
845
  onToggleViewPopover: e => this.toggleViewPopover(e),
@@ -747,7 +872,7 @@ export class MrdTable {
747
872
  // ── Render: footer ────────────────────────────────────────────────────────
748
873
  renderFooter(rowCount, effectiveTotal, isTotalKnown = true) {
749
874
  // Non-paginated mode: totalElements=0 and no paginated data loaded yet
750
- if (this.totalElements === 0 && this.loadedPages.size === 0) {
875
+ if (!this.isPaginated) {
751
876
  const count = rowCount !== null && rowCount !== void 0 ? rowCount : 0;
752
877
  if (count === 0)
753
878
  return null;
@@ -781,17 +906,18 @@ export class MrdTable {
781
906
  // ── Non-paginated mode ──────────────────────────────────────────────────
782
907
  // Only enter non-paginated mode when totalElements is 0 AND no paginated data
783
908
  // has been loaded yet — prevents the wrong branch when the host omits totalElements.
784
- if (this.totalElements === 0 && this.loadedPages.size === 0) {
785
- 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 => {
909
+ if (!this.isPaginated) {
910
+ return (h(Host, { class: { 'mrd-table--resizing': this.resizingCol !== null } }, this.renderToolbar(), h("div", { class: "mrd-table" }, h("table", { class: "mrd-table__table", style: this.lockedTableStyle() }, h("thead", null, h("tr", null, this.columns.map((col, idx) => {
786
911
  var _a;
787
912
  const name = this.colName(col);
788
913
  const isFiltered = this.activeFilters.has(name);
914
+ const isInteractive = !NON_INTERACTIVE_TYPES.has(this.colDataType(col));
789
915
  const cls = [
790
916
  'mrd-table__header',
791
917
  isFiltered ? 'mrd-table__header--filtered' : '',
792
- this.filterMode ? 'mrd-table__header--sortable' : '',
918
+ isInteractive ? 'mrd-table__header--sortable' : '',
793
919
  ].filter(Boolean).join(' ');
794
- return (h("th", { class: cls, onClick: this.filterMode ? (e) => this.handleFilterOpen(col, e) : undefined }, h("span", { class: "mrd-table__header-label" }, (_a = col.label) !== null && _a !== void 0 ? _a : ''), isFiltered && h(FilterIcon, null)));
920
+ return (h("th", { class: cls, style: this.colWidths[idx] ? { width: `${this.colWidths[idx]}px` } : undefined, onClick: isInteractive ? (e) => this.handleFilterOpen(col, e) : undefined }, h("span", { class: "mrd-table__header-label" }, (_a = col.label) !== null && _a !== void 0 ? _a : ''), isFiltered && h(FilterIcon, null), this.renderColResizer(idx)));
795
921
  }))), 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 => this.renderCell(col, row)))))), this.renderTotalsRow()), (!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(), this.renderTextblockModal(), this.renderJsonModal()));
796
922
  }
797
923
  // ── Paginated / virtual-scroll mode ────────────────────────────────────
@@ -805,9 +931,13 @@ export class MrdTable {
805
931
  const colCount = this.columns.length;
806
932
  const topSpacerHeight = this.renderStart * this.rowHeight;
807
933
  const bottomSpacerHeight = Math.max(0, (effectiveTotal - 1 - clampedEnd) * this.rowHeight);
808
- const tableStyle = this.colWidths.length > 0
809
- ? { tableLayout: 'fixed' }
810
- : undefined;
934
+ // Once the widths are frozen the table gets an explicit pixel width (the sum of the
935
+ // locked columns) instead of the stylesheet's `min-width: max-content`. max-content is
936
+ // re-derived from the cell contents, so every page that scrolled in with a longer value
937
+ // widened the table and re-proportioned all columns — the jitter this replaces.
938
+ // `minWidth: 100%` keeps a narrow table stretched to the container (and still responsive
939
+ // to container resizes) without ever consulting the content again.
940
+ const tableStyle = this.lockedTableStyle();
811
941
  const renderedRows = [];
812
942
  for (let i = this.renderStart; i <= clampedEnd; i++) {
813
943
  const row = this.getRow(i);
@@ -832,7 +962,7 @@ export class MrdTable {
832
962
  }) }, this.columns.map(col => this.renderCell(col, row))));
833
963
  }
834
964
  }
835
- 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) => {
965
+ return (h(Host, { class: { 'mrd-table--resizing': this.resizingCol !== 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) => {
836
966
  var _a;
837
967
  const name = this.colName(col);
838
968
  const isActive = this.sortField === name;
@@ -844,7 +974,7 @@ export class MrdTable {
844
974
  isActive ? `mrd-table__header--sorted-${this.sortDir}` : '',
845
975
  isFiltered ? 'mrd-table__header--filtered' : '',
846
976
  ].filter(Boolean).join(' ');
847
- return (h("th", { class: cls, style: this.colWidths[idx] ? { width: `${this.colWidths[idx]}px` } : undefined, onClick: isInteractive ? ((e) => this.filterMode ? this.handleFilterOpen(col, e) : this.handleSortClick(col)) : undefined }, h("span", { class: "mrd-table__header-label" }, (_a = col.label) !== null && _a !== void 0 ? _a : ''), isInteractive && isActive && (h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, this.sortDir === 'asc' ? '▲' : '▼')), isInteractive && !isActive && !this.filterMode && (h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, "\u21C5")), isInteractive && isFiltered && h(FilterIcon, null)));
977
+ return (h("th", { class: cls, style: this.colWidths[idx] ? { width: `${this.colWidths[idx]}px` } : undefined, onClick: isInteractive ? ((e) => this.handleFilterOpen(col, e)) : undefined }, h("span", { class: "mrd-table__header-label" }, (_a = col.label) !== null && _a !== void 0 ? _a : ''), isInteractive && isActive && (h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, this.sortDir === 'asc' ? '▲' : '▼')), isInteractive && !isActive && (h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, "\u21C5")), isInteractive && isFiltered && h(FilterIcon, null), this.renderColResizer(idx)));
848
978
  }))), 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 })))), this.renderTotalsRow())), effectiveTotal === 0 && this.loadedPages.has(0) && (h("p", { class: "mrd-table__empty" }, t('no_results', this.locale))), effectiveTotal > 0 && this.renderFooter(undefined, effectiveTotal, this.isTotalKnown()), this.renderFilterPopup(), this.renderTextblockModal(), this.renderJsonModal()));
849
979
  }
850
980
  renderTextblockModal() {
@@ -1066,9 +1196,9 @@ export class MrdTable {
1066
1196
  "renderStart": {},
1067
1197
  "renderEnd": {},
1068
1198
  "colWidths": {},
1199
+ "resizingCol": {},
1069
1200
  "sortField": {},
1070
1201
  "sortDir": {},
1071
- "filterMode": {},
1072
1202
  "activeFilters": {},
1073
1203
  "openFilterCol": {},
1074
1204
  "pendingFilter": {},
@@ -42,7 +42,10 @@ export const TableCell = ({ col, row, locale, onDownload, onOpenTextblock, onOpe
42
42
  return (h("td", { class: "mrd-table__cell" }, preview, h("button", { class: "mrd-table__textblock-btn", onClick: (e) => { e.stopPropagation(); onOpenTextblock(full); }, "aria-label": t('textblock_show_more', locale) }, "\u22EF")));
43
43
  }
44
44
  const value = CellRenderer.render(col, row, locale);
45
- return (h("td", { class: `mrd-table__cell${isNumeric ? ' mrd-table__cell--numeric' : ''}` }, value));
45
+ return (
46
+ // title: columns are locked to the width measured on page 0, so a longer value on a
47
+ // later page is ellipsised rather than widening the column — the tooltip keeps it readable.
48
+ h("td", { class: `mrd-table__cell${isNumeric ? ' mrd-table__cell--numeric' : ''}`, title: value || undefined }, value));
46
49
  };
47
50
  /** Formats the aggregation total for one column, '' when not applicable. */
48
51
  export function formatAggregationValue(col, aggregations, locale) {
@@ -84,11 +87,11 @@ export const JsonModal = ({ html, locale, onClose }) => {
84
87
  return null;
85
88
  return (h("div", { class: "mrd-table__modal-backdrop", onClick: onClose, role: "dialog", "aria-modal": "true" }, h("div", { class: "mrd-table__modal", onClick: (e) => e.stopPropagation() }, h("button", { class: "mrd-table__modal-close", onClick: onClose, "aria-label": t('close', locale) }, "\u2715"), h("pre", { class: "mrd-table__modal-json", innerHTML: html }))));
86
89
  };
87
- export const Toolbar = ({ locale, filterMode, filterCount, actions, views, activeViewIdx, viewPopoverOpen, createPickerOpen, createTypes, cb }) => {
90
+ export const Toolbar = ({ locale, filterCount, actions, views, activeViewIdx, viewPopoverOpen, createPickerOpen, createTypes, cb }) => {
88
91
  var _a;
89
92
  const hasActions = actions.length > 0;
90
93
  const hasViewSwitcher = views.length > 1;
91
- 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${filterMode ? ' mrd-table__filter-toggle--active' : ''}`, onClick: cb.onFilterToggle }, 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" }, filterMode ? t('table_filter_hide', locale) : t('table_filter', locale), filterCount > 0 ? ` (${filterCount} ${t('table_filter_active', locale)})` : '')), filterCount > 0 && (h("button", { class: "mrd-table__action mrd-table__action--secondary", onClick: cb.onClearAllFilters }, 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', locale))))), hasViewSwitcher && (h("div", { class: "mrd-table__toolbar-center" }, h("div", { class: "mrd-table__view-switcher" }, h("button", { class: "mrd-table__view-arrow", "aria-label": "Previous view", onClick: () => cb.onViewSwitch((activeViewIdx - 1 + views.length) % views.length) }, "\u25C0"), h("div", { class: "mrd-table__view-title-wrap" }, h("button", { class: "mrd-table__view-title", onClick: (e) => cb.onToggleViewPopover(e) }, (_a = views[activeViewIdx]) === null || _a === void 0 ? void 0 : _a.label), viewPopoverOpen && (h("div", { class: "mrd-table__view-popover", onClick: (e) => e.stopPropagation() }, views.map((v, i) => (h("button", { key: String(i), class: `mrd-table__view-popover-item${i === activeViewIdx ? ' mrd-table__view-popover-item--active' : ''}`, onClick: () => {
94
+ return (h("div", { class: "mrd-table__toolbar" }, h("div", { class: "mrd-table__toolbar-left" }, filterCount > 0 && (h("button", { class: "mrd-table__action mrd-table__action--secondary mrd-table__filter-clear", onClick: cb.onClearAllFilters }, 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__filter-clear-label" }, t('table_filter_clear_all', locale), " (", filterCount, ")")))), hasViewSwitcher && (h("div", { class: "mrd-table__toolbar-center" }, h("div", { class: "mrd-table__view-switcher" }, h("button", { class: "mrd-table__view-arrow", "aria-label": "Previous view", onClick: () => cb.onViewSwitch((activeViewIdx - 1 + views.length) % views.length) }, "\u25C0"), h("div", { class: "mrd-table__view-title-wrap" }, h("button", { class: "mrd-table__view-title", onClick: (e) => cb.onToggleViewPopover(e) }, (_a = views[activeViewIdx]) === null || _a === void 0 ? void 0 : _a.label), viewPopoverOpen && (h("div", { class: "mrd-table__view-popover", onClick: (e) => e.stopPropagation() }, views.map((v, i) => (h("button", { key: String(i), class: `mrd-table__view-popover-item${i === activeViewIdx ? ' mrd-table__view-popover-item--active' : ''}`, onClick: () => {
92
95
  if (i !== activeViewIdx)
93
96
  cb.onViewSwitch(i);
94
97
  cb.onCloseViewPopover();
@@ -14,13 +14,18 @@ export const MAX_RETRY_ATTEMPTS = 3;
14
14
  export function visibleCount(tableHeight, rowHeight) {
15
15
  return Math.ceil(tableHeight / rowHeight);
16
16
  }
17
- /** Render window (with buffer) for the given scroll offset. */
17
+ /** Render window (with buffer) for the given scroll offset.
18
+ * total <= 0 means "not known yet" (no totalElements prop, no aggregation total and no page
19
+ * loaded — e.g. right after a filter reset), *not* "no rows". Clamping to total - 1 would
20
+ * collapse the window to renderEnd = -1, so nothing renders and no page is ever requested;
21
+ * the window is left unclamped instead and the render pass clamps it against
22
+ * effectiveTotal once real data has landed. */
18
23
  export function computeWindow(scrollTop, rowHeight, tableHeight, total) {
19
24
  const visStart = Math.floor(scrollTop / rowHeight);
20
- const visEnd = Math.min(visStart + visibleCount(tableHeight, rowHeight), total - 1);
25
+ const unclamped = visStart + visibleCount(tableHeight, rowHeight) + BUFFER;
21
26
  return {
22
27
  renderStart: Math.max(0, visStart - BUFFER),
23
- renderEnd: Math.min(total - 1, visEnd + BUFFER),
28
+ renderEnd: total > 0 ? Math.min(total - 1, unclamped) : unclamped,
24
29
  };
25
30
  }
26
31
  /** First/last page (0-based) covering the given row window. */
@@ -53,166 +53,6 @@ export function formatTime(value, locale) {
53
53
  date.setHours(hours, minutes, 0, 0);
54
54
  return new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit' }).format(date);
55
55
  }
56
- /**
57
- * Locale-aware date input support.
58
- *
59
- * A native <input type="date"> always displays in the browser/OS locale, so the
60
- * date fields render a plain text input instead and use these helpers to format
61
- * and parse the value in the locale the host passed down — the same approach
62
- * mrd-number-field takes with formatNumber / parseLocalizedNumber.
63
- */
64
- /** 31 December 2026, 13:45 — day > 12 so day/month order is unambiguous. */
65
- const PATTERN_SAMPLE = new Date(2026, 11, 31, 13, 45, 0);
66
- function dateTimeParts(locale, withTime) {
67
- const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
68
- if (withTime) {
69
- options.hour = '2-digit';
70
- options.minute = '2-digit';
71
- }
72
- return new Intl.DateTimeFormat(locale, options).formatToParts(PATTERN_SAMPLE);
73
- }
74
- /** Order in which day/month/year appear for a locale, e.g. ['day','month','year'] for nl-NL. */
75
- function datePartOrder(locale) {
76
- const order = dateTimeParts(locale, false)
77
- .filter(p => p.type === 'day' || p.type === 'month' || p.type === 'year')
78
- .map(p => p.type);
79
- return order.length === 3 ? order : ['day', 'month', 'year'];
80
- }
81
- /** The locale's am/pm markers, so 12-hour input can be parsed back. */
82
- function dayPeriodMarkers(locale) {
83
- const marker = (hour) => {
84
- var _a, _b;
85
- return (_b = (_a = new Intl.DateTimeFormat(locale, { hour: 'numeric', hour12: true })
86
- .formatToParts(new Date(2026, 0, 1, hour))
87
- .find(p => p.type === 'dayPeriod')) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '';
88
- };
89
- return { am: marker(9), pm: marker(21) };
90
- }
91
- /** Arabic-Indic and extended Arabic-Indic digits → ASCII, so typed input parses everywhere. */
92
- function toLatinDigits(str) {
93
- return str.replace(/[٠-٩۰-۹]/g, d => {
94
- const code = d.charCodeAt(0);
95
- return String(code >= 0x06f0 ? code - 0x06f0 : code - 0x0660);
96
- });
97
- }
98
- function pad(n, size = 2) {
99
- return String(n).padStart(size, '0');
100
- }
101
- /** Two-digit years: < 70 → 20xx, otherwise 19xx. */
102
- function expandYear(value, digits) {
103
- if (digits > 2)
104
- return value;
105
- return value < 70 ? 2000 + value : 1900 + value;
106
- }
107
- function isValidYmd(year, month, day) {
108
- if (year < 1 || month < 1 || month > 12 || day < 1)
109
- return false;
110
- return day <= new Date(year, month, 0).getDate();
111
- }
112
- /**
113
- * Placeholder showing the locale's date order, e.g. 'dd-mm-jjjj' (nl-NL) or
114
- * 'mm/dd/yyyy' (en-US). Pass `hour`/`minute` tokens to include the time part.
115
- */
116
- export function localeDatePattern(locale, tokens) {
117
- const withTime = !!(tokens.hour && tokens.minute);
118
- return dateTimeParts(locale, withTime)
119
- .map(part => {
120
- var _a, _b;
121
- switch (part.type) {
122
- case 'day': return tokens.day;
123
- case 'month': return tokens.month;
124
- case 'year': return tokens.year;
125
- case 'hour': return (_a = tokens.hour) !== null && _a !== void 0 ? _a : '';
126
- case 'minute': return (_b = tokens.minute) !== null && _b !== void 0 ? _b : '';
127
- case 'dayPeriod': return 'AM';
128
- case 'literal': return part.value;
129
- default: return '';
130
- }
131
- })
132
- .join('')
133
- .trim();
134
- }
135
- /** Pulls day/month/year out of the number groups a user typed, per locale order. */
136
- function ymdFromGroups(groups, locale) {
137
- const order = datePartOrder(locale);
138
- const values = {};
139
- order.forEach((type, i) => (values[type] = groups[i]));
140
- const year = expandYear(Number(values.year), values.year.length);
141
- const month = Number(values.month);
142
- const day = Number(values.day);
143
- return isValidYmd(year, month, day) ? { year, month, day } : null;
144
- }
145
- /**
146
- * Parses a date the user typed in their own locale format → 'YYYY-MM-DD'.
147
- * Accepts any separator, one- or two-digit day/month, two-digit years, and
148
- * plain ISO input (pasted values). Returns null when it is not a valid date.
149
- */
150
- export function parseLocalizedDate(input, locale) {
151
- const raw = toLatinDigits(String(input !== null && input !== void 0 ? input : '')).trim();
152
- if (!raw)
153
- return null;
154
- const groups = raw.match(/\d+/g);
155
- if (!groups || groups.length < 3)
156
- return null;
157
- const iso = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(raw);
158
- const parsed = iso
159
- ? (isValidYmd(+iso[1], +iso[2], +iso[3]) ? { year: +iso[1], month: +iso[2], day: +iso[3] } : null)
160
- : ymdFromGroups(groups.slice(0, 3), locale);
161
- if (!parsed)
162
- return null;
163
- return `${pad(parsed.year, 4)}-${pad(parsed.month)}-${pad(parsed.day)}`;
164
- }
165
- /**
166
- * Parses a date+time the user typed in their own locale format →
167
- * 'YYYY-MM-DDTHH:mm' in local time (the shape mrd-datetime-field converts to
168
- * UTC before emitting). A missing time defaults to 00:00; am/pm is recognised
169
- * both in English and in the locale's own markers.
170
- */
171
- export function parseLocalizedDateTime(input, locale) {
172
- const raw = toLatinDigits(String(input !== null && input !== void 0 ? input : '')).trim();
173
- if (!raw)
174
- return null;
175
- const groups = raw.match(/\d+/g);
176
- if (!groups || groups.length < 3)
177
- return null;
178
- const iso = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T ]|$)/.exec(raw);
179
- const date = iso
180
- ? (isValidYmd(+iso[1], +iso[2], +iso[3]) ? { year: +iso[1], month: +iso[2], day: +iso[3] } : null)
181
- : ymdFromGroups(groups.slice(0, 3), locale);
182
- if (!date)
183
- return null;
184
- let hour = groups.length > 3 ? Number(groups[3]) : 0;
185
- const minute = groups.length > 4 ? Number(groups[4]) : 0;
186
- const lower = raw.toLowerCase();
187
- const { am, pm } = dayPeriodMarkers(locale);
188
- const isPm = /p\.?\s?m\.?/.test(lower) || (!!pm && lower.includes(pm.toLowerCase()));
189
- const isAm = /a\.?\s?m\.?/.test(lower) || (!!am && lower.includes(am.toLowerCase()));
190
- if (isPm && hour < 12)
191
- hour += 12;
192
- if (isAm && hour === 12)
193
- hour = 0;
194
- if (hour > 23 || minute > 59)
195
- return null;
196
- return `${pad(date.year, 4)}-${pad(date.month)}-${pad(date.day)}T${pad(hour)}:${pad(minute)}`;
197
- }
198
- /**
199
- * 'YYYY-MM-DD' → localized display string. Parsed as a *local* date, unlike
200
- * formatDate(), which would read a date-only string as UTC midnight and shift
201
- * the day in negative-offset timezones.
202
- */
203
- export function formatIsoDate(iso, locale) {
204
- const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(iso !== null && iso !== void 0 ? iso : ''));
205
- if (!m)
206
- return '';
207
- return formatDate(new Date(+m[1], +m[2] - 1, +m[3]), locale);
208
- }
209
- /** 'YYYY-MM-DDTHH:mm' in local time → localized display string. */
210
- export function formatLocalDateTime(local, locale) {
211
- const m = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/.exec(String(local !== null && local !== void 0 ? local : ''));
212
- if (!m)
213
- return '';
214
- return formatDateTime(new Date(+m[1], +m[2] - 1, +m[3], +m[4], +m[5]), locale);
215
- }
216
56
  export function parseLocalizedNumber(str, locale) {
217
57
  var _a, _b, _c, _d;
218
58
  if (!str || !str.trim())