vgapp 1.5.0 → 1.5.2

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,4 +1,4 @@
1
- /**
1
+ /**
2
2
  *--------------------------------------------------------------------------
3
3
  * Модуль: VGFiles
4
4
  * Автор: Vegas DEV
@@ -61,12 +61,18 @@
61
61
  }
62
62
  }
63
63
 
64
- &-errors {
65
- padding: var(--vg-files-errors-padding-y) 0;
66
-
67
- span {
68
- font-size: var(--vg-files-errors-font-size);
69
- color: var(--vg-danger);
64
+ &-errors {
65
+ display: grid;
66
+ gap: var(--vg-files-errors-gap);
67
+ padding: var(--vg-files-errors-padding-y) var(--vg-files-errors-padding-x);
68
+ margin-bottom: var(--vg-files-errors-margin-bottom);
69
+ border-left: var(--vg-files-errors-border-width) solid var(--vg-files-errors-border-color);
70
+ border-radius: var(--vg-files-errors-border-radius);
71
+ background: var(--vg-files-errors-bg);
72
+
73
+ span {
74
+ font-size: var(--vg-files-errors-font-size);
75
+ color: var(--vg-danger-color, var(--vg-danger, #ff2a2c));
70
76
  display: block;
71
77
  overflow: hidden;
72
78
  text-overflow: ellipsis;
@@ -393,7 +399,7 @@
393
399
  .vg-filepreview-download-trigger {
394
400
  padding: 0;
395
401
  background: transparent;
396
- color: var(--vg-secondary);
402
+ color: var(--vg-body-color);
397
403
  border-radius: 0;
398
404
  font-size: var(--vg-files-info-size-font-size);
399
405
  font-weight: 500;
@@ -517,14 +523,18 @@
517
523
  }
518
524
  }
519
525
 
520
- .file-failing-actions {
526
+ .file-failing-actions {
521
527
  display: inline-flex;
522
528
  align-items: center;
523
529
  gap: var(--vg-files-info-failing-actions-gap);
524
- }
525
-
526
- &-info--list {
527
- .file.failing {
530
+ }
531
+
532
+ &-info--list {
533
+ .file.validation-error {
534
+ box-shadow: inset 0 0 0 2px var(--vg-danger-color, var(--vg-danger, #ff2a2c));
535
+ }
536
+
537
+ .file.failing {
528
538
  .file-remove {
529
539
  margin-left: var(--vg-files-info-failing-actions-margin-start);
530
540
  }
@@ -634,7 +644,7 @@
634
644
  }
635
645
  }
636
646
 
637
- &--list {
647
+ &--list {
638
648
  display: flex;
639
649
  align-items: center;
640
650
  gap: var(--vg-files-drop-list-gap);
@@ -646,7 +656,7 @@
646
656
  border-radius: var(--vg-files-drop-radius);
647
657
  overflow: hidden;
648
658
 
649
- .file {
659
+ .file {
650
660
  --vg-button-color: var(--vg-text-color, var(--vg-body-color, #000000));
651
661
  border-radius: var(--vg-files-drop-file-radius);
652
662
  position: relative;
@@ -655,7 +665,11 @@
655
665
  height: var(--vg-files-drop-file-height);
656
666
 
657
667
  @include files.vgfiles-sortable();
658
- @include files.vgfiles-state();
668
+ @include files.vgfiles-state();
669
+
670
+ &.validation-error {
671
+ box-shadow: inset 0 0 0 2px var(--vg-danger-color, var(--vg-danger, #ff2a2c));
672
+ }
659
673
 
660
674
  .file-image {
661
675
  width: 100%;
@@ -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