vgapp 1.5.7 → 1.5.8

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 (109) hide show
  1. package/app/langs/en/buttons.json +17 -17
  2. package/app/langs/en/messages.json +36 -36
  3. package/app/langs/ru/buttons.json +17 -17
  4. package/app/langs/ru/messages.json +1 -1
  5. package/app/modules/vgalert/js/vgalert.js +7 -7
  6. package/app/modules/vgalert/scss/_variables.scss +16 -16
  7. package/app/modules/vgcollapse/scss/_variables.scss +7 -7
  8. package/app/modules/vgcollapse/scss/vgcollapse.scss +5 -5
  9. package/app/modules/vgdropdown/scss/_variables.scss +14 -14
  10. package/app/modules/vgdropdown/scss/vgdropdown.scss +5 -5
  11. package/app/modules/vgfilepreview/js/i18n.js +56 -56
  12. package/app/modules/vgfilepreview/js/renderers/image-modal.js +145 -145
  13. package/app/modules/vgfilepreview/js/renderers/image.js +92 -92
  14. package/app/modules/vgfilepreview/js/renderers/index.js +19 -19
  15. package/app/modules/vgfilepreview/js/renderers/office-modal.js +168 -168
  16. package/app/modules/vgfilepreview/js/renderers/office.js +79 -79
  17. package/app/modules/vgfilepreview/js/renderers/pdf-modal.js +260 -260
  18. package/app/modules/vgfilepreview/js/renderers/pdf.js +76 -76
  19. package/app/modules/vgfilepreview/js/renderers/playlist.js +71 -71
  20. package/app/modules/vgfilepreview/js/renderers/text-modal.js +343 -343
  21. package/app/modules/vgfilepreview/js/renderers/text.js +83 -83
  22. package/app/modules/vgfilepreview/js/renderers/zip-modal.js +522 -522
  23. package/app/modules/vgfilepreview/js/renderers/zip.js +89 -89
  24. package/app/modules/vgfilepreview/readme.md +68 -68
  25. package/app/modules/vgfiles/js/base.js +198 -198
  26. package/app/modules/vgfiles/js/droppable.js +17 -17
  27. package/app/modules/vgfiles/js/render.js +153 -153
  28. package/app/modules/vgfiles/js/sortable.js +52 -52
  29. package/app/modules/vgfiles/js/vgfiles.js +43 -43
  30. package/app/modules/vgfiles/readme.md +123 -123
  31. package/app/modules/vgfiles/scss/_mixins.scss +13 -13
  32. package/app/modules/vgfiles/scss/_variables.scss +10 -10
  33. package/app/modules/vgfiles/scss/vgfiles.scss +28 -28
  34. package/app/modules/vgformsender/js/hideshowpass.js +22 -22
  35. package/app/modules/vgformsender/readme.md +1 -1
  36. package/app/modules/vgformsender/scss/_variables.scss +5 -5
  37. package/app/modules/vgformsender/scss/vgformsender.scss +42 -42
  38. package/app/modules/vglawcookie/js/vglawcookie.js +19 -19
  39. package/app/modules/vglawcookie/scss/_variables.scss +13 -13
  40. package/app/modules/vglawcookie/scss/vglawcookie.scss +5 -5
  41. package/app/modules/vgmodal/js/vgmodal.interaction.js +216 -216
  42. package/app/modules/vgmodal/js/vgmodal.js +9 -9
  43. package/app/modules/vgmodal/js/vgmodal.minimized.js +227 -227
  44. package/app/modules/vgmodal/scss/_variables.scss +17 -17
  45. package/app/modules/vgmodal/scss/vgmodal.scss +77 -77
  46. package/app/modules/vgnav/js/vgnav.js +62 -62
  47. package/app/modules/vgnav/scss/_breakpoints.scss +36 -36
  48. package/app/modules/vgnav/scss/_hamburger.scss +5 -5
  49. package/app/modules/vgnav/scss/_placement.scss +5 -5
  50. package/app/modules/vgnav/scss/_toggle.scss +5 -5
  51. package/app/modules/vgnav/scss/_variables.scss +26 -26
  52. package/app/modules/vgnav/scss/vgnav.scss +41 -41
  53. package/app/modules/vgnestable/README.md +39 -39
  54. package/app/modules/vgrangeslider/js/skins.js +8 -8
  55. package/app/modules/vgrangeslider/js/vgrangeslider.js +29 -29
  56. package/app/modules/vgrangeslider/scss/_variables.scss +55 -55
  57. package/app/modules/vgrangeslider/scss/vgrangeslider.scss +239 -239
  58. package/app/modules/vgrollup/js/vgrollup.js +15 -15
  59. package/app/modules/vgrollup/scss/_variables.scss +15 -15
  60. package/app/modules/vgrollup/scss/vgrollup.scss +6 -6
  61. package/app/modules/vgselect/js/handlers.js +6 -6
  62. package/app/modules/vgselect/js/vgselect.js +167 -123
  63. package/app/modules/vgselect/readme.md +11 -11
  64. package/app/modules/vgselect/scss/_variables.scss +51 -51
  65. package/app/modules/vgselect/scss/vgselect.scss +1 -1
  66. package/app/modules/vgsidebar/js/vgsidebar.js +21 -21
  67. package/app/modules/vgsidebar/scss/_variables.scss +10 -10
  68. package/app/modules/vgsidebar/scss/vgsidebar.scss +5 -5
  69. package/app/modules/vgspy/js/vgspy.js +43 -43
  70. package/app/modules/vgtable/js/_filters.js +10 -6
  71. package/app/modules/vgtable/js/_options.js +16 -16
  72. package/app/modules/vgtable/js/_pagination.js +118 -118
  73. package/app/modules/vgtable/js/_responsive.js +79 -79
  74. package/app/modules/vgtable/js/_sticky-header.js +349 -280
  75. package/app/modules/vgtable/js/vgtable.js +98 -98
  76. package/app/modules/vgtable/scss/_sorting.scss +6 -6
  77. package/app/modules/vgtable/scss/_sticky-header.scss +31 -31
  78. package/app/modules/vgtable/scss/_variables.scss +6 -4
  79. package/app/modules/vgtable/scss/vgtable.scss +8 -4
  80. package/app/modules/vgtabs/js/vgtabs.js +91 -91
  81. package/app/modules/vgtabs/scss/_variables.scss +23 -23
  82. package/app/modules/vgtabs/scss/vgtabs.scss +5 -5
  83. package/app/modules/vgtoast/js/vgtoast.js +4 -4
  84. package/app/modules/vgtoast/scss/_variables.scss +13 -13
  85. package/app/modules/vgtoast/scss/vgtoast.scss +5 -5
  86. package/app/modules/vgtooltip/js/vgtooltip.js +127 -127
  87. package/app/modules/vgtooltip/scss/_variables.scss +10 -10
  88. package/app/modules/vgtooltip/scss/vgtooltip.scss +5 -5
  89. package/app/utils/js/components/audio-metadata.js +240 -240
  90. package/app/utils/js/components/backdrop.js +116 -116
  91. package/app/utils/js/components/file-icon.js +7 -7
  92. package/app/utils/js/components/file-preview.js +304 -304
  93. package/app/utils/js/components/responsive.js +165 -165
  94. package/app/utils/js/components/sanitize.js +150 -150
  95. package/app/utils/js/components/video-metadata.js +140 -140
  96. package/app/utils/scss/_backdrop.scss +5 -5
  97. package/app/utils/scss/_theme-default.scss +12 -12
  98. package/app/utils/scss/_theme.scss +7 -7
  99. package/app/utils/scss/default.scss +9 -9
  100. package/app/utils/scss/functions.scss +21 -21
  101. package/app/utils/scss/index.scss +4 -4
  102. package/app/utils/scss/mixin.scss +16 -16
  103. package/app/utils/scss/variables.scss +147 -147
  104. package/build/vgapp.css +1 -1
  105. package/build/vgapp.css.map +1 -1
  106. package/build/vgapp.js +1 -1
  107. package/build/vgapp.js.map +1 -1
  108. package/index.js +7 -7
  109. package/package.json +1 -1
@@ -1,12 +1,12 @@
1
- /**
2
- * Описание: разметка Fixed Header с отдельными слоями заголовка и тела VGTable.
3
- * Возможности: перенос настоящего thead без клона, intrinsic-минимумы и синхронизация ширин колонок и горизонтальной прокрутки.
4
- */
1
+ /**
2
+ * Описание: разметка Fixed Header с отдельными слоями заголовка и тела VGTable.
3
+ * Возможности: перенос настоящего thead без клона, intrinsic-минимумы и синхронизация ширин колонок и горизонтальной прокрутки.
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 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];
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];
10
10
  const GENERATED_LAYER_ATTRIBUTE = 'data-vg-table-sticky-generated';
11
11
 
12
12
  class _stickyHeader {
@@ -19,25 +19,26 @@ class _stickyHeader {
19
19
  this._header = null;
20
20
  this._headerTable = null;
21
21
  this._body = null;
22
- this._previousClasses = new Map();
23
- this._previousContainerClasses = new Map();
24
- this._previousStyles = new Map();
25
- this._columnWeights = new Map();
26
- this._hardColumnWidths = new Map();
22
+ this._previousClasses = new Map();
23
+ this._previousContainerClasses = new Map();
24
+ this._previousStyles = new Map();
25
+ this._columnWeights = new Map();
26
+ this._hardColumnWidths = new Map();
27
27
  this._columnMinimumWidths = new Map();
28
- this._hasSourceColgroup = false;
29
- this._resizeObserver = null;
30
- this._resizeFrame = null;
31
- this._boundScroll = this._syncScroll.bind(this);
32
- this._boundResize = this._scheduleRefresh.bind(this);
33
- this._boundPageScroll = this._syncStickyState.bind(this);
28
+ this._autoLayoutWidths = null;
29
+ this._hasSourceColgroup = false;
30
+ this._resizeObserver = null;
31
+ this._resizeFrame = null;
32
+ this._boundScroll = this._syncScroll.bind(this);
33
+ this._boundResize = this._scheduleRefresh.bind(this);
34
+ this._boundPageScroll = this._syncStickyState.bind(this);
34
35
  }
35
36
 
36
37
  init() {
37
- if (!this._wrapper || !this._container || !this._head) return this;
38
-
39
- const widths = this._measureColumns();
40
- this._rememberColumnGeometry(widths);
38
+ if (!this._wrapper || !this._container || !this._head) return this;
39
+
40
+ const widths = this._measureColumns();
41
+ this._rememberColumnGeometry(widths);
41
42
  MODE_CLASSES.forEach((className) => this._previousClasses.set(className, this._wrapper.classList.contains(className)));
42
43
  CONTAINER_MODE_CLASSES.forEach((className) => this._previousContainerClasses.set(className, this._container.classList.contains(className)));
43
44
  STYLE_PROPERTIES.forEach((property) => this._previousStyles.set(property, this._container.style.getPropertyValue(property)));
@@ -50,17 +51,17 @@ class _stickyHeader {
50
51
  this._container.style.setProperty('--vg-table-sticky-max-height', this._toCssLength(this._params.maxHeight, '24rem'));
51
52
  this._element.setAttribute('data-vg-table-sticky-header', this._params.mode);
52
53
 
53
- this._buildLayers(widths);
54
- this._body.addEventListener('scroll', this._boundScroll, {passive: true});
55
- if (typeof ResizeObserver !== 'undefined') {
56
- this._resizeObserver = new ResizeObserver(this._boundResize);
57
- this._resizeObserver.observe(this._container);
58
- this._resizeObserver.observe(this._body);
59
- } else if (typeof window !== 'undefined') {
60
- window.addEventListener('resize', this._boundResize);
61
- }
62
- if (this._params.mode === 'page' && typeof window !== 'undefined') window.addEventListener('scroll', this._boundPageScroll, {passive: true});
63
- this.refresh();
54
+ this._buildLayers(widths);
55
+ this._body.addEventListener('scroll', this._boundScroll, {passive: true});
56
+ if (typeof ResizeObserver !== 'undefined') {
57
+ this._resizeObserver = new ResizeObserver(this._boundResize);
58
+ this._resizeObserver.observe(this._container);
59
+ this._resizeObserver.observe(this._body);
60
+ } else if (typeof window !== 'undefined') {
61
+ window.addEventListener('resize', this._boundResize);
62
+ }
63
+ if (this._params.mode === 'page' && typeof window !== 'undefined') window.addEventListener('scroll', this._boundPageScroll, {passive: true});
64
+ this.refresh();
64
65
  return this;
65
66
  }
66
67
 
@@ -68,18 +69,47 @@ class _stickyHeader {
68
69
  return this._headerTable || this._element;
69
70
  }
70
71
 
71
- getBody() {
72
- return this._body;
73
- }
74
-
72
+ getBody() {
73
+ return this._body;
74
+ }
75
+
75
76
  refreshIntrinsicMinimums() {
77
+ const declaredMinimum = this._previousStyles.get(CONTENT_MIN_WIDTH_PROPERTY);
78
+ if (declaredMinimum) this._container.style.setProperty(CONTENT_MIN_WIDTH_PROPERTY, declaredMinimum);
79
+ else this._container.style.removeProperty(CONTENT_MIN_WIDTH_PROPERTY);
80
+
81
+ const autoWidths = this._measureAutoLayoutColumns();
76
82
  const widths = this._measureIntrinsicColumnMinimums();
83
+ const activeKeys = new Set();
77
84
  this._getHeaders().forEach((header, index) => {
78
85
  const key = this._columnKey(header, index);
86
+ activeKeys.add(key);
79
87
  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]));
88
+ // AJAX может как расширить, так и сузить обычное содержимое. Пустой th
89
+ // является служебным заголовком (checkbox/actions) и должен сохранять
90
+ // уже измеренную по body ширину, иначе колонка схлопнется.
91
+ const previousMinimum = this._columnMinimumWidths.get(key) || 0;
92
+ const minimum = this._isEmptyHeader(header)
93
+ ? Math.max(previousMinimum, widths[index])
94
+ : widths[index];
95
+ this._columnMinimumWidths.set(key, minimum);
96
+ this._columnWeights.set(key, this._isEmptyHeader(header)
97
+ ? Math.max(this._columnWeights.get(key) || 0, minimum)
98
+ : widths[index]);
99
+ });
100
+ [this._columnWeights, this._hardColumnWidths, this._columnMinimumWidths].forEach((geometry) => {
101
+ Array.from(geometry.keys()).forEach((key) => {
102
+ if (!activeKeys.has(key)) geometry.delete(key);
103
+ });
81
104
  });
82
- return this.refresh();
105
+ this._autoLayoutWidths = autoWidths.length === this._getHeaders().length && autoWidths.every((width) => width > 0)
106
+ ? autoWidths
107
+ : null;
108
+ try {
109
+ return this.refresh();
110
+ } finally {
111
+ this._autoLayoutWidths = null;
112
+ }
83
113
  }
84
114
 
85
115
  syncScroll() {
@@ -87,20 +117,20 @@ class _stickyHeader {
87
117
  return this;
88
118
  }
89
119
 
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);
120
+ refresh() {
121
+ if (!this._header || !this._headerTable || !this._body) return this;
122
+
123
+ let widths = this._resolveLayoutWidths();
124
+ const attempts = this._getHeaders().length + 2;
125
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
126
+ this._applyColgroup(this._headerTable, widths);
127
+ this._applyColgroup(this._element, widths);
128
+ const headerChanged = this._captureOverflowingHeaderMinimums();
129
+ const contentChanged = this._syncContentMinimumWidth(widths);
130
+ if (!headerChanged && !contentChanged) break;
131
+ widths = this._resolveLayoutWidths();
132
+ }
133
+ const scrollbar = Math.max(0, this._body.offsetWidth - this._body.clientWidth);
104
134
  this._header.style.paddingInlineEnd = `${scrollbar}px`;
105
135
  this._header.style.setProperty('--vg-table-sticky-scrollbar-width', `${scrollbar}px`);
106
136
  this._header.classList.toggle('vg-table-header--scrollbar', scrollbar > 0);
@@ -110,17 +140,17 @@ class _stickyHeader {
110
140
  return this;
111
141
  }
112
142
 
113
- dispose() {
114
- if (this._body) this._body.removeEventListener('scroll', this._boundScroll);
115
- this._resizeObserver?.disconnect();
116
- if (this._resizeFrame !== null && typeof cancelAnimationFrame === 'function') {
117
- cancelAnimationFrame(this._resizeFrame);
118
- this._resizeFrame = null;
119
- }
120
- if (typeof window !== 'undefined') {
121
- if (!this._resizeObserver) window.removeEventListener('resize', this._boundResize);
122
- window.removeEventListener('scroll', this._boundPageScroll);
123
- }
143
+ dispose() {
144
+ if (this._body) this._body.removeEventListener('scroll', this._boundScroll);
145
+ this._resizeObserver?.disconnect();
146
+ if (this._resizeFrame !== null && typeof cancelAnimationFrame === 'function') {
147
+ cancelAnimationFrame(this._resizeFrame);
148
+ this._resizeFrame = null;
149
+ }
150
+ if (typeof window !== 'undefined') {
151
+ if (!this._resizeObserver) window.removeEventListener('resize', this._boundResize);
152
+ window.removeEventListener('scroll', this._boundPageScroll);
153
+ }
124
154
  this._header?.classList.remove('is-stuck');
125
155
  if (this._head && this._headerTable?.contains(this._head)) this._element.prepend(this._head);
126
156
  this._header?.remove();
@@ -135,25 +165,25 @@ class _stickyHeader {
135
165
  CONTAINER_MODE_CLASSES.forEach((className) => {
136
166
  this._container.classList.toggle(className, this._previousContainerClasses.get(className) === true);
137
167
  });
138
- STYLE_PROPERTIES.forEach((property) => {
168
+ STYLE_PROPERTIES.forEach((property) => {
139
169
  const previous = this._previousStyles.get(property);
140
170
  if (previous) this._container.style.setProperty(property, previous);
141
171
  else this._container.style.removeProperty(property);
142
- });
143
- this._element.removeAttribute('data-vg-table-sticky-header');
144
- }
145
-
172
+ });
173
+ this._element.removeAttribute('data-vg-table-sticky-header');
174
+ }
175
+
146
176
  _scheduleRefresh() {
147
- if (this._resizeFrame !== null) return;
177
+ if (this._resizeFrame !== null) return;
148
178
  if (typeof requestAnimationFrame !== 'function') {
149
- this.refresh();
150
- return;
151
- }
179
+ this.refreshIntrinsicMinimums();
180
+ return;
181
+ }
152
182
  this._resizeFrame = requestAnimationFrame(() => {
153
183
  this._resizeFrame = null;
154
- this.refresh();
184
+ this.refreshIntrinsicMinimums();
155
185
  });
156
- }
186
+ }
157
187
 
158
188
  _buildLayers(widths) {
159
189
  this._header = this._element.ownerDocument.createElement('div');
@@ -177,24 +207,24 @@ class _stickyHeader {
177
207
  this._body.append(this._element);
178
208
  }
179
209
 
180
- _measureColumns() {
181
- return this._measureBodyColumns();
182
- }
183
-
184
- _measureBodyColumns() {
185
- const headerRow = this._head?.rows[this._head.rows.length - 1];
186
- const headerWidths = this._measureRow(headerRow);
187
- const rows = Array.from(this._element.tBodies || []).flatMap((body) => Array.from(body.rows || []));
188
- for (const row of rows) {
189
- if (row.hidden || row.hasAttribute('data-vg-table-state-row') || row.hasAttribute('data-vg-table-skeleton') || row.hasAttribute('data-vg-table-skeleton-row')) continue;
190
- if (row.querySelector('[data-vg-table-state]')) continue;
191
- const widths = this._measureRow(row);
192
- if (widths.length === headerWidths.length && widths.some((width) => width > 0)) return widths;
193
- }
194
- return headerWidths;
195
- }
210
+ _measureColumns() {
211
+ return this._measureBodyColumns();
212
+ }
196
213
 
197
- _measureRow(row) {
214
+ _measureBodyColumns() {
215
+ const headerRow = this._head?.rows[this._head.rows.length - 1];
216
+ const headerWidths = this._measureRow(headerRow);
217
+ const rows = Array.from(this._element.tBodies || []).flatMap((body) => Array.from(body.rows || []));
218
+ for (const row of rows) {
219
+ if (row.hidden || row.hasAttribute('data-vg-table-state-row') || row.hasAttribute('data-vg-table-skeleton') || row.hasAttribute('data-vg-table-skeleton-row')) continue;
220
+ if (row.querySelector('[data-vg-table-state]')) continue;
221
+ const widths = this._measureRow(row);
222
+ if (widths.length === headerWidths.length && widths.some((width) => width > 0)) return widths;
223
+ }
224
+ return headerWidths;
225
+ }
226
+
227
+ _measureRow(row) {
198
228
  if (!row) return [];
199
229
  return Array.from(row.cells).flatMap((cell) => {
200
230
  const span = Math.max(1, cell.colSpan || 1);
@@ -203,30 +233,62 @@ class _stickyHeader {
203
233
  });
204
234
  }
205
235
 
206
- _applyColgroup(table, widths) {
207
- if (!widths.length) return;
208
- if (table.querySelector(':scope > colgroup:not([data-vg-table-sticky-colgroup])')) return;
209
- let colgroup = table.querySelector(':scope > colgroup[data-vg-table-sticky-colgroup]');
236
+ _applyColgroup(table, widths) {
237
+ if (!widths.length) return;
238
+ if (table.querySelector(':scope > colgroup:not([data-vg-table-sticky-colgroup])')) return;
239
+ let colgroup = table.querySelector(':scope > colgroup[data-vg-table-sticky-colgroup]');
210
240
  if (!colgroup) {
211
241
  colgroup = table.ownerDocument.createElement('colgroup');
212
- colgroup.setAttribute('data-vg-table-sticky-colgroup', '');
213
- table.prepend(colgroup);
214
- }
215
- while (colgroup.children.length < widths.length) colgroup.append(table.ownerDocument.createElement('col'));
216
- while (colgroup.children.length > widths.length) colgroup.lastElementChild.remove();
217
- const headers = this._getHeaders();
218
- widths.forEach((width, index) => {
219
- const col = colgroup.children[index];
220
- if (width > 0) col.style.width = `${width}px`;
221
- else col.style.removeProperty('width');
222
- const hidden = headers[index]?.hidden === true;
223
- col.hidden = hidden;
224
- col.toggleAttribute('data-vg-table-column-hidden', hidden);
225
- });
242
+ colgroup.setAttribute('data-vg-table-sticky-colgroup', '');
243
+ table.prepend(colgroup);
244
+ }
245
+ while (colgroup.children.length < widths.length) colgroup.append(table.ownerDocument.createElement('col'));
246
+ while (colgroup.children.length > widths.length) colgroup.lastElementChild.remove();
247
+ const headers = this._getHeaders();
248
+ widths.forEach((width, index) => {
249
+ const col = colgroup.children[index];
250
+ if (width > 0) col.style.width = `${width}px`;
251
+ else col.style.removeProperty('width');
252
+ const hidden = headers[index]?.hidden === true;
253
+ col.hidden = hidden;
254
+ col.toggleAttribute('data-vg-table-column-hidden', hidden);
255
+ });
256
+ }
257
+
258
+ _measureIntrinsicColumnMinimums() {
259
+ if (!this._wrapper || !this._head) return [];
260
+
261
+ const probe = this._element.cloneNode(true);
262
+ probe.removeAttribute('data-vg-table');
263
+ probe.removeAttribute('data-vg-table-sticky-header');
264
+ probe.setAttribute('aria-hidden', 'true');
265
+ probe.querySelector(':scope > colgroup[data-vg-table-sticky-colgroup]')?.remove();
266
+ probe.insertBefore(this._head.cloneNode(true), probe.tBodies[0] || null);
267
+ probe.querySelectorAll('thead th').forEach((header) => header.style.setProperty('white-space', 'nowrap', 'important'));
268
+ [
269
+ ['position', 'absolute'],
270
+ ['inset', '0 auto auto 0'],
271
+ ['visibility', 'hidden'],
272
+ ['pointer-events', 'none'],
273
+ ['width', 'min-content'],
274
+ ['min-width', '0'],
275
+ ['max-width', 'none'],
276
+ ['table-layout', 'auto'],
277
+ ].forEach(([property, value]) => probe.style.setProperty(property, value, 'important'));
278
+
279
+ this._wrapper.append(probe);
280
+ try {
281
+ const rows = Array.from(probe.tHead?.rows || []);
282
+ return this._measureRow(rows.at(-1));
283
+ } finally {
284
+ probe.remove();
285
+ }
226
286
  }
227
287
 
228
- _measureIntrinsicColumnMinimums() {
288
+ _measureAutoLayoutColumns() {
229
289
  if (!this._wrapper || !this._head) return [];
290
+ const viewport = this._body?.clientWidth || this._container?.clientWidth || 0;
291
+ if (!(viewport > 0)) return [];
230
292
 
231
293
  const probe = this._element.cloneNode(true);
232
294
  probe.removeAttribute('data-vg-table');
@@ -234,13 +296,12 @@ class _stickyHeader {
234
296
  probe.setAttribute('aria-hidden', 'true');
235
297
  probe.querySelector(':scope > colgroup[data-vg-table-sticky-colgroup]')?.remove();
236
298
  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
299
  [
239
300
  ['position', 'absolute'],
240
301
  ['inset', '0 auto auto 0'],
241
302
  ['visibility', 'hidden'],
242
303
  ['pointer-events', 'none'],
243
- ['width', 'min-content'],
304
+ ['width', `${viewport}px`],
244
305
  ['min-width', '0'],
245
306
  ['max-width', 'none'],
246
307
  ['table-layout', 'auto'],
@@ -248,181 +309,189 @@ class _stickyHeader {
248
309
 
249
310
  this._wrapper.append(probe);
250
311
  try {
251
- const rows = Array.from(probe.tHead?.rows || []);
252
- return this._measureRow(rows.at(-1));
312
+ const headerRow = Array.from(probe.tHead?.rows || []).at(-1);
313
+ const headerWidths = this._measureRow(headerRow);
314
+ const row = Array.from(probe.tBodies || [])
315
+ .flatMap((body) => Array.from(body.rows || []))
316
+ .find((item) => !item.hidden && !item.hasAttribute('data-vg-table-state-row') && !item.hasAttribute('data-vg-table-skeleton'));
317
+ const bodyWidths = this._measureRow(row);
318
+ return bodyWidths.length === headerWidths.length && bodyWidths.some((width) => width > 0)
319
+ ? bodyWidths
320
+ : headerWidths;
253
321
  } finally {
254
322
  probe.remove();
255
323
  }
256
324
  }
257
-
258
- _rememberColumnGeometry(widths) {
259
- const headers = this._getHeaders();
260
- const sourceCols = Array.from(this._element.querySelector(':scope > colgroup:not([data-vg-table-sticky-colgroup])')?.children || []);
261
- this._hasSourceColgroup = sourceCols.length > 0;
262
- headers.forEach((header, index) => {
263
- const key = this._columnKey(header, index);
264
- this._columnWeights.set(key, widths[index] > 0 ? widths[index] : 1);
265
- if (this._hasDeclaredWidth(header) || this._hasDeclaredWidth(sourceCols[index])) {
266
- this._hardColumnWidths.set(key, widths[index] > 0 ? widths[index] : 0);
267
- } else if (this._isEmptyHeader(header) && widths[index] > 0) {
268
- this._columnMinimumWidths.set(key, widths[index]);
269
- }
270
- });
271
- }
272
-
325
+
326
+ _rememberColumnGeometry(widths) {
327
+ const headers = this._getHeaders();
328
+ const sourceCols = Array.from(this._element.querySelector(':scope > colgroup:not([data-vg-table-sticky-colgroup])')?.children || []);
329
+ this._hasSourceColgroup = sourceCols.length > 0;
330
+ headers.forEach((header, index) => {
331
+ const key = this._columnKey(header, index);
332
+ this._columnWeights.set(key, widths[index] > 0 ? widths[index] : 1);
333
+ if (this._hasDeclaredWidth(header) || this._hasDeclaredWidth(sourceCols[index])) {
334
+ this._hardColumnWidths.set(key, widths[index] > 0 ? widths[index] : 0);
335
+ } else if (this._isEmptyHeader(header) && widths[index] > 0) {
336
+ this._columnMinimumWidths.set(key, widths[index]);
337
+ }
338
+ });
339
+ }
340
+
273
341
  _resolveLayoutWidths() {
274
342
  const headers = this._getHeaders();
343
+ if (this._autoLayoutWidths?.length === headers.length) return this._autoLayoutWidths.slice();
275
344
  const measured = this._measureBodyColumns();
276
- if (this._hasSourceColgroup || !headers.length) return measured;
277
-
278
- const bodyRow = Array.from(this._element.tBodies || [])
279
- .flatMap((body) => Array.from(body.rows || []))
280
- .find((row) => !row.hidden && !row.hasAttribute('data-vg-table-state-row'));
281
- const hard = new Map();
282
- const flexible = [];
283
- headers.forEach((header, index) => {
284
- const key = this._columnKey(header, index);
285
- const bodyCell = bodyRow?.cells[index];
286
- if (this._hasDeclaredWidth(header) || this._hasDeclaredWidth(bodyCell) || this._hardColumnWidths.has(key)) {
287
- const declared = this._readDeclaredPixelWidth(header) || this._readDeclaredPixelWidth(bodyCell);
288
- const width = declared || this._hardColumnWidths.get(key) || measured[index] || 0;
289
- this._hardColumnWidths.set(key, width);
290
- hard.set(index, width);
291
- } else {
292
- flexible.push(index);
293
- }
294
- });
295
-
296
- const measuredTotal = measured.reduce((total, width) => total + width, 0);
297
- const viewport = this._body?.clientWidth || this._container?.clientWidth || measuredTotal;
298
- const minimum = this._readTableMinimumWidth();
299
- const target = Math.max(viewport, minimum, 0);
300
- const hardTotal = Array.from(hard.values()).reduce((total, width) => total + width, 0);
301
- const available = Math.max(0, target - hardTotal);
302
- const widths = Array(headers.length).fill(0);
303
- hard.forEach((width, index) => { widths[index] = width; });
304
-
305
- if (!flexible.length) return widths;
306
- this._distributeFlexibleWidths(widths, flexible, available, headers, measured);
307
- return widths;
308
- }
309
-
310
- _distributeFlexibleWidths(widths, indexes, available, headers, measured) {
311
- let pending = indexes.slice();
312
- let remaining = available;
313
- const minimumTotal = pending.reduce((total, index) => {
314
- const key = this._columnKey(headers[index], index);
315
- return total + (this._columnMinimumWidths.get(key) || 0);
316
- }, 0);
317
-
318
- if (minimumTotal > remaining) {
319
- pending.forEach((index) => {
320
- const key = this._columnKey(headers[index], index);
321
- widths[index] = this._columnMinimumWidths.get(key) || 0;
322
- });
323
- return;
324
- }
325
-
326
- while (pending.length) {
327
- const weights = pending.map((index) => {
328
- const key = this._columnKey(headers[index], index);
329
- return this._columnWeights.get(key) || measured[index] || 1;
330
- });
331
- const weightTotal = weights.reduce((total, weight) => total + weight, 0) || pending.length;
332
- const constrained = pending.filter((index, position) => {
333
- const key = this._columnKey(headers[index], index);
334
- const minimum = this._columnMinimumWidths.get(key) || 0;
335
- return minimum > remaining * weights[position] / weightTotal;
336
- });
337
-
338
- if (!constrained.length) {
339
- let distributed = 0;
340
- pending.forEach((index, position) => {
341
- const width = position === pending.length - 1
342
- ? Math.max(0, remaining - distributed)
343
- : remaining * weights[position] / weightTotal;
344
- widths[index] = width;
345
- distributed += width;
346
- });
347
- return;
348
- }
349
-
350
- constrained.forEach((index) => {
351
- const key = this._columnKey(headers[index], index);
352
- const minimum = this._columnMinimumWidths.get(key) || 0;
353
- widths[index] = minimum;
354
- remaining -= minimum;
355
- });
356
- pending = pending.filter((index) => !constrained.includes(index));
357
- }
358
- }
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
-
389
- _getHeaders() {
390
- const rows = Array.from(this._head?.rows || []);
391
- return rows.length ? Array.from(rows.at(-1).cells || []) : [];
392
- }
393
-
394
- _columnKey(header, index) {
395
- return String(header?.getAttribute('data-field') || index).trim();
396
- }
397
-
398
- _isEmptyHeader(header) {
399
- return Boolean(header) && header.childElementCount === 0 && String(header.textContent || '').trim() === '';
400
- }
401
-
402
- _hasDeclaredWidth(element) {
403
- if (!element) return false;
404
- return element.hasAttribute('width')
405
- || element.hasAttribute('data-column-width')
406
- || Boolean(element.style?.width || element.style?.minWidth || element.style?.maxWidth);
407
- }
408
-
409
- _readDeclaredPixelWidth(element) {
410
- if (!element || !this._hasDeclaredWidth(element)) return 0;
411
- const fixed = element.style?.minWidth && element.style.minWidth === element.style.maxWidth
412
- ? Number.parseFloat(element.style.width || element.style.minWidth)
413
- : 0;
414
- if (Number.isFinite(fixed) && fixed > 0) return fixed;
415
- const measured = element.getBoundingClientRect?.().width || element.offsetWidth || 0;
416
- return measured > 0 ? measured : 0;
417
- }
418
-
419
- _readTableMinimumWidth() {
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);
424
- }
425
-
345
+ if (this._hasSourceColgroup || !headers.length) return measured;
346
+
347
+ const bodyRow = Array.from(this._element.tBodies || [])
348
+ .flatMap((body) => Array.from(body.rows || []))
349
+ .find((row) => !row.hidden && !row.hasAttribute('data-vg-table-state-row'));
350
+ const hard = new Map();
351
+ const flexible = [];
352
+ headers.forEach((header, index) => {
353
+ const key = this._columnKey(header, index);
354
+ const bodyCell = bodyRow?.cells[index];
355
+ if (this._hasDeclaredWidth(header) || this._hasDeclaredWidth(bodyCell) || this._hardColumnWidths.has(key)) {
356
+ const declared = this._readDeclaredPixelWidth(header) || this._readDeclaredPixelWidth(bodyCell);
357
+ const width = declared || this._hardColumnWidths.get(key) || measured[index] || 0;
358
+ this._hardColumnWidths.set(key, width);
359
+ hard.set(index, width);
360
+ } else {
361
+ flexible.push(index);
362
+ }
363
+ });
364
+
365
+ const measuredTotal = measured.reduce((total, width) => total + width, 0);
366
+ const viewport = this._body?.clientWidth || this._container?.clientWidth || measuredTotal;
367
+ const minimum = this._readTableMinimumWidth();
368
+ const target = Math.max(viewport, minimum, 0);
369
+ const hardTotal = Array.from(hard.values()).reduce((total, width) => total + width, 0);
370
+ const available = Math.max(0, target - hardTotal);
371
+ const widths = Array(headers.length).fill(0);
372
+ hard.forEach((width, index) => { widths[index] = width; });
373
+
374
+ if (!flexible.length) return widths;
375
+ this._distributeFlexibleWidths(widths, flexible, available, headers, measured);
376
+ return widths;
377
+ }
378
+
379
+ _distributeFlexibleWidths(widths, indexes, available, headers, measured) {
380
+ let pending = indexes.slice();
381
+ let remaining = available;
382
+ const minimumTotal = pending.reduce((total, index) => {
383
+ const key = this._columnKey(headers[index], index);
384
+ return total + (this._columnMinimumWidths.get(key) || 0);
385
+ }, 0);
386
+
387
+ if (minimumTotal > remaining) {
388
+ pending.forEach((index) => {
389
+ const key = this._columnKey(headers[index], index);
390
+ widths[index] = this._columnMinimumWidths.get(key) || 0;
391
+ });
392
+ return;
393
+ }
394
+
395
+ while (pending.length) {
396
+ const weights = pending.map((index) => {
397
+ const key = this._columnKey(headers[index], index);
398
+ return this._columnWeights.get(key) || measured[index] || 1;
399
+ });
400
+ const weightTotal = weights.reduce((total, weight) => total + weight, 0) || pending.length;
401
+ const constrained = pending.filter((index, position) => {
402
+ const key = this._columnKey(headers[index], index);
403
+ const minimum = this._columnMinimumWidths.get(key) || 0;
404
+ return minimum > remaining * weights[position] / weightTotal;
405
+ });
406
+
407
+ if (!constrained.length) {
408
+ let distributed = 0;
409
+ pending.forEach((index, position) => {
410
+ const width = position === pending.length - 1
411
+ ? Math.max(0, remaining - distributed)
412
+ : remaining * weights[position] / weightTotal;
413
+ widths[index] = width;
414
+ distributed += width;
415
+ });
416
+ return;
417
+ }
418
+
419
+ constrained.forEach((index) => {
420
+ const key = this._columnKey(headers[index], index);
421
+ const minimum = this._columnMinimumWidths.get(key) || 0;
422
+ widths[index] = minimum;
423
+ remaining -= minimum;
424
+ });
425
+ pending = pending.filter((index) => !constrained.includes(index));
426
+ }
427
+ }
428
+
429
+ _captureOverflowingHeaderMinimums() {
430
+ let changed = false;
431
+ this._getHeaders().forEach((header, index) => {
432
+ const key = this._columnKey(header, index);
433
+ if (this._hasDeclaredWidth(header) || this._hardColumnWidths.has(key)) return;
434
+
435
+ const rendered = header.getBoundingClientRect?.().width || header.clientWidth || 0;
436
+ const required = header.scrollWidth || 0;
437
+ const current = this._columnMinimumWidths.get(key) || 0;
438
+ if (required <= rendered + 1 || required <= current + 1) return;
439
+
440
+ this._columnMinimumWidths.set(key, required);
441
+ changed = true;
442
+ });
443
+ return changed;
444
+ }
445
+
446
+ _syncContentMinimumWidth(widths) {
447
+ const total = widths.reduce((sum, width) => sum + width, 0);
448
+ const viewport = this._body?.clientWidth || this._container?.clientWidth || total;
449
+ const previous = this._previousStyles.get(CONTENT_MIN_WIDTH_PROPERTY);
450
+ const required = Math.max(total, this._element?.scrollWidth || 0);
451
+ const next = required > viewport + 1 ? `${Math.ceil(required)}px` : previous;
452
+ const current = this._container.style.getPropertyValue(CONTENT_MIN_WIDTH_PROPERTY);
453
+ if (next) this._container.style.setProperty(CONTENT_MIN_WIDTH_PROPERTY, next);
454
+ else this._container.style.removeProperty(CONTENT_MIN_WIDTH_PROPERTY);
455
+ return current !== (next || '');
456
+ }
457
+
458
+ _getHeaders() {
459
+ const rows = Array.from(this._head?.rows || []);
460
+ return rows.length ? Array.from(rows.at(-1).cells || []) : [];
461
+ }
462
+
463
+ _columnKey(header, index) {
464
+ return String(header?.getAttribute('data-field') || index).trim();
465
+ }
466
+
467
+ _isEmptyHeader(header) {
468
+ return Boolean(header) && header.childElementCount === 0 && String(header.textContent || '').trim() === '';
469
+ }
470
+
471
+ _hasDeclaredWidth(element) {
472
+ if (!element) return false;
473
+ return element.hasAttribute('width')
474
+ || element.hasAttribute('data-column-width')
475
+ || Boolean(element.style?.width || element.style?.minWidth || element.style?.maxWidth);
476
+ }
477
+
478
+ _readDeclaredPixelWidth(element) {
479
+ if (!element || !this._hasDeclaredWidth(element)) return 0;
480
+ const fixed = element.style?.minWidth && element.style.minWidth === element.style.maxWidth
481
+ ? Number.parseFloat(element.style.width || element.style.minWidth)
482
+ : 0;
483
+ if (Number.isFinite(fixed) && fixed > 0) return fixed;
484
+ const measured = element.getBoundingClientRect?.().width || element.offsetWidth || 0;
485
+ return measured > 0 ? measured : 0;
486
+ }
487
+
488
+ _readTableMinimumWidth() {
489
+ const generated = Number.parseFloat(this._container?.style.getPropertyValue(CONTENT_MIN_WIDTH_PROPERTY) || '0');
490
+ if (typeof getComputedStyle !== 'function') return Number.isFinite(generated) ? generated : 0;
491
+ const declared = Number.parseFloat(getComputedStyle(this._element).minWidth || '0');
492
+ return Math.max(Number.isFinite(generated) ? generated : 0, Number.isFinite(declared) ? declared : 0);
493
+ }
494
+
426
495
  _syncScroll() {
427
496
  if (this._header && this._body) this._header.scrollLeft = this._body.scrollLeft;
428
497
  }