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.
- package/app/modules/vgfilepreview/scss/_variables.scss +4 -4
- package/app/modules/vgfiles/scss/vgfiles.scss +1 -1
- package/app/modules/vgtable/js/_sticky-header.js +163 -55
- package/app/modules/vgtable/scss/_sticky-header.scss +2 -1
- package/app/modules/vgtable/scss/vgtable.scss +6 -0
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/build/vgapp.js +1 -1
- package/build/vgapp.js.map +1 -1
- package/package.json +1 -1
|
@@ -22,11 +22,11 @@ $filepreview-map: (
|
|
|
22
22
|
toggle-size: 24px,
|
|
23
23
|
icon-size: 18px,
|
|
24
24
|
toggle-radius: 999px,
|
|
25
|
-
toggle-bg:
|
|
26
|
-
toggle-color:
|
|
25
|
+
toggle-bg: rgba(0, 0, 0, .12),
|
|
26
|
+
toggle-color: #000000,
|
|
27
27
|
name-line-height: 1.2,
|
|
28
|
-
progress-track:
|
|
29
|
-
progress-fill:
|
|
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
|
),
|
|
@@ -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.
|
|
25
|
-
this.
|
|
26
|
-
this.
|
|
27
|
-
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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.
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
}
|