vgapp 1.4.7 → 1.4.9

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 (85) hide show
  1. package/README.md +24 -2
  2. package/app/modules/base-module.js +14 -2
  3. package/app/modules/module-fn.js +5 -4
  4. package/app/modules/vgalert/js/vgalert.js +21 -1
  5. package/app/modules/vgalert/scss/vgalert.scss +48 -0
  6. package/app/modules/vgdropdown/js/vgdropdown.js +58 -27
  7. package/app/modules/vgfilepreview/js/renderers/video-modal.js +5 -1
  8. package/app/modules/vgfilepreview/js/renderers/video.js +6 -1
  9. package/app/modules/vgfilepreview/js/vgfilepreview.js +5 -1
  10. package/app/modules/vgfiles/js/base.js +14 -5
  11. package/app/modules/vgfiles/js/droppable.js +37 -31
  12. package/app/modules/vgfiles/js/vgfiles.js +5 -2
  13. package/app/modules/vgfiles/scss/_variables.scss +3 -1
  14. package/app/modules/vgfiles/scss/vgfiles.scss +55 -10
  15. package/app/modules/vgformsender/js/vgformsender.js +5 -2
  16. package/app/modules/vgloadmore/js/vgloadmore.js +35 -7
  17. package/app/modules/vgnestable/js/vgnestable.js +140 -0
  18. package/app/modules/vgnestable/scss/_variables.scss +16 -9
  19. package/app/modules/vgnestable/scss/vgnestable.scss +35 -2
  20. package/app/modules/vgtable/index.js +3 -0
  21. package/app/modules/vgtable/js/_columns.js +567 -0
  22. package/app/modules/vgtable/js/_expandable.js +242 -0
  23. package/app/modules/vgtable/js/_filters.js +264 -0
  24. package/app/modules/vgtable/js/_fixed-columns.js +249 -0
  25. package/app/modules/vgtable/js/_i18n.js +75 -0
  26. package/app/modules/vgtable/js/_options.js +428 -0
  27. package/app/modules/vgtable/js/_pagination.js +491 -0
  28. package/app/modules/vgtable/js/_panning.js +124 -0
  29. package/app/modules/vgtable/js/_params-groups.js +21 -0
  30. package/app/modules/vgtable/js/_remote.js +395 -0
  31. package/app/modules/vgtable/js/_row-reorder.js +268 -0
  32. package/app/modules/vgtable/js/_search.js +134 -0
  33. package/app/modules/vgtable/js/_selection.js +113 -0
  34. package/app/modules/vgtable/js/_skeleton.js +123 -0
  35. package/app/modules/vgtable/js/_sorting.js +340 -0
  36. package/app/modules/vgtable/js/_states.js +143 -0
  37. package/app/modules/vgtable/js/_sticky-header.js +193 -0
  38. package/app/modules/vgtable/js/_url-state.js +178 -0
  39. package/app/modules/vgtable/js/vgtable.js +1444 -0
  40. package/app/modules/vgtable/readme.md +922 -0
  41. package/app/modules/vgtable/scss/_columns.scss +52 -0
  42. package/app/modules/vgtable/scss/_expandable.scss +54 -0
  43. package/app/modules/vgtable/scss/_fixed-columns.scss +117 -0
  44. package/app/modules/vgtable/scss/_pagination.scss +191 -0
  45. package/app/modules/vgtable/scss/_row-reorder.scss +45 -0
  46. package/app/modules/vgtable/scss/_skeleton.scss +65 -0
  47. package/app/modules/vgtable/scss/_sorting.scss +89 -0
  48. package/app/modules/vgtable/scss/_states.scss +70 -0
  49. package/app/modules/vgtable/scss/_sticky-header.scss +121 -0
  50. package/app/modules/vgtable/scss/_variables.scss +296 -0
  51. package/app/modules/vgtable/scss/vgtable.scss +206 -0
  52. package/app/utils/js/components/placement.js +64 -10
  53. package/app/vgapp.js +3 -3
  54. package/build/vgapp.css +1 -1
  55. package/build/vgapp.css.map +1 -1
  56. package/build/vgapp.js +1 -1
  57. package/build/vgapp.js.map +1 -1
  58. package/index.js +1 -1
  59. package/index.scss +3 -3
  60. package/package.json +5 -5
  61. package/app/modules/vgdynamictable/index.js +0 -4
  62. package/app/modules/vgdynamictable/js/editable.js +0 -438
  63. package/app/modules/vgdynamictable/js/expandable.js +0 -248
  64. package/app/modules/vgdynamictable/js/filters.js +0 -450
  65. package/app/modules/vgdynamictable/js/fixed.js +0 -566
  66. package/app/modules/vgdynamictable/js/options.js +0 -650
  67. package/app/modules/vgdynamictable/js/pagination.js +0 -623
  68. package/app/modules/vgdynamictable/js/search.js +0 -82
  69. package/app/modules/vgdynamictable/js/skeleton.js +0 -144
  70. package/app/modules/vgdynamictable/js/sortable.js +0 -442
  71. package/app/modules/vgdynamictable/js/summary-footer.js +0 -284
  72. package/app/modules/vgdynamictable/js/table-remote.js +0 -821
  73. package/app/modules/vgdynamictable/js/table-state.js +0 -243
  74. package/app/modules/vgdynamictable/js/table-url-state.js +0 -444
  75. package/app/modules/vgdynamictable/js/utils/common.js +0 -48
  76. package/app/modules/vgdynamictable/js/vgdynamictable.js +0 -3870
  77. package/app/modules/vgdynamictable/js/viewport.js +0 -322
  78. package/app/modules/vgdynamictable/readme.md +0 -251
  79. package/app/modules/vgdynamictable/scss/_actions.scss +0 -196
  80. package/app/modules/vgdynamictable/scss/_pagination.scss +0 -186
  81. package/app/modules/vgdynamictable/scss/_skeleton.scss +0 -63
  82. package/app/modules/vgdynamictable/scss/_sortable.scss +0 -66
  83. package/app/modules/vgdynamictable/scss/_table.scss +0 -137
  84. package/app/modules/vgdynamictable/scss/_variables.scss +0 -138
  85. package/app/modules/vgdynamictable/scss/vgdynamictable.scss +0 -264
@@ -0,0 +1,567 @@
1
+ /**
2
+ * Описание: единый менеджер ширины, порядка и видимости колонок VGTable.
3
+ * Возможности: pointer-resize, native drag-and-drop, внешние checkbox, localStorage, Fixed Header/Columns и публичные события.
4
+ */
5
+ import EventHandler from "../../../utils/js/dom/event";
6
+
7
+
8
+ const RESIZE_HANDLE_ATTRIBUTE = 'data-vg-table-column-resize-handle';
9
+ const COLUMN_CONTROL_SELECTOR = '[data-vg-table-column], [data-column-field]';
10
+ const RESET_SELECTOR = '[data-vg-table-columns-reset]';
11
+ const EVENT_RESIZE = 'columnresize.vg.table';
12
+ const EVENT_REORDER = 'columnreorder.vg.table';
13
+ const EVENT_VISIBILITY = 'columnvisibilitychange.vg.table';
14
+
15
+ class _columns {
16
+ constructor(table, wrapper, options = {}, hooks = {}) {
17
+ this._table = table;
18
+ this._wrapper = wrapper;
19
+ this._options = options;
20
+ this._hooks = hooks;
21
+ this._headerTable = table;
22
+ this._controls = null;
23
+ this._originalOrder = [];
24
+ this._originalCellStyles = new Map();
25
+ this._originalVisibility = new Map();
26
+ this._widths = new Map();
27
+ this._hidden = new Set();
28
+ this._drag = {active: false, from: -1};
29
+ this._resize = {active: false, index: -1, field: '', startX: 0, startWidth: 0};
30
+ this._suppressClick = false;
31
+ this._boundPointerDown = this._handlePointerDown.bind(this);
32
+ this._boundPointerMove = this._handlePointerMove.bind(this);
33
+ this._boundPointerUp = this._handlePointerUp.bind(this);
34
+ this._boundKeydown = this._handleKeydown.bind(this);
35
+ this._boundDragStart = this._handleDragStart.bind(this);
36
+ this._boundDragOver = this._handleDragOver.bind(this);
37
+ this._boundDrop = this._handleDrop.bind(this);
38
+ this._boundDragEnd = this._handleDragEnd.bind(this);
39
+ this._boundClickCapture = this._handleClickCapture.bind(this);
40
+ this._boundControlChange = this._handleControlChange.bind(this);
41
+ this._boundControlClick = this._handleControlClick.bind(this);
42
+ }
43
+
44
+ init() {
45
+ this._headerTable = this._hooks.getHeaderTable?.() || this._table;
46
+ this._originalOrder = this.getOrder();
47
+ this._rememberOriginalState();
48
+ this._controls = this._resolveControls();
49
+
50
+ if (this._options.reorder?.enabled === true) this._restoreOrder();
51
+ if (this._options.resize?.enabled === true) this._restoreWidths();
52
+ if (this._options.visibility?.enabled === true) this._restoreVisibility();
53
+ this._decorateHeaders();
54
+ this._bind();
55
+ this.refresh();
56
+ return this;
57
+ }
58
+
59
+ refresh() {
60
+ this._headerTable = this._hooks.getHeaderTable?.() || this._table;
61
+ this._decorateHeaders();
62
+ this._widths.forEach((width, field) => this._applyWidth(field, width));
63
+ this._hidden.forEach((field) => this._applyVisibility(field, false));
64
+ this._syncControls();
65
+ return this.getState();
66
+ }
67
+
68
+ getState() {
69
+ const order = this.getOrder();
70
+ return {
71
+ order,
72
+ widths: Object.fromEntries(this._widths),
73
+ visible: order.filter((field) => !this._hidden.has(field)),
74
+ hidden: order.filter((field) => this._hidden.has(field)),
75
+ };
76
+ }
77
+
78
+ getOrder() {
79
+ return this._getHeaders().map((header, index) => this._field(header, index));
80
+ }
81
+
82
+ setWidth(column, width, emit = true) {
83
+ if (this._options.resize?.enabled !== true) return false;
84
+ const index = this._resolveIndex(column);
85
+ const field = this._field(this._getHeaders()[index], index);
86
+ if (index < 0 || !field || this._hidden.has(field)) return false;
87
+ const next = this._clampWidth(width);
88
+ this._widths.set(field, next);
89
+ this._applyWidth(field, next);
90
+ this._storeWidths();
91
+ this._hooks.refreshLayout?.(false);
92
+ if (emit) EventHandler.trigger(this._table, EVENT_RESIZE, {field, columnIndex: index, width: next, widths: Object.fromEntries(this._widths)});
93
+ return true;
94
+ }
95
+
96
+ move(column, target, emit = true) {
97
+ if (this._options.reorder?.enabled !== true) return false;
98
+ const from = this._resolveIndex(column);
99
+ const to = this._resolveIndex(target);
100
+ const headers = this._getHeaders();
101
+ if (from < 0 || to < 0 || from === to || this._isLocked(headers[from], from) || this._isLocked(headers[to], to)) return false;
102
+ const field = this._field(headers[from], from);
103
+ this._moveColumn(from, to);
104
+ this._storeOrder();
105
+ this._decorateHeaders();
106
+ this._syncControls();
107
+ this._hooks.refreshLayout?.(true);
108
+ if (emit) EventHandler.trigger(this._table, EVENT_REORDER, {field, fromIndex: from, toIndex: to, order: this.getOrder()});
109
+ return true;
110
+ }
111
+
112
+ setVisible(column, visible = true, emit = true) {
113
+ if (this._options.visibility?.enabled !== true) return false;
114
+ const index = this._resolveIndex(column);
115
+ const header = this._getHeaders()[index];
116
+ const field = this._field(header, index);
117
+ if (index < 0 || !field || this._isLocked(header, index)) return false;
118
+ const nextVisible = visible !== false;
119
+ if (!nextVisible && !this._hidden.has(field) && this.getState().visible.length <= this._minVisible()) return false;
120
+ if (nextVisible) this._hidden.delete(field);
121
+ else this._hidden.add(field);
122
+ this._applyVisibility(field, nextVisible);
123
+ this._storeVisibility();
124
+ this._syncControls();
125
+ this._hooks.refreshLayout?.(true);
126
+ if (emit) EventHandler.trigger(this._table, EVENT_VISIBILITY, Object.assign({field, visible: nextVisible}, this.getState()));
127
+ return true;
128
+ }
129
+
130
+ reset(emit = true) {
131
+ this._restoreOriginalOrder();
132
+ this._restoreOriginalStyles();
133
+ this._widths.clear();
134
+ this._hidden.clear();
135
+ this._clearStorage();
136
+ this._decorateHeaders();
137
+ this._syncControls();
138
+ this._hooks.refreshLayout?.(true);
139
+ if (emit) {
140
+ const state = Object.assign({reset: true}, this.getState());
141
+ EventHandler.trigger(this._table, EVENT_RESIZE, state);
142
+ EventHandler.trigger(this._table, EVENT_REORDER, state);
143
+ EventHandler.trigger(this._table, EVENT_VISIBILITY, state);
144
+ }
145
+ return this.getState();
146
+ }
147
+
148
+ dispose() {
149
+ this._unbind();
150
+ this._restoreOriginalOrder();
151
+ this._restoreOriginalStyles();
152
+ this._getHeaders().forEach((header) => {
153
+ header.removeAttribute('data-vg-table-column-reorder');
154
+ header.removeAttribute('data-vg-table-column-dragging');
155
+ header.removeAttribute('data-vg-table-column-drag-over');
156
+ header.removeAttribute('draggable');
157
+ header.querySelector(`[${RESIZE_HANDLE_ATTRIBUTE}]`)?.remove();
158
+ });
159
+ this._wrapper?.classList.remove('vg-table-wrapper--column-resizing', 'vg-table-wrapper--column-dragging');
160
+ }
161
+
162
+ _bind() {
163
+ this._headerTable.addEventListener('pointerdown', this._boundPointerDown);
164
+ this._headerTable.addEventListener('keydown', this._boundKeydown);
165
+ this._headerTable.addEventListener('dragstart', this._boundDragStart);
166
+ this._headerTable.addEventListener('dragover', this._boundDragOver);
167
+ this._headerTable.addEventListener('drop', this._boundDrop);
168
+ this._headerTable.addEventListener('dragend', this._boundDragEnd);
169
+ this._headerTable.addEventListener('click', this._boundClickCapture, true);
170
+ document.addEventListener('pointermove', this._boundPointerMove);
171
+ document.addEventListener('pointerup', this._boundPointerUp);
172
+ this._controls?.addEventListener('change', this._boundControlChange);
173
+ this._controls?.addEventListener('click', this._boundControlClick);
174
+ }
175
+
176
+ _unbind() {
177
+ this._headerTable?.removeEventListener('pointerdown', this._boundPointerDown);
178
+ this._headerTable?.removeEventListener('keydown', this._boundKeydown);
179
+ this._headerTable?.removeEventListener('dragstart', this._boundDragStart);
180
+ this._headerTable?.removeEventListener('dragover', this._boundDragOver);
181
+ this._headerTable?.removeEventListener('drop', this._boundDrop);
182
+ this._headerTable?.removeEventListener('dragend', this._boundDragEnd);
183
+ this._headerTable?.removeEventListener('click', this._boundClickCapture, true);
184
+ document.removeEventListener('pointermove', this._boundPointerMove);
185
+ document.removeEventListener('pointerup', this._boundPointerUp);
186
+ this._controls?.removeEventListener('change', this._boundControlChange);
187
+ this._controls?.removeEventListener('click', this._boundControlClick);
188
+ }
189
+
190
+ _decorateHeaders() {
191
+ this._getHeaders().forEach((header, index) => {
192
+ const field = this._field(header, index);
193
+ const locked = this._isLocked(header, index);
194
+ if (this._options.resize?.enabled === true && !header.querySelector(`[${RESIZE_HANDLE_ATTRIBUTE}]`)) {
195
+ const handle = header.ownerDocument.createElement('span');
196
+ handle.className = 'vg-table-column-resize-handle';
197
+ handle.setAttribute(RESIZE_HANDLE_ATTRIBUTE, '');
198
+ handle.setAttribute('role', 'separator');
199
+ handle.setAttribute('aria-orientation', 'vertical');
200
+ handle.setAttribute('aria-label', this._resizeLabel(header));
201
+ handle.setAttribute('aria-valuemin', String(this._options.resize.minWidth));
202
+ handle.setAttribute('aria-valuemax', String(this._options.resize.maxWidth));
203
+ handle.setAttribute('aria-valuenow', String(Math.round(header.getBoundingClientRect().width || header.offsetWidth || this._options.resize.minWidth)));
204
+ handle.tabIndex = 0;
205
+ header.append(handle);
206
+ }
207
+ const handle = header.querySelector(`[${RESIZE_HANDLE_ATTRIBUTE}]`);
208
+ if (handle) handle.setAttribute('aria-label', this._resizeLabel(header));
209
+ if (this._options.reorder?.enabled === true) {
210
+ header.setAttribute('data-vg-table-column-reorder', locked ? 'locked' : 'enabled');
211
+ header.setAttribute('draggable', String(!locked));
212
+ }
213
+ if (field && this._hidden.has(field)) this._applyVisibility(field, false);
214
+ });
215
+ }
216
+
217
+ _handlePointerDown(event) {
218
+ const handle = event.target.closest?.(`[${RESIZE_HANDLE_ATTRIBUTE}]`);
219
+ if (!handle || this._options.resize?.enabled !== true) return;
220
+ const header = handle.closest('th');
221
+ const index = this._getHeaders().indexOf(header);
222
+ if (index < 0) return;
223
+ event.preventDefault();
224
+ event.stopPropagation();
225
+ this._resize = {
226
+ active: true,
227
+ index,
228
+ field: this._field(header, index),
229
+ startX: event.clientX,
230
+ startWidth: header.getBoundingClientRect().width || header.offsetWidth || this._options.resize.minWidth,
231
+ };
232
+ this._wrapper?.classList.add('vg-table-wrapper--column-resizing');
233
+ }
234
+
235
+ _handleKeydown(event) {
236
+ const handle = event.target.closest?.(`[${RESIZE_HANDLE_ATTRIBUTE}]`);
237
+ if (!handle || !['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
238
+ const header = handle.closest('th');
239
+ const index = this._getHeaders().indexOf(header);
240
+ if (index < 0) return;
241
+ event.preventDefault();
242
+ event.stopPropagation();
243
+ const field = this._field(header, index);
244
+ const current = this._widths.get(field) || header.getBoundingClientRect().width || header.offsetWidth || this._options.resize.minWidth;
245
+ const step = event.shiftKey ? 25 : 10;
246
+ const width = event.key === 'Home'
247
+ ? this._options.resize.minWidth
248
+ : event.key === 'End'
249
+ ? this._options.resize.maxWidth
250
+ : current + (event.key === 'ArrowRight' ? step : -step);
251
+ this.setWidth(field, width, true);
252
+ }
253
+
254
+ _handlePointerMove(event) {
255
+ if (!this._resize.active) return;
256
+ event.preventDefault();
257
+ const width = this._clampWidth(this._resize.startWidth + event.clientX - this._resize.startX);
258
+ this._widths.set(this._resize.field, width);
259
+ this._applyWidth(this._resize.field, width);
260
+ this._hooks.refreshLayout?.(false);
261
+ }
262
+
263
+ _handlePointerUp() {
264
+ if (!this._resize.active) return;
265
+ const {field} = this._resize;
266
+ const width = this._widths.get(field);
267
+ this._resize = {active: false, index: -1, field: '', startX: 0, startWidth: 0};
268
+ this._wrapper?.classList.remove('vg-table-wrapper--column-resizing');
269
+ if (width) {
270
+ this._storeWidths();
271
+ EventHandler.trigger(this._table, EVENT_RESIZE, {field, columnIndex: this._resolveIndex(field), width, widths: Object.fromEntries(this._widths)});
272
+ }
273
+ }
274
+
275
+ _handleDragStart(event) {
276
+ const header = event.target.closest?.('th[data-vg-table-column-reorder="enabled"]');
277
+ if (!header || event.target.closest?.(`[${RESIZE_HANDLE_ATTRIBUTE}]`)) return;
278
+ const from = this._getHeaders().indexOf(header);
279
+ if (from < 0) return;
280
+ this._drag = {active: true, from};
281
+ header.setAttribute('data-vg-table-column-dragging', '');
282
+ this._wrapper?.classList.add('vg-table-wrapper--column-dragging');
283
+ if (event.dataTransfer) {
284
+ event.dataTransfer.effectAllowed = 'move';
285
+ try { event.dataTransfer.setData('text/plain', String(from)); } catch (error) { /* noop */ }
286
+ }
287
+ }
288
+
289
+ _handleDragOver(event) {
290
+ if (!this._drag.active) return;
291
+ const header = event.target.closest?.('th[data-vg-table-column-reorder="enabled"]');
292
+ if (!header) return;
293
+ event.preventDefault();
294
+ this._getHeaders().forEach((item) => item.toggleAttribute('data-vg-table-column-drag-over', item === header));
295
+ }
296
+
297
+ _handleDrop(event) {
298
+ if (!this._drag.active) return;
299
+ const header = event.target.closest?.('th[data-vg-table-column-reorder="enabled"]');
300
+ if (!header) return;
301
+ event.preventDefault();
302
+ const to = this._getHeaders().indexOf(header);
303
+ const from = this._drag.from;
304
+ this._finishDrag();
305
+ this.move(from, to, true);
306
+ }
307
+
308
+ _handleDragEnd() {
309
+ if (!this._drag.active) return;
310
+ this._finishDrag();
311
+ }
312
+
313
+ _finishDrag() {
314
+ this._getHeaders().forEach((header) => {
315
+ header.removeAttribute('data-vg-table-column-dragging');
316
+ header.removeAttribute('data-vg-table-column-drag-over');
317
+ });
318
+ this._drag = {active: false, from: -1};
319
+ this._suppressClick = true;
320
+ this._wrapper?.classList.remove('vg-table-wrapper--column-dragging');
321
+ setTimeout(() => { this._suppressClick = false; }, 0);
322
+ }
323
+
324
+ _handleClickCapture(event) {
325
+ if (this._suppressClick || event.target.closest?.(`[${RESIZE_HANDLE_ATTRIBUTE}]`)) {
326
+ event.preventDefault();
327
+ event.stopPropagation();
328
+ this._suppressClick = false;
329
+ }
330
+ }
331
+
332
+ _handleControlChange(event) {
333
+ const control = event.target.closest?.(COLUMN_CONTROL_SELECTOR);
334
+ if (!control || !this._controls.contains(control)) return;
335
+ const field = control.getAttribute('data-vg-table-column') || control.getAttribute('data-column-field');
336
+ if (!this.setVisible(field, control.checked !== false)) this._syncControls();
337
+ }
338
+
339
+ _handleControlClick(event) {
340
+ if (!event.target.closest?.(RESET_SELECTOR)) return;
341
+ event.preventDefault();
342
+ this.reset(true);
343
+ }
344
+
345
+ _applyWidth(column, width) {
346
+ const index = this._resolveIndex(column);
347
+ if (index < 0) return;
348
+ this._cellsAt(index).forEach((cell) => {
349
+ this._rememberCellStyle(cell);
350
+ cell.style.width = `${width}px`;
351
+ cell.style.minWidth = `${width}px`;
352
+ cell.style.maxWidth = `${width}px`;
353
+ });
354
+ this._colsAt(index).forEach((col) => {
355
+ this._rememberCellStyle(col);
356
+ col.style.width = `${width}px`;
357
+ col.style.minWidth = `${width}px`;
358
+ col.style.maxWidth = `${width}px`;
359
+ });
360
+ this._getHeaders()[index]?.querySelector(`[${RESIZE_HANDLE_ATTRIBUTE}]`)?.setAttribute('aria-valuenow', String(width));
361
+ }
362
+
363
+ _applyVisibility(column, visible) {
364
+ const index = this._resolveIndex(column);
365
+ if (index < 0) return;
366
+ [...this._cellsAt(index), ...this._colsAt(index)].forEach((cell) => {
367
+ if (!this._originalVisibility.has(cell)) this._originalVisibility.set(cell, cell.hidden);
368
+ cell.hidden = !visible;
369
+ cell.toggleAttribute('data-vg-table-column-hidden', !visible);
370
+ });
371
+ }
372
+
373
+ _moveColumn(from, to) {
374
+ const rows = new Set([
375
+ ...Array.from(this._headerTable.rows || []),
376
+ ...Array.from(this._table.tBodies || []).flatMap((body) => Array.from(body.rows || [])),
377
+ ...Array.from(this._table.tFoot?.rows || []),
378
+ ]);
379
+ rows.forEach((row) => this._moveNode(Array.from(row.cells || []), from, to));
380
+ this._colgroups().forEach((group) => this._moveNode(Array.from(group.children || []), from, to));
381
+ }
382
+
383
+ _moveNode(nodes, from, to) {
384
+ const moving = nodes[from];
385
+ const target = nodes[to];
386
+ if (!moving || !target || moving === target) return;
387
+ if (from < to) target.after(moving);
388
+ else target.before(moving);
389
+ }
390
+
391
+ _cellsAt(index) {
392
+ const cells = [];
393
+ const rows = new Set([
394
+ ...Array.from(this._headerTable.rows || []),
395
+ ...Array.from(this._table.tBodies || []).flatMap((body) => Array.from(body.rows || [])),
396
+ ...Array.from(this._table.tFoot?.rows || []),
397
+ ]);
398
+ rows.forEach((row) => { if (!row.hasAttribute('data-vg-table-state-row') && row.cells[index]) cells.push(row.cells[index]); });
399
+ return cells;
400
+ }
401
+
402
+ _colsAt(index) {
403
+ return this._colgroups().map((group) => group.children[index]).filter(Boolean);
404
+ }
405
+
406
+ _colgroups() {
407
+ return Array.from(new Set([
408
+ ...Array.from(this._table.querySelectorAll(':scope > colgroup')),
409
+ ...Array.from(this._headerTable.querySelectorAll(':scope > colgroup')),
410
+ ]));
411
+ }
412
+
413
+ _getHeaders() {
414
+ const rows = Array.from(this._headerTable.tHead?.rows || []);
415
+ return rows.length ? Array.from(rows.at(-1).cells || []) : [];
416
+ }
417
+
418
+ _field(header, index) {
419
+ return String(header?.getAttribute('data-field') || index).trim();
420
+ }
421
+
422
+ _resolveIndex(column) {
423
+ const headers = this._getHeaders();
424
+ if (typeof column === 'number' || /^\d+$/.test(String(column ?? '').trim())) {
425
+ const index = Number.parseInt(column, 10);
426
+ return index >= 0 && index < headers.length ? index : -1;
427
+ }
428
+ const field = String(column || '').trim();
429
+ return headers.findIndex((header, index) => this._field(header, index) === field);
430
+ }
431
+
432
+ _isLocked(header, index) {
433
+ return this._hooks.isFixed?.(this._field(header, index)) === true;
434
+ }
435
+
436
+ _minVisible() {
437
+ return Math.max(1, Number.parseInt(this._options.visibility?.minVisible, 10) || 1);
438
+ }
439
+
440
+ _clampWidth(width) {
441
+ const min = Math.max(40, Number.parseInt(this._options.resize?.minWidth, 10) || 80);
442
+ const max = Math.max(min, Number.parseInt(this._options.resize?.maxWidth, 10) || 600);
443
+ return Math.min(max, Math.max(min, Math.round(Number(width) || min)));
444
+ }
445
+
446
+ _resizeLabel(header) {
447
+ const column = String(header?.textContent || '').trim();
448
+ return String(this._options.resize?.label || 'Изменить ширину колонки {column}').replace('{column}', column);
449
+ }
450
+
451
+ _resolveControls() {
452
+ const controls = this._options.visibility?.controls;
453
+ if (typeof Element !== 'undefined' && controls instanceof Element) return controls;
454
+ return controls ? this._table.ownerDocument.querySelector(String(controls)) : null;
455
+ }
456
+
457
+ _syncControls() {
458
+ this._controls?.querySelectorAll(COLUMN_CONTROL_SELECTOR).forEach((control) => {
459
+ const field = control.getAttribute('data-vg-table-column') || control.getAttribute('data-column-field');
460
+ const index = this._resolveIndex(field);
461
+ control.checked = index >= 0 && !this._hidden.has(field);
462
+ control.disabled = index < 0 || this._isLocked(this._getHeaders()[index], index);
463
+ });
464
+ }
465
+
466
+ _rememberOriginalState() {
467
+ this._getHeaders().forEach((header, index) => {
468
+ this._cellsAt(index).forEach((cell) => {
469
+ this._rememberCellStyle(cell);
470
+ if (!this._originalVisibility.has(cell)) this._originalVisibility.set(cell, cell.hidden);
471
+ });
472
+ });
473
+ this._colgroups().forEach((group) => Array.from(group.children).forEach((col) => this._rememberCellStyle(col)));
474
+ }
475
+
476
+ _rememberCellStyle(cell) {
477
+ if (this._originalCellStyles.has(cell)) return;
478
+ this._originalCellStyles.set(cell, {
479
+ width: cell.style.width,
480
+ minWidth: cell.style.minWidth,
481
+ maxWidth: cell.style.maxWidth,
482
+ });
483
+ }
484
+
485
+ _restoreOriginalStyles() {
486
+ this._originalCellStyles.forEach((style, cell) => {
487
+ cell.style.width = style.width;
488
+ cell.style.minWidth = style.minWidth;
489
+ cell.style.maxWidth = style.maxWidth;
490
+ });
491
+ this._originalVisibility.forEach((hidden, cell) => {
492
+ cell.hidden = hidden;
493
+ cell.removeAttribute('data-vg-table-column-hidden');
494
+ });
495
+ }
496
+
497
+ _restoreOriginalOrder() {
498
+ this._originalOrder.forEach((field, target) => {
499
+ const current = this._resolveIndex(field);
500
+ if (current >= 0 && current !== target) this._moveColumn(current, target);
501
+ });
502
+ }
503
+
504
+ _storageKey(group, suffix) {
505
+ const options = this._options[group] || {};
506
+ if (options.persist !== true || typeof window === 'undefined') return '';
507
+ const explicit = String(options.storageKey || '').trim();
508
+ if (explicit) return explicit;
509
+ return this._table.id ? `vg-table:${this._table.id}:${suffix}` : '';
510
+ }
511
+
512
+ _readStorage(group, suffix, fallback) {
513
+ const key = this._storageKey(group, suffix);
514
+ if (!key) return fallback;
515
+ try { return JSON.parse(window.localStorage.getItem(key)) ?? fallback; } catch (error) { return fallback; }
516
+ }
517
+
518
+ _writeStorage(group, suffix, value) {
519
+ const key = this._storageKey(group, suffix);
520
+ if (!key) return;
521
+ try { window.localStorage.setItem(key, JSON.stringify(value)); } catch (error) { /* noop */ }
522
+ }
523
+
524
+ _restoreOrder() {
525
+ const order = this._readStorage('reorder', 'column-order', []);
526
+ if (!Array.isArray(order)) return;
527
+ order.forEach((field, target) => {
528
+ const current = this._resolveIndex(field);
529
+ if (current >= 0 && current !== target
530
+ && !this._isLocked(this._getHeaders()[current], current)
531
+ && !this._isLocked(this._getHeaders()[target], target)) this._moveColumn(current, target);
532
+ });
533
+ }
534
+
535
+ _storeOrder() { this._writeStorage('reorder', 'column-order', this.getOrder()); }
536
+
537
+ _restoreWidths() {
538
+ const widths = this._readStorage('resize', 'column-widths', {});
539
+ if (!widths || typeof widths !== 'object' || Array.isArray(widths)) return;
540
+ Object.entries(widths).forEach(([field, width]) => {
541
+ if (this._resolveIndex(field) >= 0) this._widths.set(field, this._clampWidth(width));
542
+ });
543
+ }
544
+
545
+ _storeWidths() { this._writeStorage('resize', 'column-widths', Object.fromEntries(this._widths)); }
546
+
547
+ _restoreVisibility() {
548
+ const hidden = this._readStorage('visibility', 'hidden-columns', []);
549
+ if (!Array.isArray(hidden)) return;
550
+ hidden.forEach((field) => {
551
+ const index = this._resolveIndex(field);
552
+ const visibleCount = this._getHeaders().length - this._hidden.size;
553
+ if (visibleCount > this._minVisible() && index >= 0 && !this._isLocked(this._getHeaders()[index], index)) this._hidden.add(field);
554
+ });
555
+ }
556
+
557
+ _storeVisibility() { this._writeStorage('visibility', 'hidden-columns', Array.from(this._hidden)); }
558
+
559
+ _clearStorage() {
560
+ [['reorder', 'column-order'], ['resize', 'column-widths'], ['visibility', 'hidden-columns']].forEach(([group, suffix]) => {
561
+ const key = this._storageKey(group, suffix);
562
+ if (key) try { window.localStorage.removeItem(key); } catch (error) { /* noop */ }
563
+ });
564
+ }
565
+ }
566
+
567
+ export default _columns;