vgapp 1.4.8 → 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 +22 -10
  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 +4 -4
  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,491 @@
1
+ /**
2
+ * Описание: локальная и remote-пагинация строк базовой таблицы VGTable.
3
+ * Возможности: выбор страницы и размера, серверная meta, многоточия, быстрый переход, сохранение, Fixed Header container и публичные события.
4
+ */
5
+ import EventHandler from "../../../utils/js/dom/event";
6
+ import VGDropdown from "../../vgdropdown";
7
+
8
+
9
+ const EVENT_PAGE_CHANGE = 'pagechange.vg.table';
10
+ const EVENT_PER_PAGE_CHANGE = 'perpagechange.vg.table';
11
+ const GENERATED_ATTRIBUTE = 'data-vg-table-pagination-generated';
12
+
13
+ class _pagination {
14
+ constructor(table, options = {}) {
15
+ this._table = table;
16
+ this._options = options;
17
+ this._host = table.closest('.vg-table-wrapper') || table.parentElement;
18
+ this._containers = [];
19
+ this._dropdowns = [];
20
+ this._page = this._positiveInt(options.page, 1);
21
+ this._perPage = this._clampPerPage(options.per);
22
+ this._remote = options.remote === true;
23
+ this._totalRowsValue = 0;
24
+ this._totalPagesValue = this._remote ? this._page : 1;
25
+ this._boundClick = this._handleClick.bind(this);
26
+ this._boundChange = this._handleChange.bind(this);
27
+ this._boundKeydown = this._handleKeydown.bind(this);
28
+ this._boundFocusIn = this._handleFocusIn.bind(this);
29
+ this._boundFocusOut = this._handleFocusOut.bind(this);
30
+ }
31
+
32
+ init() {
33
+ this._restoreState();
34
+ this._containers = this._resolveContainers();
35
+ this._containers.forEach((container) => {
36
+ container.addEventListener('click', this._boundClick);
37
+ container.addEventListener('change', this._boundChange);
38
+ container.addEventListener('keydown', this._boundKeydown);
39
+ container.addEventListener('focusin', this._boundFocusIn);
40
+ container.addEventListener('focusout', this._boundFocusOut);
41
+ });
42
+ this.refresh();
43
+ }
44
+
45
+ dispose() {
46
+ this._disposeDropdowns();
47
+ this._containers.forEach((container) => {
48
+ container.removeEventListener('click', this._boundClick);
49
+ container.removeEventListener('change', this._boundChange);
50
+ container.removeEventListener('keydown', this._boundKeydown);
51
+ container.removeEventListener('focusin', this._boundFocusIn);
52
+ container.removeEventListener('focusout', this._boundFocusOut);
53
+ if (container.hasAttribute(GENERATED_ATTRIBUTE)) container.remove();
54
+ });
55
+ this._rows().forEach((row) => {
56
+ row.hidden = row.getAttribute('data-vg-table-expand-hidden') === 'true'
57
+ || row.hasAttribute('data-vg-table-filter-hidden');
58
+ row.removeAttribute('data-vg-table-page-row');
59
+ });
60
+ this._containers = [];
61
+ }
62
+
63
+ setPage(page, emit = false, source = 'api') {
64
+ const nextPage = this._clampPage(page);
65
+ if (nextPage === this._page) return false;
66
+
67
+ this._page = nextPage;
68
+ this._storeState();
69
+ this._render();
70
+ this._scroll();
71
+ if (emit) this._emit(EVENT_PAGE_CHANGE, source);
72
+ this._notify(source);
73
+ return true;
74
+ }
75
+
76
+ setPerPage(perPage, emit = false, source = 'api') {
77
+ const nextPerPage = this._clampPerPage(perPage);
78
+ const changed = nextPerPage !== this._perPage;
79
+ if (!changed && this._page === 1) return false;
80
+
81
+ this._perPage = nextPerPage;
82
+ this._page = 1;
83
+ this._storeState();
84
+ this._render();
85
+ this._scroll();
86
+ if (emit) {
87
+ this._emit(EVENT_PER_PAGE_CHANGE, source);
88
+ this._emit(EVENT_PAGE_CHANGE, source);
89
+ }
90
+ this._notify(source);
91
+ return true;
92
+ }
93
+
94
+ setMeta(meta = {}) {
95
+ if (!this._remote) return this.getState();
96
+ this._totalRowsValue = this._nonNegativeInt(meta.total, this._totalRowsValue);
97
+ this._totalPagesValue = this._positiveInt(meta.pages ?? meta.totalPages, Math.max(1, Math.ceil(this._totalRowsValue / this._perPage)));
98
+ this._perPage = this._clampPerPage(meta.per_page ?? meta.perPage ?? this._perPage);
99
+ this._page = Math.min(this._positiveInt(meta.page, this._page), this._totalPagesValue);
100
+ this._storeState();
101
+ this._render();
102
+ return this.getState();
103
+ }
104
+
105
+ getState() {
106
+ return {
107
+ page: this._page,
108
+ perPage: this._perPage,
109
+ totalPages: this._totalPages(),
110
+ totalRows: this._remote ? this._totalRowsValue : this._rows().length,
111
+ };
112
+ }
113
+
114
+ refresh(resetPage = false) {
115
+ if (resetPage) this._page = 1;
116
+ this._page = this._clampPage(this._page);
117
+ this._render();
118
+ return this.getState();
119
+ }
120
+
121
+ _render() {
122
+ if (!this._remote) {
123
+ const rows = this._rows();
124
+ const start = (this._page - 1) * this._perPage;
125
+ const end = start + this._perPage;
126
+ rows.forEach((row, index) => {
127
+ const visible = index >= start && index < end;
128
+ const expandHidden = row.getAttribute('data-vg-table-expand-hidden') === 'true';
129
+ row.hidden = !visible || expandHidden;
130
+ row.setAttribute('data-vg-table-page-row', visible ? 'visible' : 'hidden');
131
+ });
132
+ }
133
+
134
+ const markup = this._buildMarkup();
135
+ this._disposeDropdowns();
136
+ this._containers.forEach((container) => {
137
+ container.className = `vg-table-pagination vg-table-pagination--${this._positionFor(container)}`;
138
+ container.innerHTML = markup;
139
+ this._initDropdowns(container);
140
+ });
141
+ }
142
+
143
+ _buildMarkup() {
144
+ const align = this._align();
145
+ const size = this._options.size || {};
146
+ const quick = this._options.quick || {};
147
+ const labels = this._options.labels || {};
148
+ const pages = this._buildPages();
149
+ const sizeMarkup = size.enabled === false ? '' : this._buildSizeMarkup(size);
150
+ const quickMarkup = this._isQuickJumpEnabled() ? `
151
+ <label class="vg-table-pagination__quick">
152
+ <span class="vg-table-pagination__sr-only">${quick.label || 'Перейти к странице'}</span>
153
+ <input type="number" min="1" max="${this._totalPages()}" value="${this._page}" data-pagination-quick-input>
154
+ <button type="button" class="vg-table-page vg-table-page--quick" data-pagination-quick-button>${quick.button || 'Перейти'}</button>
155
+ </label>` : '';
156
+
157
+ return `
158
+ <div class="vg-table-pagination__inner vg-table-pagination__inner--${align}">
159
+ ${sizeMarkup}
160
+ <nav class="vg-table-pagination__pages" aria-label="${labels.aria || 'Пагинация'}">
161
+ <button type="button" class="vg-table-page vg-table-page--prev" data-pagination-page="prev" aria-label="${labels.prev || 'Назад'}" ${this._page <= 1 ? 'disabled' : ''}>${this._buildChevron('prev')}</button>
162
+ ${pages.map((item) => this._buildPageButton(item)).join('')}
163
+ <button type="button" class="vg-table-page vg-table-page--next" data-pagination-page="next" aria-label="${labels.next || 'Вперёд'}" ${this._page >= this._totalPages() ? 'disabled' : ''}>${this._buildChevron('next')}</button>
164
+ </nav>
165
+ ${quickMarkup}
166
+ </div>`;
167
+ }
168
+
169
+ _buildSizeMarkup(size) {
170
+ const options = this._sizeOptions();
171
+ return `
172
+ <label class="vg-table-pagination__size">
173
+ ${size.label === false ? '' : `<span class="vg-table-pagination__size-heading">${size.label || 'Строк на странице'}</span>`}
174
+ <div class="vg-table-pagination__size-control vg-dropdown">
175
+ <input
176
+ type="text"
177
+ min="1"
178
+ max="${this._positiveInt(this._options.max, 100)}"
179
+ step="1"
180
+ inputmode="numeric"
181
+ data-pagination-per-page
182
+ data-vg-toggle="dropdown"
183
+ value="${this._formatPerPage(this._perPage)}"
184
+ aria-label="${size.aria || size.label || 'Строк на странице'}"
185
+ aria-haspopup="true"
186
+ >
187
+ <span class="vg-table-pagination__size-chevron" aria-hidden="true">
188
+ <svg viewBox="0 0 20 20" focusable="false" aria-hidden="true"><path d="M5 7.5L10 12.5L15 7.5" /></svg>
189
+ </span>
190
+ <div class="vg-dropdown-content vg-table-pagination__size-dropdown">
191
+ <div class="vg-dropdown-container">
192
+ ${options.map((value) => `<button type="button" class="vg-table-pagination__size-option" data-pagination-per-page-option="${value}">${this._formatPerPage(value)}</button>`).join('')}
193
+ </div>
194
+ </div>
195
+ </div>
196
+ </label>`;
197
+ }
198
+
199
+ _buildPageButton(item) {
200
+ if (typeof item === 'object') {
201
+ const hover = this._options.ellipsisHover !== false;
202
+ const labels = this._options.labels || {};
203
+ const label = item.direction === 'prev'
204
+ ? labels.ellipsisPrev || 'Перейти к предыдущим страницам'
205
+ : labels.ellipsisNext || 'Перейти к следующим страницам';
206
+ return `<button type="button" class="vg-table-page vg-table-page--ellipsis${hover ? ' vg-table-page--ellipsis-hover' : ''}" data-pagination-page="ellipsis-${item.direction}" aria-label="${label}"><span class="vg-table-page__ellipsis-dots" aria-hidden="true">…</span>${hover ? this._buildEllipsisChevron(item.direction) : ''}</button>`;
207
+ }
208
+ const active = item === this._page;
209
+ return `<button type="button" class="vg-table-page${active ? ' is-active' : ''}" data-pagination-page="${item}" ${active ? 'aria-current="page"' : ''}>${item}</button>`;
210
+ }
211
+
212
+ _buildChevron(direction) {
213
+ const path = direction === 'prev' ? 'M12.5 4.5L7 10l5.5 5.5' : 'M7.5 4.5L13 10l-5.5 5.5';
214
+ return `<svg class="vg-table-page__chevron" viewBox="0 0 20 20" focusable="false" aria-hidden="true"><path d="${path}" /></svg>`;
215
+ }
216
+
217
+ _buildEllipsisChevron(direction) {
218
+ const paths = direction === 'prev'
219
+ ? '<path d="M10.5 5L5.5 10l5 5" /><path d="M15 5l-5 5 5 5" />'
220
+ : '<path d="M9.5 5l5 5-5 5" /><path d="M5 5l5 5-5 5" />';
221
+ return `<svg class="vg-table-page__ellipsis-chevron vg-table-page__ellipsis-chevron--${direction}" viewBox="0 0 20 20" focusable="false" aria-hidden="true">${paths}</svg>`;
222
+ }
223
+
224
+ _buildPages() {
225
+ const total = this._totalPages();
226
+ const visible = Math.max(1, this._positiveInt(this._options.visible, 5));
227
+ const threshold = Math.max(1, this._positiveInt(this._options.threshold, 5));
228
+ if (this._options.ellipsis === false || total <= threshold || total <= visible + 2) {
229
+ return this._range(1, total);
230
+ }
231
+
232
+ let start = Math.max(2, this._page - Math.floor(visible / 2));
233
+ let end = Math.min(total - 1, start + visible - 1);
234
+ start = Math.max(2, end - visible + 1);
235
+ const pages = [1];
236
+ if (start > 2) pages.push({direction: 'prev'});
237
+ for (let page = start; page <= end; page += 1) pages.push(page);
238
+ if (end < total - 1) pages.push({direction: 'next'});
239
+ pages.push(total);
240
+ return pages;
241
+ }
242
+
243
+ _handleClick(event) {
244
+ const sizeOption = event.target.closest('[data-pagination-per-page-option]');
245
+ if (sizeOption) {
246
+ event.preventDefault();
247
+ const value = sizeOption.getAttribute('data-pagination-per-page-option');
248
+ this.setPerPage(value, true, 'per-page');
249
+ this._hideDropdown(sizeOption);
250
+ return;
251
+ }
252
+
253
+ const quickButton = event.target.closest('[data-pagination-quick-button]');
254
+ if (quickButton) {
255
+ const input = quickButton.closest('.vg-table-pagination__quick')?.querySelector('[data-pagination-quick-input]');
256
+ this.setPage(input?.value, true, 'quick');
257
+ return;
258
+ }
259
+
260
+ const button = event.target.closest('[data-pagination-page]');
261
+ if (!button || button.disabled) return;
262
+ const value = button.getAttribute('data-pagination-page');
263
+ if (value === 'prev') return void this.setPage(this._page - 1, true, 'prev');
264
+ if (value === 'next') return void this.setPage(this._page + 1, true, 'next');
265
+ if (value === 'ellipsis-prev') return void this.setPage(this._ellipsisTarget('prev'), true, 'ellipsis');
266
+ if (value === 'ellipsis-next') return void this.setPage(this._ellipsisTarget('next'), true, 'ellipsis');
267
+ this.setPage(value, true, 'page');
268
+ }
269
+
270
+ _handleChange(event) {
271
+ const input = event.target.closest('[data-pagination-per-page]');
272
+ if (input) this._applyPerPageInput(input);
273
+ }
274
+
275
+ _handleKeydown(event) {
276
+ const sizeInput = event.target.closest('[data-pagination-per-page]');
277
+ if (sizeInput && event.key === 'Escape') {
278
+ this._hideDropdown(sizeInput);
279
+ sizeInput.blur();
280
+ return;
281
+ }
282
+ if (sizeInput && event.key === 'Enter') {
283
+ event.preventDefault();
284
+ this._applyPerPageInput(sizeInput);
285
+ return;
286
+ }
287
+
288
+ if (event.key !== 'Enter') return;
289
+ const input = event.target.closest('[data-pagination-quick-input]');
290
+ if (!input) return;
291
+ event.preventDefault();
292
+ this.setPage(input.value, true, 'quick');
293
+ }
294
+
295
+ _handleFocusIn(event) {
296
+ const input = event.target.closest('[data-pagination-per-page]');
297
+ if (input) input.value = String(this._perPage);
298
+ }
299
+
300
+ _handleFocusOut(event) {
301
+ const input = event.target.closest('[data-pagination-per-page]');
302
+ if (!input) return;
303
+ const related = event.relatedTarget instanceof Element ? event.relatedTarget : null;
304
+ const control = input.closest('.vg-table-pagination__size-control');
305
+ if (control && related && control.contains(related)) return;
306
+ this._applyPerPageInput(input);
307
+ }
308
+
309
+ _applyPerPageInput(input) {
310
+ this.setPerPage(input.value, true, 'per-page');
311
+ input.value = this._formatPerPage(this._perPage);
312
+ }
313
+
314
+ _emit(name, source) {
315
+ EventHandler.trigger(this._table, name, Object.assign(this.getState(), {source}));
316
+ }
317
+
318
+ _notify(source) {
319
+ if (typeof this._options.onChange !== 'function') return;
320
+ this._options.onChange(Object.assign(this.getState(), {source}));
321
+ }
322
+
323
+ _resolveContainers() {
324
+ if (!this._host) return [];
325
+ const position = this._position();
326
+ const existing = Array.from(this._host.querySelectorAll('[data-vg-table-pagination]'));
327
+ if (existing.length) {
328
+ if (position === 'both') return existing;
329
+ return [existing.find((item) => item.getAttribute('data-position') === position) || existing[0]];
330
+ }
331
+
332
+ const positions = position === 'both' ? ['top', 'bottom'] : [position];
333
+ const tableAnchor = this._table.closest('.vg-table-container') || this._table;
334
+ return positions.map((item) => {
335
+ const container = document.createElement('div');
336
+ container.setAttribute(GENERATED_ATTRIBUTE, '');
337
+ container.setAttribute('data-position', item);
338
+ if (item === 'top') this._host.insertBefore(container, tableAnchor);
339
+ else this._host.insertBefore(container, tableAnchor.nextSibling);
340
+ return container;
341
+ });
342
+ }
343
+
344
+ _rows() {
345
+ return Array.from(this._table.tBodies || [])
346
+ .flatMap((body) => Array.from(body.rows || []))
347
+ .filter((row) => !row.hasAttribute('data-vg-table-filter-hidden') && !row.hasAttribute('data-vg-table-state-row'));
348
+ }
349
+
350
+ _totalPages() {
351
+ if (this._remote) return Math.max(1, this._totalPagesValue);
352
+ return Math.max(1, Math.ceil(this._rows().length / this._perPage));
353
+ }
354
+
355
+ _clampPage(value) {
356
+ return Math.min(this._positiveInt(value, 1), this._totalPages());
357
+ }
358
+
359
+ _clampPerPage(value) {
360
+ const maximum = this._positiveInt(this._options.max, 100);
361
+ return Math.min(this._positiveInt(value, 10), maximum);
362
+ }
363
+
364
+ _sizeOptions() {
365
+ const size = this._options.size || {};
366
+ const source = Array.isArray(size.options) ? size.options : [10, 25, 50, 100];
367
+ const values = source.map((value) => this._clampPerPage(value)).filter((value) => value > 0);
368
+ values.push(this._perPage);
369
+ return Array.from(new Set(values)).sort((left, right) => left - right);
370
+ }
371
+
372
+ _formatPerPage(value) {
373
+ const suffix = String((this._options.size || {}).suffix || 'на страницу').trim();
374
+ return suffix ? `${this._positiveInt(value, this._perPage)} / ${suffix}` : String(this._positiveInt(value, this._perPage));
375
+ }
376
+
377
+ _initDropdowns(container) {
378
+ container.querySelectorAll('[data-pagination-per-page][data-vg-toggle="dropdown"]').forEach((toggle) => {
379
+ const instance = VGDropdown.init(toggle, {
380
+ placement: 'auto',
381
+ hover: false,
382
+ animation: {fade: true, enable: false, delay: 120},
383
+ });
384
+ this._dropdowns.push({toggle, instance});
385
+ });
386
+ }
387
+
388
+ _disposeDropdowns() {
389
+ this._dropdowns.forEach(({instance}) => instance?.dispose?.());
390
+ this._dropdowns = [];
391
+ }
392
+
393
+ _dropdownFor(node) {
394
+ const control = node.closest('.vg-table-pagination__size-control');
395
+ const toggle = control?.querySelector('[data-pagination-per-page]');
396
+ return this._dropdowns.find((item) => item.toggle === toggle)?.instance || null;
397
+ }
398
+
399
+ _hideDropdown(node) {
400
+ this._dropdownFor(node)?.hide?.();
401
+ }
402
+
403
+ _position() {
404
+ const value = String(this._options.position || 'bottom').toLowerCase();
405
+ return ['top', 'bottom', 'both'].includes(value) ? value : 'bottom';
406
+ }
407
+
408
+ _positionFor(container) {
409
+ return container.getAttribute('data-position') || this._position().replace('both', 'bottom');
410
+ }
411
+
412
+ _align() {
413
+ const value = String(this._options.align || 'right').toLowerCase();
414
+ return ['left', 'center', 'right', 'between'].includes(value) ? value : 'right';
415
+ }
416
+
417
+ _isQuickJumpEnabled() {
418
+ const enabled = (this._options.quick || {}).enabled;
419
+ if (enabled === 'auto') return this._totalPages() > this._positiveInt(this._options.threshold, 5);
420
+ return enabled === true;
421
+ }
422
+
423
+ _ellipsisTarget(direction) {
424
+ const numeric = this._buildPages().filter(Number.isInteger);
425
+ if (direction === 'prev') return Math.max(1, (numeric[1] || 2) - 1);
426
+ return Math.min(this._totalPages(), (numeric[numeric.length - 2] || this._totalPages() - 1) + 1);
427
+ }
428
+
429
+ _scroll() {
430
+ const mode = this._options.scroll;
431
+ if (!mode || mode === false || typeof window === 'undefined') return;
432
+ if (mode === 'window') {
433
+ window.scrollTo?.({top: 0, behavior: 'smooth'});
434
+ return;
435
+ }
436
+ this._host?.scrollIntoView?.({block: 'start', behavior: 'smooth'});
437
+ }
438
+
439
+ _storageKey() {
440
+ const explicit = String((this._options.storage || {}).key || '').trim();
441
+ if (explicit) return explicit;
442
+ if (this._table.id) return `vg:table:pagination:${this._table.id}`;
443
+ const path = typeof window !== 'undefined' ? window.location.pathname : '';
444
+ const tables = Array.from(document.querySelectorAll('[data-vg-table]'));
445
+ const index = Math.max(0, tables.indexOf(this._table));
446
+ return `vg:table:pagination:${path}:${index}`;
447
+ }
448
+
449
+ _restoreState() {
450
+ if (typeof window === 'undefined' || !window.localStorage) return;
451
+ const persist = this._options.persist || {};
452
+ if (persist.page !== true && persist.per !== true) return;
453
+ try {
454
+ const saved = JSON.parse(window.localStorage.getItem(this._storageKey()) || '{}');
455
+ const savedPage = this._positiveInt(saved.page, 0);
456
+ const savedPerPage = this._positiveInt(saved.perPage, 0);
457
+ if (persist.page === true && savedPage > 0) this._page = savedPage;
458
+ if (persist.per === true && savedPerPage > 0) this._perPage = this._clampPerPage(savedPerPage);
459
+ } catch (_) {}
460
+ }
461
+
462
+ _storeState() {
463
+ if (typeof window === 'undefined' || !window.localStorage) return;
464
+ const persist = this._options.persist || {};
465
+ if (persist.page !== true && persist.per !== true) return;
466
+ const state = {};
467
+ if (persist.page === true) state.page = this._page;
468
+ if (persist.per === true) state.perPage = this._perPage;
469
+ try {
470
+ window.localStorage.setItem(this._storageKey(), JSON.stringify(state));
471
+ } catch (_) {}
472
+ }
473
+
474
+ _positiveInt(value, fallback) {
475
+ const parsed = Number.parseInt(value, 10);
476
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
477
+ }
478
+
479
+ _nonNegativeInt(value, fallback) {
480
+ const parsed = Number.parseInt(value, 10);
481
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
482
+ }
483
+
484
+ _range(start, end) {
485
+ const values = [];
486
+ for (let value = start; value <= end; value += 1) values.push(value);
487
+ return values;
488
+ }
489
+ }
490
+
491
+ export default _pagination;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Описание: горизонтальное перемещение адаптивной таблицы мышью.
3
+ * Возможности: Shift-перетаскивание, синхронное обновление связанных слоёв, защита интерактивных элементов и подавление случайного клика.
4
+ */
5
+ const INTERACTIVE_SELECTOR = 'a, button, input, select, textarea, [contenteditable="true"], [role="button"]';
6
+ const MOVE_THRESHOLD = 4;
7
+
8
+ class _panning {
9
+ constructor(table, options = {}) {
10
+ this._scrollHost = table.closest('.vg-table-body') || table.closest('.vg-table-container') || table.closest('.vg-table-wrapper');
11
+ this._stateTarget = table.closest('.vg-table-wrapper') || this._scrollHost;
12
+ this._onMove = typeof options.onMove === 'function' ? options.onMove : null;
13
+ this._active = false;
14
+ this._moved = false;
15
+ this._pointerId = null;
16
+ this._startX = 0;
17
+ this._startScrollLeft = 0;
18
+ this._suppressClick = false;
19
+ this._suppressClickTimer = null;
20
+ this._boundPointerDown = this._handlePointerDown.bind(this);
21
+ this._boundPointerMove = this._handlePointerMove.bind(this);
22
+ this._boundPointerUp = this._handlePointerUp.bind(this);
23
+ this._boundPointerCancel = this._handlePointerCancel.bind(this);
24
+ this._boundClick = this._handleClick.bind(this);
25
+ }
26
+
27
+ init() {
28
+ if (!this._scrollHost) return;
29
+
30
+ this._scrollHost.addEventListener('pointerdown', this._boundPointerDown);
31
+ this._scrollHost.addEventListener('pointermove', this._boundPointerMove);
32
+ this._scrollHost.addEventListener('pointerup', this._boundPointerUp);
33
+ this._scrollHost.addEventListener('pointercancel', this._boundPointerCancel);
34
+ this._scrollHost.addEventListener('click', this._boundClick, true);
35
+ }
36
+
37
+ dispose() {
38
+ if (!this._scrollHost) return;
39
+
40
+ this._scrollHost.removeEventListener('pointerdown', this._boundPointerDown);
41
+ this._scrollHost.removeEventListener('pointermove', this._boundPointerMove);
42
+ this._scrollHost.removeEventListener('pointerup', this._boundPointerUp);
43
+ this._scrollHost.removeEventListener('pointercancel', this._boundPointerCancel);
44
+ this._scrollHost.removeEventListener('click', this._boundClick, true);
45
+ this._scrollHost.classList.remove('is-panning');
46
+ this._stateTarget?.classList.remove('is-panning');
47
+ if (this._suppressClickTimer) window.clearTimeout(this._suppressClickTimer);
48
+ this._reset();
49
+ }
50
+
51
+ _handlePointerDown(event) {
52
+ if (!event.isPrimary || event.pointerType !== 'mouse' || event.button !== 0 || !event.shiftKey) return;
53
+ if (this._scrollHost.scrollWidth <= this._scrollHost.clientWidth) return;
54
+ if (event.target.closest(INTERACTIVE_SELECTOR)) return;
55
+
56
+ this._active = true;
57
+ this._moved = false;
58
+ this._pointerId = event.pointerId;
59
+ this._startX = event.clientX;
60
+ this._startScrollLeft = this._scrollHost.scrollLeft;
61
+ this._stateTarget.classList.add('is-panning');
62
+ if (event.cancelable) event.preventDefault();
63
+
64
+ if (typeof this._scrollHost.setPointerCapture === 'function') {
65
+ this._scrollHost.setPointerCapture(event.pointerId);
66
+ }
67
+ }
68
+
69
+ _handlePointerMove(event) {
70
+ if (!this._active || event.pointerId !== this._pointerId) return;
71
+
72
+ const deltaX = event.clientX - this._startX;
73
+ if (!this._moved && Math.abs(deltaX) > MOVE_THRESHOLD) this._moved = true;
74
+ this._scrollHost.scrollLeft = this._startScrollLeft - deltaX;
75
+ this._onMove?.(this._scrollHost.scrollLeft);
76
+
77
+ if (this._moved && event.cancelable) event.preventDefault();
78
+ }
79
+
80
+ _handlePointerUp(event) {
81
+ if (event.pointerId !== this._pointerId) return;
82
+
83
+ if (typeof this._scrollHost.releasePointerCapture === 'function') {
84
+ try {
85
+ this._scrollHost.releasePointerCapture(event.pointerId);
86
+ } catch (error) {
87
+ // Pointer capture may already be released by the browser.
88
+ }
89
+ }
90
+
91
+ if (this._moved) {
92
+ this._suppressClick = true;
93
+ this._suppressClickTimer = window.setTimeout(() => {
94
+ this._suppressClick = false;
95
+ this._suppressClickTimer = null;
96
+ }, 0);
97
+ }
98
+
99
+ this._reset();
100
+ }
101
+
102
+ _handlePointerCancel(event) {
103
+ if (event.pointerId === this._pointerId) this._reset();
104
+ }
105
+
106
+ _handleClick(event) {
107
+ if (!this._suppressClick) return;
108
+
109
+ this._suppressClick = false;
110
+ if (this._suppressClickTimer) window.clearTimeout(this._suppressClickTimer);
111
+ this._suppressClickTimer = null;
112
+ event.preventDefault();
113
+ event.stopImmediatePropagation();
114
+ }
115
+
116
+ _reset() {
117
+ this._active = false;
118
+ this._moved = false;
119
+ this._pointerId = null;
120
+ this._stateTarget?.classList.remove('is-panning');
121
+ }
122
+ }
123
+
124
+ export default _panning;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Описание: реестр именованных групп параметров VGTable.
3
+ * Возможности: регистрация общих конфигураций и их подключение одним data-group-params.
4
+ */
5
+
6
+ const groups = new Map();
7
+
8
+ const normalizeName = (name) => String(name || '').trim();
9
+
10
+ const registerParamsGroup = (name, params = {}) => {
11
+ const key = normalizeName(name);
12
+ if (!key || !params || typeof params !== 'object' || Array.isArray(params)) return false;
13
+ groups.set(key, params);
14
+ return true;
15
+ };
16
+
17
+ const unregisterParamsGroup = (name) => groups.delete(normalizeName(name));
18
+
19
+ const getParamsGroup = (name) => groups.get(normalizeName(name)) || null;
20
+
21
+ export {getParamsGroup, registerParamsGroup, unregisterParamsGroup};