vgapp 1.5.0 → 1.5.1

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.
@@ -22,11 +22,11 @@ $filepreview-map: (
22
22
  toggle-size: 24px,
23
23
  icon-size: 18px,
24
24
  toggle-radius: 999px,
25
- toggle-bg: var(--vg-media-border-color, rgba(255, 255, 255, .12)),
26
- toggle-color: var(--vg-media-text-color, #ffffff),
25
+ toggle-bg: rgba(0, 0, 0, .12),
26
+ toggle-color: #000000,
27
27
  name-line-height: 1.2,
28
- progress-track: var(--vg-media-subtle-bg, rgba(255, 255, 255, .06)),
29
- progress-fill: var(--vg-media-active-bg, rgba(255, 255, 255, .16)),
28
+ progress-track: rgba(0, 0, 0, .16),
29
+ progress-fill: rgba(0, 0, 0, .16),
30
30
  progress-radius: 6px,
31
31
  progress-padding-x: 4px,
32
32
  ),
@@ -393,7 +393,7 @@
393
393
  .vg-filepreview-download-trigger {
394
394
  padding: 0;
395
395
  background: transparent;
396
- color: var(--vg-secondary);
396
+ color: var(--vg-body-color);
397
397
  border-radius: 0;
398
398
  font-size: var(--vg-files-info-size-font-size);
399
399
  font-weight: 500;
@@ -1,7 +1,7 @@
1
- /**
2
- * Описание: разметка Fixed Header с отдельными слоями заголовка и тела VGTable.
3
- * Возможности: перенос настоящего thead без клона, синхронизация ширин колонок и горизонтальной прокрутки.
4
- */
1
+ /**
2
+ * Описание: разметка Fixed Header с отдельными слоями заголовка и тела VGTable.
3
+ * Возможности: перенос настоящего thead без клона, автоматическая синхронизация ширин колонок и горизонтальной прокрутки.
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'];
@@ -18,19 +18,24 @@ class _stickyHeader {
18
18
  this._header = null;
19
19
  this._headerTable = null;
20
20
  this._body = null;
21
- this._previousClasses = new Map();
21
+ this._previousClasses = new Map();
22
22
  this._previousContainerClasses = new Map();
23
23
  this._previousStyles = new Map();
24
- this._previousHeaderWidths = new Map();
25
- this._boundScroll = this._syncScroll.bind(this);
26
- this._boundResize = this.refresh.bind(this);
27
- this._boundPageScroll = this._syncStickyState.bind(this);
24
+ this._columnWeights = new Map();
25
+ this._hardColumnWidths = new Map();
26
+ this._hasSourceColgroup = false;
27
+ this._resizeObserver = null;
28
+ this._resizeFrame = null;
29
+ this._boundScroll = this._syncScroll.bind(this);
30
+ this._boundResize = this._scheduleRefresh.bind(this);
31
+ this._boundPageScroll = this._syncStickyState.bind(this);
28
32
  }
29
33
 
30
34
  init() {
31
- if (!this._wrapper || !this._container || !this._head) return this;
32
-
33
- const widths = this._measureColumns();
35
+ if (!this._wrapper || !this._container || !this._head) return this;
36
+
37
+ const widths = this._measureColumns();
38
+ this._rememberColumnGeometry(widths);
34
39
  MODE_CLASSES.forEach((className) => this._previousClasses.set(className, this._wrapper.classList.contains(className)));
35
40
  CONTAINER_MODE_CLASSES.forEach((className) => this._previousContainerClasses.set(className, this._container.classList.contains(className)));
36
41
  STYLE_PROPERTIES.forEach((property) => this._previousStyles.set(property, this._container.style.getPropertyValue(property)));
@@ -43,13 +48,17 @@ class _stickyHeader {
43
48
  this._container.style.setProperty('--vg-table-sticky-max-height', this._toCssLength(this._params.maxHeight, '24rem'));
44
49
  this._element.setAttribute('data-vg-table-sticky-header', this._params.mode);
45
50
 
46
- this._buildLayers(widths);
47
- this._body.addEventListener('scroll', this._boundScroll, {passive: true});
48
- if (typeof window !== 'undefined') {
49
- window.addEventListener('resize', this._boundResize);
50
- if (this._params.mode === 'page') window.addEventListener('scroll', this._boundPageScroll, {passive: true});
51
- }
52
- this.refresh();
51
+ this._buildLayers(widths);
52
+ this._body.addEventListener('scroll', this._boundScroll, {passive: true});
53
+ if (typeof ResizeObserver !== 'undefined') {
54
+ this._resizeObserver = new ResizeObserver(this._boundResize);
55
+ this._resizeObserver.observe(this._container);
56
+ this._resizeObserver.observe(this._body);
57
+ } else if (typeof window !== 'undefined') {
58
+ window.addEventListener('resize', this._boundResize);
59
+ }
60
+ if (this._params.mode === 'page' && typeof window !== 'undefined') window.addEventListener('scroll', this._boundPageScroll, {passive: true});
61
+ this.refresh();
53
62
  return this;
54
63
  }
55
64
 
@@ -69,10 +78,9 @@ class _stickyHeader {
69
78
  refresh() {
70
79
  if (!this._header || !this._headerTable || !this._body) return this;
71
80
 
72
- const widths = this._measureBodyColumns();
81
+ const widths = this._resolveLayoutWidths();
73
82
  this._applyColgroup(this._headerTable, widths);
74
83
  this._applyColgroup(this._element, widths);
75
- this._applyHeaderWidths(widths);
76
84
  const scrollbar = Math.max(0, this._body.offsetWidth - this._body.clientWidth);
77
85
  this._header.style.paddingInlineEnd = `${scrollbar}px`;
78
86
  this._header.style.setProperty('--vg-table-sticky-scrollbar-width', `${scrollbar}px`);
@@ -83,12 +91,17 @@ class _stickyHeader {
83
91
  return this;
84
92
  }
85
93
 
86
- dispose() {
87
- if (this._body) this._body.removeEventListener('scroll', this._boundScroll);
88
- if (typeof window !== 'undefined') {
89
- window.removeEventListener('resize', this._boundResize);
90
- window.removeEventListener('scroll', this._boundPageScroll);
91
- }
94
+ dispose() {
95
+ if (this._body) this._body.removeEventListener('scroll', this._boundScroll);
96
+ this._resizeObserver?.disconnect();
97
+ if (this._resizeFrame !== null && typeof cancelAnimationFrame === 'function') {
98
+ cancelAnimationFrame(this._resizeFrame);
99
+ this._resizeFrame = null;
100
+ }
101
+ if (typeof window !== 'undefined') {
102
+ if (!this._resizeObserver) window.removeEventListener('resize', this._boundResize);
103
+ window.removeEventListener('scroll', this._boundPageScroll);
104
+ }
92
105
  this._header?.classList.remove('is-stuck');
93
106
  if (this._head && this._headerTable?.contains(this._head)) this._element.prepend(this._head);
94
107
  this._header?.remove();
@@ -108,12 +121,20 @@ class _stickyHeader {
108
121
  if (previous) this._container.style.setProperty(property, previous);
109
122
  else this._container.style.removeProperty(property);
110
123
  });
111
- this._previousHeaderWidths.forEach((width, cell) => {
112
- cell.style.width = width;
113
- });
114
- this._previousHeaderWidths.clear();
115
124
  this._element.removeAttribute('data-vg-table-sticky-header');
116
- }
125
+ }
126
+
127
+ _scheduleRefresh() {
128
+ if (this._resizeFrame !== null) return;
129
+ if (typeof requestAnimationFrame !== 'function') {
130
+ this.refresh();
131
+ return;
132
+ }
133
+ this._resizeFrame = requestAnimationFrame(() => {
134
+ this._resizeFrame = null;
135
+ this.refresh();
136
+ });
137
+ }
117
138
 
118
139
  _buildLayers(widths) {
119
140
  this._header = this._element.ownerDocument.createElement('div');
@@ -154,7 +175,7 @@ class _stickyHeader {
154
175
  return headerWidths;
155
176
  }
156
177
 
157
- _measureRow(row) {
178
+ _measureRow(row) {
158
179
  if (!row) return [];
159
180
  return Array.from(row.cells).flatMap((cell) => {
160
181
  const span = Math.max(1, cell.colSpan || 1);
@@ -164,34 +185,121 @@ class _stickyHeader {
164
185
  }
165
186
 
166
187
  _applyColgroup(table, widths) {
167
- if (!widths.length) return;
168
- if (table.querySelector(':scope > colgroup:not([data-vg-table-sticky-colgroup])')) return;
169
- let colgroup = table.querySelector(':scope > colgroup[data-vg-table-sticky-colgroup]');
188
+ if (!widths.length) return;
189
+ if (table.querySelector(':scope > colgroup:not([data-vg-table-sticky-colgroup])')) return;
190
+ let colgroup = table.querySelector(':scope > colgroup[data-vg-table-sticky-colgroup]');
170
191
  if (!colgroup) {
171
192
  colgroup = table.ownerDocument.createElement('colgroup');
172
- colgroup.setAttribute('data-vg-table-sticky-colgroup', '');
173
- table.prepend(colgroup);
174
- }
175
- colgroup.replaceChildren(...widths.map((width) => {
176
- const col = table.ownerDocument.createElement('col');
177
- if (width > 0) col.style.width = `${width}px`;
178
- return col;
179
- }));
193
+ colgroup.setAttribute('data-vg-table-sticky-colgroup', '');
194
+ table.prepend(colgroup);
195
+ }
196
+ while (colgroup.children.length < widths.length) colgroup.append(table.ownerDocument.createElement('col'));
197
+ while (colgroup.children.length > widths.length) colgroup.lastElementChild.remove();
198
+ const headers = this._getHeaders();
199
+ widths.forEach((width, index) => {
200
+ const col = colgroup.children[index];
201
+ if (width > 0) col.style.width = `${width}px`;
202
+ else col.style.removeProperty('width');
203
+ const hidden = headers[index]?.hidden === true;
204
+ col.hidden = hidden;
205
+ col.toggleAttribute('data-vg-table-column-hidden', hidden);
206
+ });
180
207
  }
181
208
 
182
- _applyHeaderWidths(widths) {
183
- const row = this._head?.rows[this._head.rows.length - 1];
184
- if (!row || !widths.length) return;
185
- let columnIndex = 0;
186
- Array.from(row.cells).forEach((cell) => {
187
- if (!this._previousHeaderWidths.has(cell)) this._previousHeaderWidths.set(cell, cell.style.width);
188
- const span = Math.max(1, cell.colSpan || 1);
189
- const width = widths.slice(columnIndex, columnIndex + span).reduce((total, value) => total + value, 0);
190
- if (width > 0) cell.style.width = `${width}px`;
191
- columnIndex += span;
209
+ _rememberColumnGeometry(widths) {
210
+ const headers = this._getHeaders();
211
+ const sourceCols = Array.from(this._element.querySelector(':scope > colgroup:not([data-vg-table-sticky-colgroup])')?.children || []);
212
+ this._hasSourceColgroup = sourceCols.length > 0;
213
+ headers.forEach((header, index) => {
214
+ const key = this._columnKey(header, index);
215
+ this._columnWeights.set(key, widths[index] > 0 ? widths[index] : 1);
216
+ if (this._hasDeclaredWidth(header) || this._hasDeclaredWidth(sourceCols[index])) {
217
+ this._hardColumnWidths.set(key, widths[index] > 0 ? widths[index] : 0);
218
+ }
192
219
  });
193
220
  }
194
-
221
+
222
+ _resolveLayoutWidths() {
223
+ const headers = this._getHeaders();
224
+ const measured = this._measureBodyColumns();
225
+ if (this._hasSourceColgroup || !headers.length) return measured;
226
+
227
+ const bodyRow = Array.from(this._element.tBodies || [])
228
+ .flatMap((body) => Array.from(body.rows || []))
229
+ .find((row) => !row.hidden && !row.hasAttribute('data-vg-table-state-row'));
230
+ const hard = new Map();
231
+ const flexible = [];
232
+ headers.forEach((header, index) => {
233
+ const key = this._columnKey(header, index);
234
+ const bodyCell = bodyRow?.cells[index];
235
+ if (this._hasDeclaredWidth(header) || this._hasDeclaredWidth(bodyCell) || this._hardColumnWidths.has(key)) {
236
+ const declared = this._readDeclaredPixelWidth(header) || this._readDeclaredPixelWidth(bodyCell);
237
+ const width = declared || this._hardColumnWidths.get(key) || measured[index] || 0;
238
+ this._hardColumnWidths.set(key, width);
239
+ hard.set(index, width);
240
+ } else {
241
+ flexible.push(index);
242
+ }
243
+ });
244
+
245
+ const measuredTotal = measured.reduce((total, width) => total + width, 0);
246
+ const viewport = this._body?.clientWidth || this._container?.clientWidth || measuredTotal;
247
+ const minimum = this._readTableMinimumWidth();
248
+ const target = Math.max(viewport, minimum, 0);
249
+ const hardTotal = Array.from(hard.values()).reduce((total, width) => total + width, 0);
250
+ const available = Math.max(0, target - hardTotal);
251
+ const widths = Array(headers.length).fill(0);
252
+ hard.forEach((width, index) => { widths[index] = width; });
253
+
254
+ if (!flexible.length) return widths;
255
+ const weights = flexible.map((index) => {
256
+ const key = this._columnKey(headers[index], index);
257
+ return this._columnWeights.get(key) || measured[index] || 1;
258
+ });
259
+ const weightTotal = weights.reduce((total, weight) => total + weight, 0) || flexible.length;
260
+ let distributed = 0;
261
+ flexible.forEach((index, position) => {
262
+ const width = position === flexible.length - 1
263
+ ? Math.max(0, available - distributed)
264
+ : available * weights[position] / weightTotal;
265
+ widths[index] = width;
266
+ distributed += width;
267
+ });
268
+ return widths;
269
+ }
270
+
271
+ _getHeaders() {
272
+ const rows = Array.from(this._head?.rows || []);
273
+ return rows.length ? Array.from(rows.at(-1).cells || []) : [];
274
+ }
275
+
276
+ _columnKey(header, index) {
277
+ return String(header?.getAttribute('data-field') || index).trim();
278
+ }
279
+
280
+ _hasDeclaredWidth(element) {
281
+ if (!element) return false;
282
+ return element.hasAttribute('width')
283
+ || element.hasAttribute('data-column-width')
284
+ || Boolean(element.style?.width || element.style?.minWidth || element.style?.maxWidth);
285
+ }
286
+
287
+ _readDeclaredPixelWidth(element) {
288
+ if (!element || !this._hasDeclaredWidth(element)) return 0;
289
+ const fixed = element.style?.minWidth && element.style.minWidth === element.style.maxWidth
290
+ ? Number.parseFloat(element.style.width || element.style.minWidth)
291
+ : 0;
292
+ if (Number.isFinite(fixed) && fixed > 0) return fixed;
293
+ const measured = element.getBoundingClientRect?.().width || element.offsetWidth || 0;
294
+ return measured > 0 ? measured : 0;
295
+ }
296
+
297
+ _readTableMinimumWidth() {
298
+ if (typeof getComputedStyle !== 'function') return 0;
299
+ const value = Number.parseFloat(getComputedStyle(this._element).minWidth || '0');
300
+ return Number.isFinite(value) ? value : 0;
301
+ }
302
+
195
303
  _syncScroll() {
196
304
  if (this._header && this._body) this._header.scrollLeft = this._body.scrollLeft;
197
305
  }
@@ -121,7 +121,8 @@
121
121
  }
122
122
 
123
123
  .vg-table-body {
124
- overflow: auto;
124
+ overflow-x: auto;
125
+ overflow-y: hidden;
125
126
  }
126
127
  }
127
128
  }
@@ -113,6 +113,12 @@
113
113
  display: none;
114
114
  }
115
115
  }
116
+
117
+ &.not-splitter {
118
+ &:before {
119
+ display: none;
120
+ }
121
+ }
116
122
  }
117
123
  }
118
124