vgapp 1.4.9 → 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
  ),
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Описание: базовая логика выбора, валидации и отображения файлов VGFiles.
3
- * Возможности: управляет набором файлов, ошибками, списками, статистикой и скрытыми полями формы.
2
+ * Описание: базовая логика выбора, валидации и отображения файлов VGFiles.
3
+ * Возможности: управляет набором файлов, ошибками, списками, кастомными действиями, статистикой и скрытыми полями формы.
4
4
  */
5
5
  import BaseModule from "../../base-module";
6
6
  import {mergeDeepObject} from "../../../utils/js/functions";
@@ -422,7 +422,7 @@ class VGFilesBase extends BaseModule {
422
422
  };
423
423
  }
424
424
 
425
- _renderUIDropList(files) {
425
+ _renderUIDropList(files) {
426
426
  if (!this._nodes.drop) return;
427
427
 
428
428
  let $list = Selectors.find(`.${this._getClass('drop-list')}`, this._nodes.drop);
@@ -435,9 +435,10 @@ class VGFilesBase extends BaseModule {
435
435
  const fragment = document.createDocumentFragment();
436
436
 
437
437
  files.forEach((file) => {
438
- let classes = $itemsTemplateClasses;
438
+ let classes = [...$itemsTemplateClasses];
439
439
 
440
- if (this._params.detach) classes.push('with-remove')
440
+ if (this._params.detach) classes.push('with-remove')
441
+ if (this._params.customButtons?.hideOnUploadState) classes.push('hide-custom-buttons-on-upload-state')
441
442
  if (this._params.sortable.enabled) classes.push('with-sortable')
442
443
  if (this._params.limits.count === 1) {
443
444
  classes.push('single');
@@ -606,12 +607,13 @@ class VGFilesBase extends BaseModule {
606
607
  return '';
607
608
  }
608
609
 
609
- _createInfoListItem(file, i, itemsTemplate, itemTemplateClasses, fileKey, signature) {
610
+ _createInfoListItem(file, i, itemsTemplate, itemTemplateClasses, fileKey, signature) {
610
611
  let classes = [...itemTemplateClasses];
611
612
 
612
613
  if (this._params.image) classes.push('with-image');
613
614
  if (this._params.info) classes.push('with-info');
614
- if (this._params.detach) classes.push('with-remove')
615
+ if (this._params.detach) classes.push('with-remove')
616
+ if (this._params.customButtons?.hideOnUploadState) classes.push('hide-custom-buttons-on-upload-state')
615
617
  if (this._params.sortable.enabled) classes.push('with-sortable');
616
618
  const previewPath = this._resolveFilePreviewPath(file);
617
619
  const displayName = this._resolveDisplayName(file);
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Описание: основной класс выбора, отображения и AJAX-загрузки файлов VGFiles.
3
- * Возможности: управляет списками и dropzone, прогрессом, удалением, повторной загрузкой и сортировкой.
3
+ * Возможности: управляет списками и dropzone, прогрессом, кастомными действиями, удалением, повторной загрузкой и сортировкой.
4
4
  */
5
5
  import VGFilesBase from "./base";
6
6
  import FileUploader from "./loader";
@@ -47,8 +47,11 @@ class VGFiles extends VGFilesBase {
47
47
  prepend: true,
48
48
  replace: true,
49
49
  rename: false,
50
- smartdrop: false,
51
- uploads: {
50
+ smartdrop: false,
51
+ customButtons: {
52
+ hideOnUploadState: true
53
+ },
54
+ uploads: {
52
55
  mode: 'sequential',
53
56
  route: '',
54
57
  maxParallel: 3,
@@ -23,8 +23,18 @@
23
23
  }
24
24
  }
25
25
 
26
- @mixin vgfiles-state() {
27
- &.pending {
26
+ @mixin vgfiles-state() {
27
+ &.hide-custom-buttons-on-upload-state {
28
+ &.pending,
29
+ &.loading,
30
+ &.failing {
31
+ .file-actions {
32
+ display: none;
33
+ }
34
+ }
35
+ }
36
+
37
+ &.pending {
28
38
  > * {
29
39
  opacity: .32;
30
40
  pointer-events: none;
@@ -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,18 +18,24 @@ class _stickyHeader {
18
18
  this._header = null;
19
19
  this._headerTable = null;
20
20
  this._body = null;
21
- this._previousClasses = new Map();
22
- this._previousContainerClasses = new Map();
23
- this._previousStyles = new Map();
24
- this._boundScroll = this._syncScroll.bind(this);
25
- this._boundResize = this.refresh.bind(this);
26
- this._boundPageScroll = this._syncStickyState.bind(this);
21
+ this._previousClasses = new Map();
22
+ this._previousContainerClasses = new Map();
23
+ this._previousStyles = new Map();
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);
27
32
  }
28
33
 
29
34
  init() {
30
- if (!this._wrapper || !this._container || !this._head) return this;
31
-
32
- const widths = this._measureColumns();
35
+ if (!this._wrapper || !this._container || !this._head) return this;
36
+
37
+ const widths = this._measureColumns();
38
+ this._rememberColumnGeometry(widths);
33
39
  MODE_CLASSES.forEach((className) => this._previousClasses.set(className, this._wrapper.classList.contains(className)));
34
40
  CONTAINER_MODE_CLASSES.forEach((className) => this._previousContainerClasses.set(className, this._container.classList.contains(className)));
35
41
  STYLE_PROPERTIES.forEach((property) => this._previousStyles.set(property, this._container.style.getPropertyValue(property)));
@@ -42,13 +48,17 @@ class _stickyHeader {
42
48
  this._container.style.setProperty('--vg-table-sticky-max-height', this._toCssLength(this._params.maxHeight, '24rem'));
43
49
  this._element.setAttribute('data-vg-table-sticky-header', this._params.mode);
44
50
 
45
- this._buildLayers(widths);
46
- this._body.addEventListener('scroll', this._boundScroll, {passive: true});
47
- if (typeof window !== 'undefined') {
48
- window.addEventListener('resize', this._boundResize);
49
- if (this._params.mode === 'page') window.addEventListener('scroll', this._boundPageScroll, {passive: true});
50
- }
51
- 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();
52
62
  return this;
53
63
  }
54
64
 
@@ -68,9 +78,9 @@ class _stickyHeader {
68
78
  refresh() {
69
79
  if (!this._header || !this._headerTable || !this._body) return this;
70
80
 
71
- const widths = this._measureBodyColumns();
72
- this._applyColgroup(this._headerTable, widths);
73
- this._applyColgroup(this._element, widths);
81
+ const widths = this._resolveLayoutWidths();
82
+ this._applyColgroup(this._headerTable, widths);
83
+ this._applyColgroup(this._element, widths);
74
84
  const scrollbar = Math.max(0, this._body.offsetWidth - this._body.clientWidth);
75
85
  this._header.style.paddingInlineEnd = `${scrollbar}px`;
76
86
  this._header.style.setProperty('--vg-table-sticky-scrollbar-width', `${scrollbar}px`);
@@ -81,12 +91,17 @@ class _stickyHeader {
81
91
  return this;
82
92
  }
83
93
 
84
- dispose() {
85
- if (this._body) this._body.removeEventListener('scroll', this._boundScroll);
86
- if (typeof window !== 'undefined') {
87
- window.removeEventListener('resize', this._boundResize);
88
- window.removeEventListener('scroll', this._boundPageScroll);
89
- }
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
+ }
90
105
  this._header?.classList.remove('is-stuck');
91
106
  if (this._head && this._headerTable?.contains(this._head)) this._element.prepend(this._head);
92
107
  this._header?.remove();
@@ -101,13 +116,25 @@ class _stickyHeader {
101
116
  CONTAINER_MODE_CLASSES.forEach((className) => {
102
117
  this._container.classList.toggle(className, this._previousContainerClasses.get(className) === true);
103
118
  });
104
- STYLE_PROPERTIES.forEach((property) => {
119
+ STYLE_PROPERTIES.forEach((property) => {
105
120
  const previous = this._previousStyles.get(property);
106
121
  if (previous) this._container.style.setProperty(property, previous);
107
122
  else this._container.style.removeProperty(property);
108
- });
109
- this._element.removeAttribute('data-vg-table-sticky-header');
110
- }
123
+ });
124
+ this._element.removeAttribute('data-vg-table-sticky-header');
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
+ }
111
138
 
112
139
  _buildLayers(widths) {
113
140
  this._header = this._element.ownerDocument.createElement('div');
@@ -131,18 +158,24 @@ class _stickyHeader {
131
158
  this._body.append(this._element);
132
159
  }
133
160
 
134
- _measureColumns() {
135
- const row = this._element.tBodies[0]?.rows[0] || this._head.rows[this._head.rows.length - 1];
136
- return this._measureRow(row);
137
- }
138
-
139
- _measureBodyColumns() {
140
- const row = this._element.tBodies[0]?.rows[0];
141
- const widths = this._measureRow(row);
142
- return widths.some((width) => width > 0) ? widths : this._measureRow(this._head.rows[this._head.rows.length - 1]);
143
- }
161
+ _measureColumns() {
162
+ return this._measureBodyColumns();
163
+ }
164
+
165
+ _measureBodyColumns() {
166
+ const headerRow = this._head?.rows[this._head.rows.length - 1];
167
+ const headerWidths = this._measureRow(headerRow);
168
+ const rows = Array.from(this._element.tBodies || []).flatMap((body) => Array.from(body.rows || []));
169
+ for (const row of rows) {
170
+ if (row.hidden || row.hasAttribute('data-vg-table-state-row') || row.hasAttribute('data-vg-table-skeleton') || row.hasAttribute('data-vg-table-skeleton-row')) continue;
171
+ if (row.querySelector('[data-vg-table-state]')) continue;
172
+ const widths = this._measureRow(row);
173
+ if (widths.length === headerWidths.length && widths.some((width) => width > 0)) return widths;
174
+ }
175
+ return headerWidths;
176
+ }
144
177
 
145
- _measureRow(row) {
178
+ _measureRow(row) {
146
179
  if (!row) return [];
147
180
  return Array.from(row.cells).flatMap((cell) => {
148
181
  const span = Math.max(1, cell.colSpan || 1);
@@ -151,22 +184,122 @@ class _stickyHeader {
151
184
  });
152
185
  }
153
186
 
154
- _applyColgroup(table, widths) {
155
- if (!widths.length) return;
156
- if (table.querySelector(':scope > colgroup:not([data-vg-table-sticky-colgroup])')) return;
157
- let colgroup = table.querySelector(':scope > colgroup[data-vg-table-sticky-colgroup]');
187
+ _applyColgroup(table, widths) {
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]');
158
191
  if (!colgroup) {
159
192
  colgroup = table.ownerDocument.createElement('colgroup');
160
- colgroup.setAttribute('data-vg-table-sticky-colgroup', '');
161
- table.prepend(colgroup);
162
- }
163
- colgroup.replaceChildren(...widths.map((width) => {
164
- const col = table.ownerDocument.createElement('col');
165
- if (width > 0) col.style.width = `${width}px`;
166
- return col;
167
- }));
168
- }
169
-
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
+ });
207
+ }
208
+
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
+ }
219
+ });
220
+ }
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
+
170
303
  _syncScroll() {
171
304
  if (this._header && this._body) this._header.scrollLeft = this._body.scrollLeft;
172
305
  }
@@ -23,17 +23,18 @@
23
23
  border-radius: var(--vg-table-sticky-radius);
24
24
  background: var(--vg-table-sticky-bg);
25
25
 
26
- .vg-table-header,
27
- .vg-table-body {
28
- width: 100%;
29
- }
30
-
31
- .vg-table-header {
32
- position: relative;
33
- overflow: hidden;
34
- background: var(--vg-table-thead-bg);
35
- border-start-start-radius: var(--vg-table-sticky-inner-radius);
36
- border-start-end-radius: var(--vg-table-sticky-inner-radius);
26
+ .vg-table-header,
27
+ .vg-table-body {
28
+ width: 100%;
29
+ }
30
+
31
+ .vg-table-header {
32
+ position: relative;
33
+ z-index: var(--vg-table-sticky-z-index);
34
+ overflow: hidden;
35
+ background: var(--vg-table-thead-bg);
36
+ border-start-start-radius: var(--vg-table-sticky-inner-radius);
37
+ border-start-end-radius: var(--vg-table-sticky-inner-radius);
37
38
 
38
39
  &--scrollbar::after {
39
40
  content: "";
@@ -46,11 +47,16 @@
46
47
  border-bottom: var(--vg-table-sticky-border-width) var(--vg-table-sticky-border-style) var(--vg-table-sticky-border-color);
47
48
  background: var(--vg-table-fixed-header-bg, var(--vg-table-thead-bg));
48
49
  pointer-events: none;
49
- }
50
- }
51
-
52
- .vg-table-header__table,
53
- .vg-table-body > .vg-table {
50
+ }
51
+ }
52
+
53
+ .vg-table-body {
54
+ position: relative;
55
+ z-index: 0;
56
+ }
57
+
58
+ .vg-table-header__table,
59
+ .vg-table-body > .vg-table {
54
60
  width: 100%;
55
61
  min-width: 100%;
56
62
  table-layout: fixed;
@@ -114,8 +120,9 @@
114
120
  }
115
121
  }
116
122
 
117
- .vg-table-body {
118
- overflow: visible;
119
- }
120
- }
121
- }
123
+ .vg-table-body {
124
+ overflow-x: auto;
125
+ overflow-y: hidden;
126
+ }
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