vgapp 1.2.5 → 1.2.6

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 (42) hide show
  1. package/CHANGELOG.md +58 -35
  2. package/agents.md +2 -2
  3. package/app/modules/base-module.js +87 -10
  4. package/app/modules/vgalert/js/vgalert.js +3 -3
  5. package/app/modules/vgdynamictable/index.js +5 -0
  6. package/app/modules/vgdynamictable/js/editable.js +438 -0
  7. package/app/modules/vgdynamictable/js/expandable.js +248 -0
  8. package/app/modules/vgdynamictable/js/filters.js +450 -0
  9. package/app/modules/vgdynamictable/js/fixed.js +566 -0
  10. package/app/modules/vgdynamictable/js/options.js +646 -0
  11. package/app/modules/vgdynamictable/js/pagination.js +623 -0
  12. package/app/modules/vgdynamictable/js/search.js +82 -0
  13. package/app/modules/vgdynamictable/js/skeleton.js +136 -0
  14. package/app/modules/vgdynamictable/js/sortable.js +442 -0
  15. package/app/modules/vgdynamictable/js/summary-footer.js +284 -0
  16. package/app/modules/vgdynamictable/js/table-remote.js +821 -0
  17. package/app/modules/vgdynamictable/js/table-state.js +243 -0
  18. package/app/modules/vgdynamictable/js/table-url-state.js +444 -0
  19. package/app/modules/vgdynamictable/js/utils/common.js +48 -0
  20. package/app/modules/vgdynamictable/js/vgdynamictable.js +3829 -0
  21. package/app/modules/vgdynamictable/js/viewport.js +322 -0
  22. package/app/modules/vgdynamictable/readme.md +251 -0
  23. package/app/modules/vgdynamictable/scss/_actions.scss +193 -0
  24. package/app/modules/vgdynamictable/scss/_pagination.scss +183 -0
  25. package/app/modules/vgdynamictable/scss/_skeleton.scss +60 -0
  26. package/app/modules/vgdynamictable/scss/_sortable.scss +63 -0
  27. package/app/modules/vgdynamictable/scss/_table.scss +157 -0
  28. package/app/modules/vgdynamictable/scss/_variables.scss +130 -0
  29. package/app/modules/vgdynamictable/scss/vgdynamictable.scss +267 -0
  30. package/app/modules/vgrangeslider/index.js +3 -0
  31. package/app/modules/vgrangeslider/js/skins.js +222 -0
  32. package/app/modules/vgrangeslider/js/vgrangeslider.js +704 -0
  33. package/app/modules/vgrangeslider/readme.md +523 -0
  34. package/app/modules/vgrangeslider/scss/_variables.scss +53 -0
  35. package/app/modules/vgrangeslider/scss/vgrangeslider.scss +240 -0
  36. package/app/utils/js/components/ajax.js +116 -7
  37. package/build/vgapp.css +2 -3246
  38. package/build/vgapp.css.map +1 -1
  39. package/build/vgapp.js +2 -30
  40. package/index.js +2 -0
  41. package/index.scss +6 -0
  42. package/package.json +1 -1
@@ -0,0 +1,136 @@
1
+ const skeletonMethods = {
2
+ _renderLoadingSkeleton() {
3
+ const body = this._getBody();
4
+ const columns = this._getRenderedColumnsCount();
5
+ const columnWidths = this._getCurrentColumnWidthsPx();
6
+ const rowsCount = this._getSkeletonRowsCount();
7
+ const rowHeight = this._getSkeletonRowHeightPx();
8
+ const rowHeightStyle = Number.isFinite(rowHeight) && rowHeight > 0
9
+ ? `height:${rowHeight}px;--vgdt-skeleton-row-height:${rowHeight}px;`
10
+ : '';
11
+
12
+ const rowHtml = Array.from({ length: rowsCount }).map((_, rowIndex) => {
13
+ const cells = Array.from({ length: columns }).map((_, columnIndex) => {
14
+ const width = Number.isFinite(columnWidths[columnIndex]) ? Math.round(columnWidths[columnIndex]) : 0;
15
+ const widthStyle = width > 0
16
+ ? ` style="width:${width}px;min-width:${width}px;max-width:${width}px;"`
17
+ : '';
18
+ return `<td${widthStyle}><div class="skeleton-cell"><div class="skeleton-line"></div></div></td>`;
19
+ }).join('');
20
+ return `<tr class="skeleton-row" style="${rowHeightStyle}--skeleton-delay:${(rowIndex * 0.08).toFixed(2)}s">${cells}</tr>`;
21
+ }).join('');
22
+
23
+ body.innerHTML = rowHtml;
24
+ this._updatePanHintVisibility();
25
+ this._refreshStickyAndFixedLayout();
26
+ },
27
+
28
+ _getRenderedColumnsCount() {
29
+ const headerCount = this._getHeaderCells().length;
30
+ const fieldsCount = this._fields.length;
31
+ const resolved = Math.max(headerCount, fieldsCount);
32
+ return resolved > 0 ? resolved : 1;
33
+ },
34
+
35
+ _getCurrentColumnWidthsPx() {
36
+ const headers = this._getHeaderCells();
37
+ const headerWidths = headers.map((header) => {
38
+ if (!header || typeof header.getBoundingClientRect !== 'function') {
39
+ return 0;
40
+ }
41
+ const rect = header.getBoundingClientRect();
42
+ return rect && Number.isFinite(rect.width) && rect.width > 0 ? rect.width : 0;
43
+ });
44
+
45
+ if (headerWidths.some((width) => width > 0)) {
46
+ return headerWidths;
47
+ }
48
+
49
+ const body = this._getBody();
50
+ const firstRow = body && body.rows && body.rows.length ? body.rows[0] : null;
51
+ if (!firstRow || !firstRow.cells || !firstRow.cells.length) {
52
+ return [];
53
+ }
54
+
55
+ return Array.from(firstRow.cells).map((cell) => {
56
+ if (!cell || typeof cell.getBoundingClientRect !== 'function') {
57
+ return 0;
58
+ }
59
+ const rect = cell.getBoundingClientRect();
60
+ return rect && Number.isFinite(rect.width) && rect.width > 0 ? rect.width : 0;
61
+ });
62
+ },
63
+
64
+ _getSkeletonRowsCount() {
65
+ const fromPerPageControl = this._getPerPageControlValue();
66
+ if (Number.isFinite(fromPerPageControl) && fromPerPageControl > 0) {
67
+ return fromPerPageControl;
68
+ }
69
+
70
+ const loadingOptions = this._params.loading || {};
71
+ const explicit = Number.parseInt(loadingOptions.skeleton, 10);
72
+ if (Number.isFinite(explicit) && explicit > 0) {
73
+ return explicit;
74
+ }
75
+
76
+ const renderedRows = this._getRenderedDataRows();
77
+ if (renderedRows.length > 0) {
78
+ return renderedRows.length;
79
+ }
80
+
81
+ const fromState = Number.parseInt(this._pageState && this._pageState.perPage, 10);
82
+ if (Number.isFinite(fromState) && fromState > 0) {
83
+ return fromState;
84
+ }
85
+
86
+ const pagination = this._params.pagination || {};
87
+ const fromParams = Number.parseInt(pagination.perPage, 10);
88
+ if (Number.isFinite(fromParams) && fromParams > 0) {
89
+ return fromParams;
90
+ }
91
+
92
+ return 10;
93
+ },
94
+
95
+ _getPerPageControlValue() {
96
+ if (!this._parent) {
97
+ return NaN;
98
+ }
99
+ const perPageControl = this._parent.querySelector('[data-per-page]');
100
+ if (!perPageControl) {
101
+ return NaN;
102
+ }
103
+ const value = Number.parseInt(perPageControl.value, 10);
104
+ return Number.isFinite(value) ? this._clampPerPage(value, 10) : NaN;
105
+ },
106
+
107
+ _getSkeletonRowHeightPx() {
108
+ const renderedRows = this._getRenderedDataRows();
109
+ if (!renderedRows.length) {
110
+ return null;
111
+ }
112
+
113
+ const firstRow = renderedRows[0];
114
+ const rect = firstRow.getBoundingClientRect();
115
+ if (!rect || !Number.isFinite(rect.height) || rect.height <= 0) {
116
+ return null;
117
+ }
118
+
119
+ return Math.round(rect.height);
120
+ },
121
+
122
+ _getRenderedDataRows() {
123
+ const body = this._getBody();
124
+ return Array.from(body.rows || []).filter((row) => {
125
+ if (!row || row.classList.contains('skeleton-row')) {
126
+ return false;
127
+ }
128
+ if (row.querySelector('[data-table-state]')) {
129
+ return false;
130
+ }
131
+ return true;
132
+ });
133
+ },
134
+ };
135
+
136
+ export default skeletonMethods;
@@ -0,0 +1,442 @@
1
+ class Sortable {
2
+ constructor(table, options = {}) {
3
+ this._table = table;
4
+ this._options = Object.assign({
5
+ initialField: '',
6
+ initialDir: 'asc',
7
+ directions: ['asc', 'desc'],
8
+ ascLabel: 'Сортировка по возрастанию',
9
+ descLabel: 'Сортировка по убыванию',
10
+ isColumnSortable: null,
11
+ onChange: null,
12
+ multiSort: false,
13
+ multiSortWithShift: true,
14
+ hideUnsortedArrows: false,
15
+ }, options);
16
+
17
+ this._headers = [];
18
+ this._sorts = [];
19
+ this._headerControls = new WeakMap();
20
+ this._boundClick = this._handleClick.bind(this);
21
+ this._boundKeyDown = this._handleKeyDown.bind(this);
22
+ }
23
+
24
+ init() {
25
+ this._headers = this._getHeaders();
26
+ if (!this._headers.length) {
27
+ return;
28
+ }
29
+ this._syncControlsVisibilityMode();
30
+
31
+ this._headers.forEach((header, index) => {
32
+ if (!this._isSortable(header, index)) {
33
+ header.setAttribute('aria-sort', 'none');
34
+ return;
35
+ }
36
+
37
+ header.dataset.sortable = '1';
38
+ header.tabIndex = 0;
39
+ header.setAttribute('role', 'button');
40
+ header.setAttribute('aria-sort', 'none');
41
+ this._decorateHeader(header);
42
+ });
43
+
44
+ this._table.addEventListener('click', this._boundClick);
45
+ this._table.addEventListener('keydown', this._boundKeyDown);
46
+ this._applyInitialState();
47
+ }
48
+
49
+ setState(payload = {}) {
50
+ if (Array.isArray(payload.sorts)) {
51
+ const normalized = this._normalizeSorts(payload.sorts);
52
+ this._setSorts(normalized, false);
53
+ return;
54
+ }
55
+
56
+ const index = this._resolveIndex(payload);
57
+ if (index < 0) {
58
+ this._setSorts([], false);
59
+ return;
60
+ }
61
+ if (!this._isSortable(this._headers[index], index)) {
62
+ return;
63
+ }
64
+ this._setSorts([{ index, dir: this._normalizeDir(payload.dir) }], false);
65
+ }
66
+
67
+ refresh(payload = {}) {
68
+ this._headers = this._getHeaders();
69
+ if (!this._headers.length) {
70
+ return;
71
+ }
72
+ this._syncControlsVisibilityMode();
73
+
74
+ this._headers.forEach((header, index) => {
75
+ if (!this._isSortable(header, index)) {
76
+ header.removeAttribute('data-sortable');
77
+ header.removeAttribute('data-sort-direction');
78
+ header.removeAttribute('data-sort-priority');
79
+ header.setAttribute('aria-sort', 'none');
80
+ return;
81
+ }
82
+
83
+ header.dataset.sortable = '1';
84
+ header.tabIndex = 0;
85
+ header.setAttribute('role', 'button');
86
+ if (!header.hasAttribute('aria-sort')) {
87
+ header.setAttribute('aria-sort', 'none');
88
+ }
89
+ this._decorateHeader(header);
90
+ });
91
+
92
+ if (Array.isArray(payload.sorts)) {
93
+ this._setSorts(this._normalizeSorts(payload.sorts), false);
94
+ return;
95
+ }
96
+
97
+ this._setSorts(this._normalizeSorts(this._sorts), false);
98
+ }
99
+
100
+ _applyInitialState() {
101
+ const index = this._resolveIndex({
102
+ field: this._options.initialField,
103
+ columnIndex: -1,
104
+ });
105
+ if (index < 0 || !this._isSortable(this._headers[index], index)) {
106
+ this._setSorts([], false);
107
+ return;
108
+ }
109
+ this._setSorts([{ index, dir: this._normalizeDir(this._options.initialDir) }], false);
110
+ }
111
+
112
+ _getHeaders() {
113
+ const head = this._table.tHead || this._table.querySelector('thead');
114
+ if (!head || !head.rows.length) {
115
+ return [];
116
+ }
117
+ return Array.from(head.rows[0].cells);
118
+ }
119
+
120
+ _isSortable(header, index) {
121
+ if (typeof this._options.isColumnSortable === 'function') {
122
+ return Boolean(this._options.isColumnSortable(header, index));
123
+ }
124
+ return true;
125
+ }
126
+
127
+ _handleClick(event) {
128
+ const header = event.target.closest('th');
129
+ if (!header || !this._table.contains(header)) {
130
+ return;
131
+ }
132
+
133
+ const index = this._headers.indexOf(header);
134
+ if (index < 0 || !this._isSortable(header, index)) {
135
+ return;
136
+ }
137
+
138
+ const arrow = event.target.closest('[data-sort-arrow]');
139
+ const multiMode = this._isMultiMode(event);
140
+ if (arrow && header.contains(arrow)) {
141
+ const dir = this._normalizeDir(arrow.getAttribute('data-sort-arrow'));
142
+ this._applyColumnDirection(index, dir, multiMode, true);
143
+ return;
144
+ }
145
+
146
+ this._cycleColumn(index, multiMode);
147
+ }
148
+
149
+ _handleKeyDown(event) {
150
+ if (event.key !== 'Enter' && event.key !== ' ') {
151
+ return;
152
+ }
153
+
154
+ const header = event.target.closest('th');
155
+ if (!header || !this._table.contains(header)) {
156
+ return;
157
+ }
158
+
159
+ const index = this._headers.indexOf(header);
160
+ if (index < 0 || !this._isSortable(header, index)) {
161
+ return;
162
+ }
163
+
164
+ event.preventDefault();
165
+ this._cycleColumn(index, this._isMultiMode(event));
166
+ }
167
+
168
+ _isMultiMode(event) {
169
+ if (!this._options.multiSort) {
170
+ return false;
171
+ }
172
+ if (!this._options.multiSortWithShift) {
173
+ return true;
174
+ }
175
+ return Boolean(event && event.shiftKey);
176
+ }
177
+
178
+ _cycleColumn(index, multiMode) {
179
+ const current = this._sorts.find((item) => item.index === index) || null;
180
+ const directions = this._getDirections();
181
+ const firstDir = directions[0];
182
+ const secondDir = directions[1] || firstDir;
183
+
184
+ if (!current) {
185
+ this._applyColumnDirection(index, firstDir, multiMode, true);
186
+ return;
187
+ }
188
+ if (current.dir === firstDir) {
189
+ this._applyColumnDirection(index, secondDir, multiMode, true);
190
+ return;
191
+ }
192
+ this._removeColumnDirection(index, multiMode, true);
193
+ }
194
+
195
+ _applyColumnDirection(index, dir, multiMode, emit) {
196
+ let next = this._sorts.slice();
197
+ next = next.filter((item) => item.index !== index);
198
+ if (!multiMode) {
199
+ next = [];
200
+ }
201
+ next.push({ index, dir: this._normalizeDir(dir) });
202
+ this._setSorts(next, emit);
203
+ }
204
+
205
+ _removeColumnDirection(index, multiMode, emit) {
206
+ let next = this._sorts.slice();
207
+ next = next.filter((item) => item.index !== index);
208
+ if (!multiMode) {
209
+ next = [];
210
+ }
211
+ this._setSorts(next, emit);
212
+ }
213
+
214
+ _setSorts(nextSorts, emit) {
215
+ this._sorts = this._normalizeSorts(nextSorts);
216
+ const sortByIndex = new Map();
217
+ this._sorts.forEach((item, index) => {
218
+ sortByIndex.set(item.index, {
219
+ dir: item.dir,
220
+ priority: index + 1,
221
+ });
222
+ });
223
+
224
+ this._headers.forEach((header, headerIndex) => {
225
+ const sort = sortByIndex.has(headerIndex) ? sortByIndex.get(headerIndex) : null;
226
+ if (!this._isSortable(header, headerIndex)) {
227
+ header.removeAttribute('data-sort-direction');
228
+ header.removeAttribute('data-sort-priority');
229
+ header.setAttribute('aria-sort', 'none');
230
+ return;
231
+ }
232
+
233
+ const controls = this._getHeaderControls(header);
234
+ const controlsNode = controls.controlsNode;
235
+ const ascButton = controls.ascButton;
236
+ const descButton = controls.descButton;
237
+ const shouldHideControls = Boolean(this._options.hideUnsortedArrows && !sort);
238
+
239
+ if (controlsNode) {
240
+ controlsNode.hidden = shouldHideControls;
241
+ }
242
+
243
+ if (sort) {
244
+ header.dataset.sortDirection = sort.dir;
245
+ header.dataset.sortPriority = String(sort.priority);
246
+ header.setAttribute('aria-sort', sort.dir === 'asc' ? 'ascending' : 'descending');
247
+ if (ascButton) ascButton.setAttribute('aria-pressed', sort.dir === 'asc' ? 'true' : 'false');
248
+ if (descButton) descButton.setAttribute('aria-pressed', sort.dir === 'desc' ? 'true' : 'false');
249
+ } else {
250
+ header.removeAttribute('data-sort-direction');
251
+ header.removeAttribute('data-sort-priority');
252
+ header.setAttribute('aria-sort', 'none');
253
+ if (ascButton) ascButton.setAttribute('aria-pressed', 'false');
254
+ if (descButton) descButton.setAttribute('aria-pressed', 'false');
255
+ }
256
+ });
257
+ this._syncSortedColumnsHighlight();
258
+
259
+ if (!emit || typeof this._options.onChange !== 'function') {
260
+ return;
261
+ }
262
+
263
+ const first = this._sorts[0] || null;
264
+ this._options.onChange({
265
+ field: first ? this._getFieldByIndex(first.index) : '',
266
+ dir: first ? first.dir : this._normalizeDir(this._options.initialDir),
267
+ columnIndex: first ? first.index : -1,
268
+ sorts: this._sorts.map((item) => ({
269
+ field: this._getFieldByIndex(item.index),
270
+ dir: item.dir,
271
+ columnIndex: item.index,
272
+ })),
273
+ });
274
+ }
275
+
276
+ _normalizeSorts(items) {
277
+ const unique = [];
278
+ const seen = new Set();
279
+ (Array.isArray(items) ? items : []).forEach((item) => {
280
+ const index = Number.parseInt(item.index ?? item.columnIndex, 10);
281
+ if (!Number.isInteger(index) || index < 0 || index >= this._headers.length) {
282
+ return;
283
+ }
284
+ if (!this._isSortable(this._headers[index], index)) {
285
+ return;
286
+ }
287
+ if (seen.has(index)) {
288
+ return;
289
+ }
290
+ seen.add(index);
291
+ unique.push({
292
+ index,
293
+ dir: this._normalizeDir(item.dir),
294
+ });
295
+ });
296
+ return unique;
297
+ }
298
+
299
+ _syncSortedColumnsHighlight() {
300
+ const rows = this._getAllRows();
301
+ if (!rows.length) {
302
+ return;
303
+ }
304
+
305
+ rows.forEach((row) => {
306
+ Array.from(row.cells || []).forEach((cell) => {
307
+ cell.removeAttribute('data-sorted-column');
308
+ cell.removeAttribute('data-sorted-priority');
309
+ });
310
+ });
311
+
312
+ this._sorts.forEach((sort, index) => {
313
+ rows.forEach((row) => {
314
+ const cell = row.cells && row.cells[sort.index] ? row.cells[sort.index] : null;
315
+ if (!cell) {
316
+ return;
317
+ }
318
+ cell.setAttribute('data-sorted-column', '1');
319
+ cell.setAttribute('data-sorted-priority', String(index + 1));
320
+ });
321
+ });
322
+ }
323
+
324
+ _getAllRows() {
325
+ const rows = [];
326
+ const sections = ['tHead', 'tBodies', 'tFoot'];
327
+
328
+ sections.forEach((section) => {
329
+ const value = this._table[section];
330
+ if (!value) {
331
+ return;
332
+ }
333
+ if (section === 'tBodies') {
334
+ Array.from(value).forEach((body) => {
335
+ rows.push(...Array.from(body.rows || []));
336
+ });
337
+ return;
338
+ }
339
+ rows.push(...Array.from(value.rows || []));
340
+ });
341
+
342
+ return rows;
343
+ }
344
+
345
+ _decorateHeader(header) {
346
+ if (header.querySelector('.vgdt-sort-controls')) {
347
+ this._headerControls.delete(header);
348
+ return;
349
+ }
350
+
351
+ const label = document.createElement('span');
352
+ label.className = 'vgdt-sort-label';
353
+ while (header.firstChild) {
354
+ label.appendChild(header.firstChild);
355
+ }
356
+
357
+ const controls = document.createElement('span');
358
+ controls.className = 'vgdt-sort-controls';
359
+ controls.innerHTML = `
360
+ <button type="button" class="vgdt-sort-arrow vgdt-sort-arrow--asc" data-sort-arrow="asc" aria-label="${this._escapeAttr(this._options.ascLabel)}" aria-pressed="false">&#9650;</button>
361
+ <button type="button" class="vgdt-sort-arrow vgdt-sort-arrow--desc" data-sort-arrow="desc" aria-label="${this._escapeAttr(this._options.descLabel)}" aria-pressed="false">&#9660;</button>
362
+ `;
363
+
364
+ label.appendChild(controls);
365
+ header.appendChild(label);
366
+ this._headerControls.delete(header);
367
+ }
368
+
369
+ _getHeaderControls(header) {
370
+ if (this._headerControls.has(header)) {
371
+ return this._headerControls.get(header);
372
+ }
373
+ const controls = {
374
+ controlsNode: header.querySelector('.vgdt-sort-controls'),
375
+ ascButton: header.querySelector('[data-sort-arrow="asc"]'),
376
+ descButton: header.querySelector('[data-sort-arrow="desc"]'),
377
+ };
378
+ this._headerControls.set(header, controls);
379
+ return controls;
380
+ }
381
+
382
+ _getFieldByIndex(index) {
383
+ const header = this._headers[index];
384
+ if (!header) {
385
+ return '';
386
+ }
387
+ return (header.getAttribute('data-field') || '').trim();
388
+ }
389
+
390
+ _resolveIndex(payload = {}) {
391
+ const field = (payload.field || '').toString().trim();
392
+ if (field) {
393
+ const byField = this._headers.findIndex((header) => {
394
+ return (header.getAttribute('data-field') || '').trim() === field;
395
+ });
396
+ if (byField >= 0) {
397
+ return byField;
398
+ }
399
+ }
400
+
401
+ const byIndex = Number.parseInt(payload.columnIndex, 10);
402
+ if (Number.isInteger(byIndex) && byIndex >= 0 && byIndex < this._headers.length) {
403
+ return byIndex;
404
+ }
405
+
406
+ return -1;
407
+ }
408
+
409
+ _normalizeDir(dir) {
410
+ const normalized = String(dir).toLowerCase();
411
+ const directions = this._getDirections();
412
+ return directions.includes(normalized) ? normalized : directions[0];
413
+ }
414
+
415
+ _getDirections() {
416
+ const list = Array.isArray(this._options.directions) ? this._options.directions : ['asc', 'desc'];
417
+ const normalized = list
418
+ .map((item) => String(item).toLowerCase().trim())
419
+ .filter((item) => item === 'asc' || item === 'desc');
420
+ return normalized.length ? Array.from(new Set(normalized)) : ['asc', 'desc'];
421
+ }
422
+
423
+ _syncControlsVisibilityMode() {
424
+ if (this._options.hideUnsortedArrows) {
425
+ this._table.setAttribute('data-sort-hide-unsorted-arrows', '1');
426
+ return;
427
+ }
428
+ this._table.removeAttribute('data-sort-hide-unsorted-arrows');
429
+ }
430
+
431
+ _escapeAttr(value) {
432
+ return String(value)
433
+ .replace(/&/g, '&amp;')
434
+ .replace(/"/g, '&quot;')
435
+ .replace(/</g, '&lt;')
436
+ .replace(/>/g, '&gt;');
437
+ }
438
+ }
439
+
440
+ export default Sortable;
441
+
442
+