vgapp 1.5.3 → 1.5.5

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.
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Описание: разметка Fixed Header с отдельными слоями заголовка и тела VGTable.
3
- * Возможности: перенос настоящего thead без клона, автоматическая синхронизация ширин колонок и горизонтальной прокрутки.
3
+ * Возможности: перенос настоящего thead без клона, intrinsic-минимумы и синхронизация ширин колонок и горизонтальной прокрутки.
4
4
  */
5
5
 
6
6
  const MODE_CLASSES = ['vg-table-wrapper--sticky-container', 'vg-table-wrapper--sticky-page'];
7
7
  const CONTAINER_MODE_CLASSES = ['vg-table-container--sticky-container', 'vg-table-container--sticky-page'];
8
- const STYLE_PROPERTIES = ['--vg-table-sticky-top', '--vg-table-sticky-max-height'];
8
+ const CONTENT_MIN_WIDTH_PROPERTY = '--vg-table-sticky-content-min-width';
9
+ const STYLE_PROPERTIES = ['--vg-table-sticky-top', '--vg-table-sticky-max-height', CONTENT_MIN_WIDTH_PROPERTY];
9
10
  const GENERATED_LAYER_ATTRIBUTE = 'data-vg-table-sticky-generated';
10
11
 
11
12
  class _stickyHeader {
@@ -67,22 +68,39 @@ class _stickyHeader {
67
68
  return this._headerTable || this._element;
68
69
  }
69
70
 
70
- getBody() {
71
- return this._body;
72
- }
71
+ getBody() {
72
+ return this._body;
73
+ }
74
+
75
+ refreshIntrinsicMinimums() {
76
+ const widths = this._measureIntrinsicColumnMinimums();
77
+ this._getHeaders().forEach((header, index) => {
78
+ const key = this._columnKey(header, index);
79
+ if (this._hasDeclaredWidth(header) || this._hardColumnWidths.has(key) || !(widths[index] > 0)) return;
80
+ this._columnMinimumWidths.set(key, Math.max(this._columnMinimumWidths.get(key) || 0, widths[index]));
81
+ });
82
+ return this.refresh();
83
+ }
73
84
 
74
85
  syncScroll() {
75
86
  this._syncScroll();
76
87
  return this;
77
88
  }
78
89
 
79
- refresh() {
80
- if (!this._header || !this._headerTable || !this._body) return this;
81
-
82
- const widths = this._resolveLayoutWidths();
83
- this._applyColgroup(this._headerTable, widths);
84
- this._applyColgroup(this._element, widths);
85
- const scrollbar = Math.max(0, this._body.offsetWidth - this._body.clientWidth);
90
+ refresh() {
91
+ if (!this._header || !this._headerTable || !this._body) return this;
92
+
93
+ let widths = this._resolveLayoutWidths();
94
+ const attempts = this._getHeaders().length + 2;
95
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
96
+ this._applyColgroup(this._headerTable, widths);
97
+ this._applyColgroup(this._element, widths);
98
+ const headerChanged = this._captureOverflowingHeaderMinimums();
99
+ const contentChanged = this._syncContentMinimumWidth(widths);
100
+ if (!headerChanged && !contentChanged) break;
101
+ widths = this._resolveLayoutWidths();
102
+ }
103
+ const scrollbar = Math.max(0, this._body.offsetWidth - this._body.clientWidth);
86
104
  this._header.style.paddingInlineEnd = `${scrollbar}px`;
87
105
  this._header.style.setProperty('--vg-table-sticky-scrollbar-width', `${scrollbar}px`);
88
106
  this._header.classList.toggle('vg-table-header--scrollbar', scrollbar > 0);
@@ -207,6 +225,36 @@ class _stickyHeader {
207
225
  });
208
226
  }
209
227
 
228
+ _measureIntrinsicColumnMinimums() {
229
+ if (!this._wrapper || !this._head) return [];
230
+
231
+ const probe = this._element.cloneNode(true);
232
+ probe.removeAttribute('data-vg-table');
233
+ probe.removeAttribute('data-vg-table-sticky-header');
234
+ probe.setAttribute('aria-hidden', 'true');
235
+ probe.querySelector(':scope > colgroup[data-vg-table-sticky-colgroup]')?.remove();
236
+ probe.insertBefore(this._head.cloneNode(true), probe.tBodies[0] || null);
237
+ probe.querySelectorAll('thead th').forEach((header) => header.style.setProperty('white-space', 'nowrap', 'important'));
238
+ [
239
+ ['position', 'absolute'],
240
+ ['inset', '0 auto auto 0'],
241
+ ['visibility', 'hidden'],
242
+ ['pointer-events', 'none'],
243
+ ['width', 'min-content'],
244
+ ['min-width', '0'],
245
+ ['max-width', 'none'],
246
+ ['table-layout', 'auto'],
247
+ ].forEach(([property, value]) => probe.style.setProperty(property, value, 'important'));
248
+
249
+ this._wrapper.append(probe);
250
+ try {
251
+ const rows = Array.from(probe.tHead?.rows || []);
252
+ return this._measureRow(rows.at(-1));
253
+ } finally {
254
+ probe.remove();
255
+ }
256
+ }
257
+
210
258
  _rememberColumnGeometry(widths) {
211
259
  const headers = this._getHeaders();
212
260
  const sourceCols = Array.from(this._element.querySelector(':scope > colgroup:not([data-vg-table-sticky-colgroup])')?.children || []);
@@ -309,6 +357,35 @@ class _stickyHeader {
309
357
  }
310
358
  }
311
359
 
360
+ _captureOverflowingHeaderMinimums() {
361
+ let changed = false;
362
+ this._getHeaders().forEach((header, index) => {
363
+ const key = this._columnKey(header, index);
364
+ if (this._hasDeclaredWidth(header) || this._hardColumnWidths.has(key)) return;
365
+
366
+ const rendered = header.getBoundingClientRect?.().width || header.clientWidth || 0;
367
+ const required = header.scrollWidth || 0;
368
+ const current = this._columnMinimumWidths.get(key) || 0;
369
+ if (required <= rendered + 1 || required <= current + 1) return;
370
+
371
+ this._columnMinimumWidths.set(key, required);
372
+ changed = true;
373
+ });
374
+ return changed;
375
+ }
376
+
377
+ _syncContentMinimumWidth(widths) {
378
+ const total = widths.reduce((sum, width) => sum + width, 0);
379
+ const viewport = this._body?.clientWidth || this._container?.clientWidth || total;
380
+ const previous = this._previousStyles.get(CONTENT_MIN_WIDTH_PROPERTY);
381
+ const required = Math.max(total, this._element?.scrollWidth || 0);
382
+ const next = required > viewport + 1 ? `${Math.ceil(required)}px` : previous;
383
+ const current = this._container.style.getPropertyValue(CONTENT_MIN_WIDTH_PROPERTY);
384
+ if (next) this._container.style.setProperty(CONTENT_MIN_WIDTH_PROPERTY, next);
385
+ else this._container.style.removeProperty(CONTENT_MIN_WIDTH_PROPERTY);
386
+ return current !== (next || '');
387
+ }
388
+
312
389
  _getHeaders() {
313
390
  const rows = Array.from(this._head?.rows || []);
314
391
  return rows.length ? Array.from(rows.at(-1).cells || []) : [];
@@ -340,9 +417,10 @@ class _stickyHeader {
340
417
  }
341
418
 
342
419
  _readTableMinimumWidth() {
343
- if (typeof getComputedStyle !== 'function') return 0;
344
- const value = Number.parseFloat(getComputedStyle(this._element).minWidth || '0');
345
- return Number.isFinite(value) ? value : 0;
420
+ const generated = Number.parseFloat(this._container?.style.getPropertyValue(CONTENT_MIN_WIDTH_PROPERTY) || '0');
421
+ if (typeof getComputedStyle !== 'function') return Number.isFinite(generated) ? generated : 0;
422
+ const declared = Number.parseFloat(getComputedStyle(this._element).minWidth || '0');
423
+ return Math.max(Number.isFinite(generated) ? generated : 0, Number.isFinite(declared) ? declared : 0);
346
424
  }
347
425
 
348
426
  _syncScroll() {
@@ -146,11 +146,12 @@ class VGTable extends BaseModule {
146
146
  if (!this._complex && !this._sorting && this._params.sort.enabled === true && this._params.rowReorder.enabled !== true) {
147
147
  this._sorting = new _sorting(this._element, Object.assign({}, this._params.sort, {remote: this._isRemote}), this._stickyHeader?.getHeaderTable());
148
148
  this._sorting.init();
149
- this._element.addEventListener(
150
- 'sortchange.vg.table',
151
- this._isRemote ? this._boundRemoteSortChange : this._boundLocalSortChange
152
- );
153
- }
149
+ this._element.addEventListener(
150
+ 'sortchange.vg.table',
151
+ this._isRemote ? this._boundRemoteSortChange : this._boundLocalSortChange
152
+ );
153
+ }
154
+ this._stickyHeader?.refreshIntrinsicMinimums?.();
154
155
 
155
156
  // Фиксируем настоящие ячейки через native sticky, включая отдельный слой Fixed Header
156
157
  if (!this._complex && !this._fixedColumns && this._params.fixedColumns.enabled === true) {
@@ -1136,7 +1137,7 @@ class VGTable extends BaseModule {
1136
1137
 
1137
1138
  _refreshColumnsLayout(structure = false) {
1138
1139
  this._sorting?.refresh?.(structure);
1139
- this._stickyHeader?.refresh?.();
1140
+ this._stickyHeader?.refreshIntrinsicMinimums?.();
1140
1141
  this._fixedColumns?.refresh?.();
1141
1142
  }
1142
1143
 
@@ -1425,11 +1426,11 @@ class VGTable extends BaseModule {
1425
1426
  _afterRemoteRender(detail = {}) {
1426
1427
  if (detail.renderedCount > 0) this._states?.clear();
1427
1428
  this._columns?.refresh?.();
1428
- this._sorting?.refresh?.();
1429
- this._expandable?.refresh?.();
1430
- this._rowReorder?.refresh?.();
1431
- this._stickyHeader?.refresh?.();
1432
- this._fixedColumns?.refresh?.();
1429
+ this._sorting?.refresh?.();
1430
+ this._expandable?.refresh?.();
1431
+ this._rowReorder?.refresh?.();
1432
+ this._stickyHeader?.refreshIntrinsicMinimums?.();
1433
+ this._fixedColumns?.refresh?.();
1433
1434
  }
1434
1435
 
1435
1436
  /**
@@ -57,9 +57,9 @@
57
57
 
58
58
  .vg-table-header__table,
59
59
  .vg-table-body > .vg-table {
60
- width: 100%;
61
- min-width: 100%;
62
- table-layout: fixed;
60
+ width: 100%;
61
+ min-width: max(100%, var(--vg-table-sticky-content-min-width, 0px));
62
+ table-layout: fixed;
63
63
  overflow: visible;
64
64
  border: 0;
65
65
  border-radius: 0;