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,1444 @@
1
+ /**
2
+ * Описание: основной модуль базовых таблиц VGTable.
3
+ * Возможности: i18n, wrapper/container, состояния и URL state, local/remote, sticky, управление колонками и строками, сортировка, дерево, пагинация, выбор и panning.
4
+ */
5
+ import BaseModule from "../../base-module";
6
+ import {mergeDeepObject} from "../../../utils/js/functions";
7
+ import EventHandler from "../../../utils/js/dom/event";
8
+ import Selectors from "../../../utils/js/dom/selectors";
9
+ import _sorting from "./_sorting.js";
10
+ import _panning from "./_panning.js";
11
+ import _selection from "./_selection.js";
12
+ import _pagination from "./_pagination.js";
13
+ import _expandable from "./_expandable.js";
14
+ import _stickyHeader from "./_sticky-header.js";
15
+ import _fixedColumns from "./_fixed-columns.js";
16
+ import _columns from "./_columns.js";
17
+ import _rowReorder from "./_row-reorder.js";
18
+ import _remote from "./_remote.js";
19
+ import _filters from "./_filters.js";
20
+ import _search from "./_search.js";
21
+ import _skeleton from "./_skeleton.js";
22
+ import _states from "./_states.js";
23
+ import _urlState from "./_url-state.js";
24
+ import {normalizeLocale, resolveDictionary} from "./_i18n.js";
25
+ import {getParamsGroup, registerParamsGroup, unregisterParamsGroup} from "./_params-groups.js";
26
+ import {
27
+ DEFAULT_OPTIONS,
28
+ GENERATED_TABLE_CONTAINER_ATTRIBUTE,
29
+ GENERATED_WRAPPER_ATTRIBUTE,
30
+ NAME,
31
+ NAME_KEY,
32
+ SELECTOR_DATA_TOGGLE,
33
+ TABLE_CONTAINER_SELECTOR,
34
+ WRAPPER_SELECTOR,
35
+ } from "./_options.js";
36
+
37
+ const FILTER_HIDDEN_ATTRIBUTE = 'data-vg-table-filter-hidden';
38
+
39
+ class VGTable extends BaseModule {
40
+ constructor(element, params = {}) {
41
+ super(element, params);
42
+ const paramsGroup = getParamsGroup(element?.getAttribute?.('data-group-params')) || {};
43
+ const locale = normalizeLocale(params.locale || paramsGroup.locale || element?.getAttribute?.('data-locale') || DEFAULT_OPTIONS.locale);
44
+ const dictionaries = mergeDeepObject({}, paramsGroup.i18n || {}, params.i18n || {});
45
+ this._dictionary = resolveDictionary(locale, dictionaries);
46
+ this._params = this._getParams(element, mergeDeepObject(DEFAULT_OPTIONS, this._dictionary, paramsGroup, params, {locale, i18n: dictionaries}));
47
+ this._params.locale = normalizeLocale(this._params.locale);
48
+ this._normalizeSortDataOptions();
49
+ this._normalizeRequestDataOptions();
50
+ this._normalizePaginationDataOptions();
51
+ this._normalizeSearchDataOptions();
52
+ this._normalizeFiltersDataOptions();
53
+ this._normalizeStateDataOptions();
54
+ this._normalizeUrlStateDataOptions();
55
+ this._normalizeLoadingDataOptions();
56
+ this._normalizeExpandableDataOptions();
57
+ this._normalizeStickyHeaderDataOptions();
58
+ this._normalizeFixedColumnsDataOptions();
59
+ this._normalizeColumnsDataOptions();
60
+ this._normalizeRowReorderDataOptions();
61
+ this._sorting = null;
62
+ this._panning = null;
63
+ this._selection = null;
64
+ this._expandable = null;
65
+ this._pagination = null;
66
+ this._filters = null;
67
+ this._search = null;
68
+ this._skeleton = null;
69
+ this._states = null;
70
+ this._urlState = null;
71
+ this._applyingUrlState = false;
72
+ this._filtersState = {filters: {}, params: {}, fields: [], meta: {count: 0}};
73
+ this._filterParamKeys = new Set();
74
+ this._searchState = {value: '', param: 'q'};
75
+ this._searchParamKey = '';
76
+ this._stickyHeader = null;
77
+ this._fixedColumns = null;
78
+ this._columns = null;
79
+ this._rowReorder = null;
80
+ this._remote = null;
81
+ this._isRemote = Boolean(String(this._params.request.route || '').trim());
82
+ this._complex = false;
83
+ this._wrapper = null;
84
+ this._ownsWrapper = false;
85
+ this._tableContainer = null;
86
+ this._ownsTableContainer = false;
87
+ this._boundPaginationRefresh = this._refreshPagination.bind(this);
88
+ this._boundLocalSortChange = this._handleLocalSortChange.bind(this);
89
+ this._boundRemoteSortChange = this._handleRemoteSortChange.bind(this);
90
+ }
91
+
92
+ static get NAME() { return NAME; }
93
+
94
+ static get NAME_KEY() { return NAME_KEY; }
95
+
96
+ /** Регистрирует общую конфигурацию, подключаемую через data-group-params. */
97
+ static registerParamsGroup(name, params = {}) { return registerParamsGroup(name, params); }
98
+
99
+ /** Удаляет зарегистрированную группу параметров. */
100
+ static unregisterParamsGroup(name) { return unregisterParamsGroup(name); }
101
+
102
+ /** Возвращает зарегистрированную группу параметров. */
103
+ static getParamsGroup(name) { return getParamsGroup(name); }
104
+
105
+ /**
106
+ * Инициализация таблицы
107
+ * @returns {VGTable}
108
+ */
109
+ init() {
110
+ this._ensureWrapper();
111
+ this._ensureTableContainer();
112
+ this._syncComplexState();
113
+
114
+ // Включаем нативный sticky-заголовок без клонирования DOM
115
+ if (!this._stickyHeader && this._params.stickyHeader.enabled === true) {
116
+ this._stickyHeader = new _stickyHeader(this._element, this._wrapper, this._tableContainer, this._params.stickyHeader);
117
+ this._stickyHeader.init();
118
+ }
119
+
120
+ if (!this._complex && !this._columns && this._hasColumnsFeatures()) {
121
+ this._columns = new _columns(this._element, this._wrapper, {
122
+ resize: this._params.columnResize,
123
+ reorder: this._params.columnReorder,
124
+ visibility: this._params.columnVisibility,
125
+ }, {
126
+ getHeaderTable: () => this._stickyHeader?.getHeaderTable?.() || this._element,
127
+ isFixed: (field) => this._isFixedColumnField(field),
128
+ refreshLayout: (structure = false) => this._refreshColumnsLayout(structure),
129
+ });
130
+ this._columns.init();
131
+ }
132
+
133
+ if (!this._complex && !this._rowReorder && this._params.rowReorder.enabled === true) {
134
+ this._rowReorder = new _rowReorder(this._element, this._wrapper, this._params.rowReorder, {
135
+ refreshLayout: () => this._refreshRowsLayout(),
136
+ });
137
+ this._rowReorder.init();
138
+ }
139
+
140
+ // Включаем сортировку
141
+ if (!this._complex && !this._sorting && this._params.sort.enabled === true && this._params.rowReorder.enabled !== true) {
142
+ this._sorting = new _sorting(this._element, Object.assign({}, this._params.sort, {remote: this._isRemote}), this._stickyHeader?.getHeaderTable());
143
+ this._sorting.init();
144
+ this._element.addEventListener(
145
+ 'sortchange.vg.table',
146
+ this._isRemote ? this._boundRemoteSortChange : this._boundLocalSortChange
147
+ );
148
+ }
149
+
150
+ // Фиксируем настоящие ячейки через native sticky, включая отдельный слой Fixed Header
151
+ if (!this._complex && !this._fixedColumns && this._params.fixedColumns.enabled === true) {
152
+ this._fixedColumns = new _fixedColumns(
153
+ this._element,
154
+ this._wrapper,
155
+ this._params.fixedColumns,
156
+ this._stickyHeader
157
+ );
158
+ this._fixedColumns.init();
159
+ }
160
+
161
+ // Включаем перемещение
162
+ if (!this._panning && this._params.pan.enabled === true) {
163
+ this._panning = new _panning(this._element, {
164
+ onMove: () => {
165
+ this._stickyHeader?.syncScroll?.();
166
+ this._fixedColumns?.syncScroll?.();
167
+ },
168
+ });
169
+ this._panning.init();
170
+ }
171
+
172
+ // Включаем выбор строк
173
+ if (!this._selection && this._params.selection.enabled === true) {
174
+ this._selection = new _selection(this._element, this._params.selection);
175
+ this._selection.init();
176
+ }
177
+
178
+ // Включаем многоуровневые сворачиваемые строки
179
+ if (!this._expandable && this._params.expandable.enabled === true) {
180
+ this._expandable = new _expandable(this._element, this._params.expandable);
181
+ this._expandable.init();
182
+ }
183
+
184
+ // Включаем локальную или серверную пагинацию
185
+ if (!this._pagination && this._params.pagination.enabled === true) {
186
+ this._pagination = new _pagination(this._element, Object.assign({}, this._params.pagination, {
187
+ remote: this._isRemote,
188
+ onChange: (state) => this._handlePaginationChange(state),
189
+ }));
190
+ this._pagination.init();
191
+ }
192
+
193
+ // Подключаем внешнюю форму фильтров до первого remote-запроса.
194
+ if (!this._filters && this._params.filters.enabled === true) {
195
+ this._filters = new _filters(this._element, this._params.filters, (state) => this._handleFiltersChange(state));
196
+ this._filters.init();
197
+ this._filtersState = this._filters.getState();
198
+ this._filterParamKeys = new Set(Object.keys(this._filtersState.params || {}));
199
+ if (!this._isRemote) this._applyLocalFilters(this._filtersState);
200
+ }
201
+
202
+ // Подключаем отдельное поле поиска до первого remote-запроса.
203
+ if (!this._search && this._params.search.enabled === true) {
204
+ this._search = new _search(this._element, this._params.search, (state) => this._handleSearchChange(state));
205
+ this._search.init();
206
+ this._searchState = this._search.getState();
207
+ this._searchParamKey = this._searchState.param;
208
+ if (!this._isRemote) this._applyLocalFilters(this._filtersState);
209
+ }
210
+
211
+ if (!this._states && this._params.state.enabled === true) {
212
+ this._states = new _states(this._element, this._wrapper, this._params.state, {
213
+ remote: this._isRemote,
214
+ getHeaderTable: () => this._stickyHeader?.getHeaderTable() || this._element,
215
+ reset: () => this._resetQueryControls(),
216
+ });
217
+ this._states.init();
218
+ if (!this._isRemote) this._states.syncLocal(this._hasActiveQuery());
219
+ }
220
+
221
+ if (!this._urlState && this._params.urlState.enabled === true) {
222
+ this._urlState = new _urlState(this._element, this._params.urlState, {
223
+ getFilterFields: () => this._filters?.getFields?.() || [],
224
+ apply: (state, options) => this._applyUrlState(state, options),
225
+ });
226
+ this._urlState.init();
227
+ if (this._params.urlState.read !== false) {
228
+ this._applyUrlState(this._urlState.read('init'), {source: 'init', clearMissing: false});
229
+ }
230
+ }
231
+
232
+ if (!this._remote && this._isRemote) {
233
+ this._skeleton = new _skeleton(this._element, this._params.loading, {
234
+ getPagination: () => this._pagination?.getState() || null,
235
+ getHeaderTable: () => this._stickyHeader?.getHeaderTable() || this._element,
236
+ getScrollHost: () => this._stickyHeader?.getBody() || this._tableContainer,
237
+ afterRender: () => {
238
+ this._columns?.refresh?.();
239
+ this._stickyHeader?.refresh?.();
240
+ this._fixedColumns?.refresh?.();
241
+ },
242
+ });
243
+ this._remote = new _remote(this._element, Object.assign({}, this._params.request, {labels: this._dictionary.remote || {}}), {
244
+ request: (options) => this._route(options),
245
+ buildUrl: (params, endpoint, options) => this._buildRouteUrl(params, endpoint, options),
246
+ getPagination: () => this._pagination?.getState() || {
247
+ page: this._params.pagination.page,
248
+ perPage: this._params.pagination.per,
249
+ },
250
+ getSort: () => this._sorting?.getSortState?.() || this._sorting?.getSort() || null,
251
+ getHeaderTable: () => this._stickyHeader?.getHeaderTable() || this._element,
252
+ applyMeta: (meta) => this._pagination?.setMeta(meta),
253
+ afterRender: (detail) => this._afterRemoteRender(detail),
254
+ renderLoading: () => this._skeleton?.render(),
255
+ renderEmpty: () => this._states?.render(this._hasActiveQuery() ? 'filtered-empty' : 'empty'),
256
+ renderError: () => this._states?.render('error'),
257
+ getLoadingMinDelay: () => this._params.loading.minDelay,
258
+ });
259
+ const initialParams = Object.assign({}, this._filtersState.params || {});
260
+ if (this._searchState.value) initialParams[this._searchState.param] = this._searchState.value;
261
+ if (Object.keys(initialParams).length) this._remote.setParams(initialParams);
262
+ this._remote.init();
263
+ }
264
+
265
+ return this;
266
+ }
267
+
268
+ /** Возвращает признак загрузки строк с сервера. */
269
+ isRemote() {
270
+ return this._isRemote;
271
+ }
272
+
273
+ /**
274
+ * Возвращает признак неоднородной сетки, для которой сортировка отключена.
275
+ * @returns {boolean}
276
+ */
277
+ isComplex() {
278
+ return this._complex;
279
+ }
280
+
281
+ /** Активная локаль и переключение встроенных подписей без пересоздания таблицы. */
282
+ getLocale() {
283
+ return this._params.locale;
284
+ }
285
+
286
+ setLocale(locale) {
287
+ const normalized = normalizeLocale(locale);
288
+ this._dictionary = resolveDictionary(normalized, this._params.i18n || {});
289
+ this._params.locale = normalized;
290
+ this._mergeLocale(this._params, this._dictionary);
291
+ this._normalizePaginationDataOptions();
292
+ this._normalizeStateDataOptions();
293
+ this._normalizeExpandableDataOptions();
294
+ this._normalizeColumnsDataOptions();
295
+ this._normalizeRowReorderDataOptions();
296
+ if (this._remote) this._remote._options.labels = this._dictionary.remote || {};
297
+ this._pagination?.refresh?.();
298
+ this._expandable?.refresh?.();
299
+ this._columns?.refresh?.();
300
+ this._rowReorder?.refresh?.();
301
+ const state = this._states?.getState?.();
302
+ if (state) this._states.render(state.type);
303
+ EventHandler.trigger(this._element, 'localechange.vg.table', {locale: normalized});
304
+ return normalized;
305
+ }
306
+
307
+ _mergeLocale(target, source) {
308
+ if (!target || !source || typeof source !== 'object') return target;
309
+ Object.entries(source).forEach(([key, value]) => {
310
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
311
+ if (!target[key] || typeof target[key] !== 'object' || Array.isArray(target[key])) target[key] = {};
312
+ this._mergeLocale(target[key], value);
313
+ } else target[key] = value;
314
+ });
315
+ return target;
316
+ }
317
+
318
+ /** Подготовка мультисортировки и совместимых Data API aliases. */
319
+ _normalizeSortDataOptions() {
320
+ const sort = this._params.sort;
321
+ const boolean = (value) => !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
322
+ const multiple = this._element.getAttribute('data-sort-multiple') ?? this._element.getAttribute('data-sort-multi');
323
+ const withShift = this._element.getAttribute('data-sort-multiple-with-shift') ?? this._element.getAttribute('data-sort-multi-with-shift');
324
+ if (multiple !== null) sort.multiple = boolean(multiple);
325
+ if (withShift !== null) sort.multipleWithShift = boolean(withShift);
326
+ }
327
+
328
+ /** Подготовка resize/reorder/visibility из Data API. */
329
+ _normalizeColumnsDataOptions() {
330
+ const boolean = (value) => !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
331
+ const number = (value, fallback, minimum) => {
332
+ const parsed = Number.parseInt(value, 10);
333
+ return Number.isFinite(parsed) && parsed >= minimum ? parsed : fallback;
334
+ };
335
+ const groups = [
336
+ ['columnResize', 'column-resize'],
337
+ ['columnReorder', 'column-reorder'],
338
+ ['columnVisibility', 'column-visibility'],
339
+ ];
340
+ groups.forEach(([key, prefix]) => {
341
+ const group = this._params[key];
342
+ if (Object.prototype.hasOwnProperty.call(group, 'enable')) group.enabled = boolean(group.enable);
343
+ const enabled = this._element.getAttribute(`data-${prefix}-enable`) ?? this._element.getAttribute(`data-${prefix}-enabled`);
344
+ if (enabled !== null) group.enabled = boolean(enabled);
345
+ const persist = this._element.getAttribute(`data-${prefix}-persist`);
346
+ if (persist !== null) group.persist = boolean(persist);
347
+ const storageKey = this._element.getAttribute(`data-${prefix}-storage-key`);
348
+ if (storageKey !== null) group.storageKey = storageKey;
349
+ });
350
+ const resize = this._params.columnResize;
351
+ resize.minWidth = number(this._element.getAttribute('data-column-resize-min-width') ?? resize.minWidth, 80, 40);
352
+ resize.maxWidth = number(this._element.getAttribute('data-column-resize-max-width') ?? resize.maxWidth, 600, resize.minWidth);
353
+ resize.maxWidth = Math.max(resize.minWidth, resize.maxWidth);
354
+ const visibility = this._params.columnVisibility;
355
+ const controls = this._element.getAttribute('data-column-visibility-controls');
356
+ if (controls !== null) visibility.controls = controls;
357
+ visibility.minVisible = number(this._element.getAttribute('data-column-visibility-min-visible') ?? visibility.minVisible, 1, 1);
358
+ }
359
+
360
+ /** Подготовка самостоятельного Row Reorder из Data API. */
361
+ _normalizeRowReorderDataOptions() {
362
+ const options = this._params.rowReorder;
363
+ const assign = (suffix, callback) => {
364
+ const value = this._element.getAttribute(`data-row-reorder-${suffix}`);
365
+ if (value !== null) callback(value);
366
+ };
367
+ const boolean = value => ['true', '1', 'yes', 'on'].includes(String(value).toLowerCase());
368
+ assign('enable', value => { options.enabled = boolean(value); });
369
+ assign('enabled', value => { options.enabled = boolean(value); });
370
+ assign('mode', value => { options.mode = String(value).toLowerCase() === 'row' ? 'row' : 'handle'; });
371
+ assign('handle-selector', value => { options.handleSelector = String(value).trim() || options.handleSelector; });
372
+ assign('key-attr', value => { options.keyAttr = String(value).trim() || options.keyAttr; });
373
+ assign('persist', value => { options.persist = boolean(value); });
374
+ assign('storage-key', value => { options.storageKey = String(value).trim(); });
375
+ }
376
+
377
+ _hasColumnsFeatures() {
378
+ return this._params.columnResize.enabled === true
379
+ || this._params.columnReorder.enabled === true
380
+ || this._params.columnVisibility.enabled === true;
381
+ }
382
+
383
+ /**
384
+ * Подготовка request-параметров из Data API.
385
+ * @private
386
+ */
387
+ _normalizeRequestDataOptions() {
388
+ this._params.request = mergeDeepObject(
389
+ {},
390
+ this._params.request,
391
+ {
392
+ headers: Object.assign({}, this._params.request.headers),
393
+ params: Object.assign({}, this._params.request.params),
394
+ parammap: Object.assign({}, this._params.request.parammap),
395
+ cache: Object.assign({}, this._params.request.cache),
396
+ export: Object.assign({}, this._params.request.export),
397
+ }
398
+ );
399
+ const request = this._params.request;
400
+ const read = (name) => this._element.getAttribute(`data-request-${name}`);
401
+ const assign = (name, callback) => {
402
+ const value = read(name);
403
+ if (value !== null) callback(value);
404
+ };
405
+ const boolean = (value) => !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
406
+ const number = (value, fallback) => {
407
+ const parsed = Number.parseInt(value, 10);
408
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
409
+ };
410
+ const object = (value, fallback) => {
411
+ try {
412
+ const parsed = JSON.parse(value);
413
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : fallback;
414
+ } catch (_) {
415
+ return fallback;
416
+ }
417
+ };
418
+
419
+ assign('route', (value) => { request.route = value; });
420
+ assign('method', (value) => { request.method = value; });
421
+ assign('credentials', (value) => { request.credentials = value; });
422
+ assign('params', (value) => { request.params = object(value, request.params); });
423
+ assign('parammap', (value) => { request.parammap = object(value, request.parammap); });
424
+ assign('responsemode', (value) => { request.responsemode = value; });
425
+ assign('datapath', (value) => { request.datapath = value; });
426
+ assign('metapath', (value) => { request.metapath = value; });
427
+ assign('viewpath', (value) => { request.viewpath = value; });
428
+ assign('viewparam', (value) => { request.viewparam = value; });
429
+ assign('viewvalue', (value) => { request.viewvalue = value; });
430
+ assign('fieldsparam', (value) => { request.fieldsparam = value; });
431
+ assign('cache-enable', (value) => { request.cache.enable = boolean(value); });
432
+ assign('cache-ttl', (value) => { request.cache.ttl = number(value, request.cache.ttl); });
433
+ assign('cache-max', (value) => { request.cache.max = number(value, request.cache.max); });
434
+ assign('export-route', (value) => { request.export.route = value; });
435
+
436
+ request.route = String(request.route || '').trim();
437
+ request.method = String(request.method || 'GET').toUpperCase();
438
+ request.credentials = String(request.credentials || 'same-origin');
439
+ request.responsemode = ['view', 'auto'].includes(String(request.responsemode).toLowerCase())
440
+ ? String(request.responsemode).toLowerCase()
441
+ : 'data';
442
+ }
443
+
444
+ /**
445
+ * Определяет таблицу с многострочным thead, colspan или rowspan.
446
+ * @private
447
+ */
448
+ _syncComplexState() {
449
+ const rows = this._isRemote
450
+ ? Array.from(this._element.tHead?.rows || [])
451
+ : Array.from(this._element.rows || []);
452
+ const hasSpans = rows.some((row) => Array.from(row.cells || []).some((cell) => cell.colSpan > 1 || cell.rowSpan > 1));
453
+ this._complex = Boolean((this._element.tHead?.rows.length || 0) > 1 || hasSpans);
454
+ this._element.classList.toggle('vg-table-complex', this._complex);
455
+ this._element.toggleAttribute('data-vg-table-complex', this._complex);
456
+ }
457
+
458
+ /**
459
+ * Подготовка переменных для пагинации
460
+ * @private
461
+ */
462
+ _normalizePaginationDataOptions() {
463
+ this._params.pagination = mergeDeepObject(
464
+ {},
465
+ this._params.pagination,
466
+ {
467
+ size: Object.assign({}, this._params.pagination.size),
468
+ quick: Object.assign({}, this._params.pagination.quick),
469
+ persist: Object.assign({}, this._params.pagination.persist),
470
+ storage: Object.assign({}, this._params.pagination.storage),
471
+ labels: Object.assign({}, this._params.pagination.labels),
472
+ }
473
+ );
474
+ const pagination = this._params.pagination;
475
+ const read = (name) => this._element.getAttribute(`data-pagination-${name}`);
476
+ const assign = (name, callback) => {
477
+ const value = read(name);
478
+ if (value !== null) callback(value);
479
+ };
480
+ const boolean = (value) => !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
481
+ const number = (value, fallback) => {
482
+ const parsed = Number.parseInt(value, 10);
483
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
484
+ };
485
+ const list = (value) => String(value)
486
+ .replace(/^\[|\]$/g, '')
487
+ .split(',')
488
+ .map((item) => number(item.trim(), 0))
489
+ .filter((item) => item > 0);
490
+
491
+ assign('enable', (value) => { pagination.enabled = boolean(value); });
492
+ assign('enabled', (value) => { pagination.enabled = boolean(value); });
493
+ assign('per-page', (value) => { pagination.per = number(value, pagination.per); });
494
+ assign('max-per-page', (value) => { pagination.max = number(value, pagination.max); });
495
+ assign('ellipsis', (value) => { pagination.ellipsis = boolean(value); });
496
+ assign('ellipsis-hover', (value) => { pagination.ellipsisHover = boolean(value); });
497
+ assign('ellipsis-after', (value) => { pagination.threshold = number(value, pagination.threshold); });
498
+ assign('max-visible-pages', (value) => { pagination.visible = number(value, pagination.visible); });
499
+ assign('show-per-page', (value) => { pagination.size.enabled = boolean(value); });
500
+ assign('show-per-page-label', (value) => { pagination.size.label = boolean(value) ? 'Строк на странице' : false; });
501
+ assign('per-page-label', (value) => { pagination.size.label = value; });
502
+ assign('per-page-option-suffix', (value) => { pagination.size.suffix = value; });
503
+ assign('per-page-options', (value) => { pagination.size.options = list(value); });
504
+ assign('quick-jump', (value) => { pagination.quick.enabled = value === 'auto' ? 'auto' : boolean(value); });
505
+ assign('quick-jump-button-label', (value) => { pagination.quick.button = value; });
506
+ assign('persist-page', (value) => { pagination.persist.page = boolean(value); });
507
+ assign('persist-per-page', (value) => { pagination.persist.per = boolean(value); });
508
+ assign('storage-key', (value) => { pagination.storage.key = value; });
509
+ assign('scroll-to-top', (value) => { pagination.scroll = boolean(value) ? 'table' : false; });
510
+ assign('scroll-to-window-top', (value) => { if (boolean(value)) pagination.scroll = 'window'; });
511
+ assign('prev-label', (value) => { pagination.labels.prev = value; });
512
+ assign('next-label', (value) => { pagination.labels.next = value; });
513
+ }
514
+
515
+ /**
516
+ * Подготовка параметров отдельного поля поиска из Data API.
517
+ * @private
518
+ */
519
+ _normalizeSearchDataOptions() {
520
+ this._params.search = mergeDeepObject(
521
+ {},
522
+ this._params.search,
523
+ {button: Object.assign({}, this._params.search.button)}
524
+ );
525
+ const search = this._params.search;
526
+ const read = (name) => this._element.getAttribute(`data-search-${name}`);
527
+ const assign = (name, callback) => {
528
+ const value = read(name);
529
+ if (value !== null) callback(value);
530
+ };
531
+ const boolean = (value) => !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
532
+ const number = (value, fallback) => {
533
+ const parsed = Number.parseInt(value, 10);
534
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
535
+ };
536
+ const list = (value) => {
537
+ if (Array.isArray(value)) return value.map((item) => String(item).trim()).filter(Boolean);
538
+ try {
539
+ const parsed = JSON.parse(value);
540
+ if (Array.isArray(parsed)) return parsed.map((item) => String(item).trim()).filter(Boolean);
541
+ } catch (_) {
542
+ // Строка через запятую является полноценным вариантом Data API.
543
+ }
544
+ return String(value || '').split(',').map((item) => item.trim()).filter(Boolean);
545
+ };
546
+
547
+ if (Object.prototype.hasOwnProperty.call(search, 'enable')) search.enabled = boolean(search.enable);
548
+ assign('enable', (value) => { search.enabled = boolean(value); });
549
+ assign('enabled', (value) => { search.enabled = boolean(value); });
550
+ assign('input', (value) => { search.input = value; });
551
+ assign('input-selector', (value) => { search.input = value; });
552
+ assign('param', (value) => { search.param = value; });
553
+ assign('debounce', (value) => { search.debounce = number(value, search.debounce); });
554
+ assign('debounce-ms', (value) => { search.debounce = number(value, search.debounce); });
555
+ assign('reset-page', (value) => { search.resetPage = boolean(value); });
556
+ assign('reset-page-on-change', (value) => { search.resetPage = boolean(value); });
557
+ assign('trim', (value) => { search.trim = boolean(value); });
558
+ assign('fields', (value) => { search.fields = list(value); });
559
+ assign('button-reset', (value) => { search.button.reset = value; });
560
+ search.fields = list(search.fields);
561
+ search.param = String(search.param || 'q').trim() || 'q';
562
+ }
563
+
564
+ /**
565
+ * Подготовка параметров внешней формы фильтров из Data API.
566
+ * @private
567
+ */
568
+ _normalizeFiltersDataOptions() {
569
+ this._params.filters = mergeDeepObject(
570
+ {},
571
+ this._params.filters,
572
+ {
573
+ buttons: Object.assign({}, this._params.filters.buttons),
574
+ }
575
+ );
576
+ const filters = this._params.filters;
577
+ const read = (name) => this._element.getAttribute(`data-filters-${name}`);
578
+ const assign = (name, callback) => {
579
+ const value = read(name);
580
+ if (value !== null) callback(value);
581
+ };
582
+ const boolean = (value) => !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
583
+ const number = (value, fallback) => {
584
+ const parsed = Number.parseInt(value, 10);
585
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
586
+ };
587
+
588
+ if (Object.prototype.hasOwnProperty.call(filters, 'enable')) filters.enabled = boolean(filters.enable);
589
+ assign('enable', (value) => { filters.enabled = boolean(value); });
590
+ assign('enabled', (value) => { filters.enabled = boolean(value); });
591
+ assign('form', (value) => { filters.form = value; });
592
+ assign('form-selector', (value) => { filters.form = value; });
593
+ assign('debounce', (value) => { filters.debounce = number(value, filters.debounce); });
594
+ assign('debounce-ms', (value) => { filters.debounce = number(value, filters.debounce); });
595
+ assign('apply', (value) => { filters.apply = value; });
596
+ assign('apply-mode', (value) => { filters.apply = value; });
597
+ assign('reset-page', (value) => { filters.resetPage = boolean(value); });
598
+ assign('reset-page-on-change', (value) => { filters.resetPage = boolean(value); });
599
+ assign('skip-empty', (value) => { filters.skipEmpty = boolean(value); });
600
+ assign('trim', (value) => { filters.trim = boolean(value); });
601
+ assign('trim-values', (value) => { filters.trim = boolean(value); });
602
+ assign('default-operator', (value) => { filters.defaultOperator = value; });
603
+ assign('field-attr', (value) => { filters.fieldAttr = value; });
604
+ assign('part-attr', (value) => { filters.partAttr = value; });
605
+ assign('type-attr', (value) => { filters.typeAttr = value; });
606
+ assign('value-attr', (value) => { filters.valueAttr = value; });
607
+ assign('operator-attr', (value) => { filters.operatorAttr = value; });
608
+ assign('button-apply', (value) => { filters.buttons.apply = value; });
609
+ assign('button-reset', (value) => { filters.buttons.reset = value; });
610
+
611
+ filters.apply = String(filters.apply).toLowerCase() === 'manual' ? 'manual' : 'auto';
612
+ }
613
+
614
+ /** Подготовка текстов и включения state-layer из Data API. */
615
+ _normalizeStateDataOptions() {
616
+ this._params.state = mergeDeepObject({}, this._params.state, {
617
+ labels: Object.assign({}, this._params.state.labels),
618
+ });
619
+ const state = this._params.state;
620
+ const read = (name) => this._element.getAttribute(`data-state-${name}`);
621
+ const boolean = (value) => !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
622
+ const enabled = read('enable') ?? read('enabled');
623
+ if (enabled !== null) state.enabled = boolean(enabled);
624
+ ['empty', 'filtered-empty', 'error', 'retry', 'reset'].forEach((key) => {
625
+ const value = read(`${key}-label`);
626
+ if (value !== null) state.labels[key] = value;
627
+ });
628
+ }
629
+
630
+ /** Подготовка единого URL state из Data API. */
631
+ _normalizeUrlStateDataOptions() {
632
+ this._params.urlState = mergeDeepObject({}, this._params.urlState, {
633
+ keys: Object.assign({}, this._params.urlState.keys),
634
+ include: Object.assign({}, this._params.urlState.include),
635
+ });
636
+ const state = this._params.urlState;
637
+ const read = (name) => this._element.getAttribute(`data-url-state-${name}`);
638
+ const assign = (name, callback) => {
639
+ const value = read(name);
640
+ if (value !== null) callback(value);
641
+ };
642
+ const boolean = (value) => !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
643
+ if (Object.prototype.hasOwnProperty.call(state, 'enable')) state.enabled = boolean(state.enable);
644
+ assign('enable', (value) => { state.enabled = boolean(value); });
645
+ assign('enabled', (value) => { state.enabled = boolean(value); });
646
+ assign('read', (value) => { state.read = boolean(value); });
647
+ assign('write', (value) => { state.write = boolean(value); });
648
+ assign('listen', (value) => { state.listen = boolean(value); });
649
+ assign('mode', (value) => { state.mode = value; });
650
+ assign('prefix', (value) => { state.prefix = value; });
651
+ assign('page-key', (value) => { state.keys.page = value; });
652
+ assign('per-page-key', (value) => { state.keys.perPage = value; });
653
+ assign('sort-key', (value) => { state.keys.sort = value; });
654
+ assign('direction-key', (value) => { state.keys.direction = value; });
655
+ assign('search-key', (value) => { state.keys.search = value; });
656
+ assign('filter-prefix', (value) => { state.keys.filterPrefix = value; });
657
+ assign('include-pagination', (value) => { state.include.pagination = boolean(value); });
658
+ assign('include-sort', (value) => { state.include.sort = boolean(value); });
659
+ assign('include-search', (value) => { state.include.search = boolean(value); });
660
+ assign('include-filters', (value) => { state.include.filters = boolean(value); });
661
+ state.mode = String(state.mode).toLowerCase() === 'push' ? 'push' : 'replace';
662
+ }
663
+
664
+ /**
665
+ * Подготовка параметров skeleton-загрузки из Data API.
666
+ * @private
667
+ */
668
+ _normalizeLoadingDataOptions() {
669
+ const loading = this._params.loading;
670
+ const read = (name) => this._element.getAttribute(`data-loading-${name}`);
671
+ const boolean = (value) => !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
672
+ const number = (value, fallback, minimum = 0) => {
673
+ const parsed = Number.parseInt(value, 10);
674
+ return Number.isFinite(parsed) && parsed >= minimum ? parsed : fallback;
675
+ };
676
+ const enabled = read('enable') ?? read('enabled');
677
+ if (enabled !== null) loading.enabled = boolean(enabled);
678
+ const delay = read('min-delay');
679
+ if (delay !== null) loading.minDelay = number(delay, loading.minDelay);
680
+ const rows = read('skeleton') ?? read('skeleton-rows');
681
+ if (rows !== null) loading.skeleton = number(rows, loading.skeleton, 1);
682
+ }
683
+
684
+ /**
685
+ * Подготовка параметров многоуровневых строк из Data API.
686
+ * @private
687
+ */
688
+ _normalizeExpandableDataOptions() {
689
+ const expandable = this._params.expandable;
690
+ expandable.labels = Object.assign({}, expandable.labels);
691
+ const read = (name) => this._element.getAttribute(`data-expandable-${name}`);
692
+ const assign = (name, callback) => {
693
+ const value = read(name);
694
+ if (value !== null) callback(value);
695
+ };
696
+ const boolean = (value) => !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
697
+
698
+ if (Object.prototype.hasOwnProperty.call(expandable, 'enable')) {
699
+ expandable.enabled = boolean(expandable.enable);
700
+ }
701
+ assign('enable', (value) => { expandable.enabled = boolean(value); });
702
+ assign('enabled', (value) => { expandable.enabled = boolean(value); });
703
+ assign('id-attr', (value) => { expandable.idAttr = value; });
704
+ assign('parent-attr', (value) => { expandable.parentAttr = value; });
705
+ assign('toggle-selector', (value) => { expandable.toggleSelector = value; });
706
+ assign('collapsed', (value) => { expandable.collapsed = boolean(value); });
707
+ assign('expand-label', (value) => { expandable.labels.expand = value; });
708
+ assign('collapse-label', (value) => { expandable.labels.collapse = value; });
709
+ }
710
+
711
+ /**
712
+ * Подготовка параметров sticky-заголовка из Data API и старых имён VGDynamicTable.
713
+ * @private
714
+ */
715
+ _normalizeStickyHeaderDataOptions() {
716
+ this._params.stickyHeader = Object.assign({}, this._params.stickyHeader);
717
+ const sticky = this._params.stickyHeader;
718
+ const read = (name) => this._element.getAttribute(`data-sticky-header-${name}`);
719
+ const assign = (name, callback) => {
720
+ const value = read(name);
721
+ if (value !== null) callback(value);
722
+ };
723
+ const boolean = (value) => !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
724
+
725
+ if (Object.prototype.hasOwnProperty.call(sticky, 'enable')) sticky.enabled = boolean(sticky.enable);
726
+ if (Object.prototype.hasOwnProperty.call(sticky, 'max')) sticky.maxHeight = sticky.max;
727
+ assign('enable', (value) => { sticky.enabled = boolean(value); });
728
+ assign('enabled', (value) => { sticky.enabled = boolean(value); });
729
+ assign('mode', (value) => { sticky.mode = value; });
730
+ assign('top', (value) => { sticky.top = value; });
731
+ assign('top-offset', (value) => { sticky.top = value; });
732
+ assign('max', (value) => { sticky.maxHeight = value; });
733
+ assign('max-height', (value) => { sticky.maxHeight = value; });
734
+ sticky.mode = String(sticky.mode).toLowerCase() === 'page' ? 'page' : 'container';
735
+ }
736
+
737
+ /**
738
+ * Подготовка параметров фиксированных колонок из Data API.
739
+ * @private
740
+ */
741
+ _normalizeFixedColumnsDataOptions() {
742
+ this._params.fixedColumns = Object.assign({}, this._params.fixedColumns);
743
+ const fixed = this._params.fixedColumns;
744
+ const read = (name) => this._element.getAttribute(`data-fixed-columns-${name}`);
745
+ const assign = (name, callback) => {
746
+ const value = read(name);
747
+ if (value !== null) callback(value);
748
+ };
749
+ const boolean = (value) => !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
750
+ const number = (value, fallback) => {
751
+ const parsed = Number.parseFloat(value);
752
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
753
+ };
754
+
755
+ let explicitEnabled = null;
756
+ if (Object.prototype.hasOwnProperty.call(fixed, 'enable')) fixed.enabled = boolean(fixed.enable);
757
+ const columnsAttribute = this._element.getAttribute('data-fixed-columns');
758
+ if (columnsAttribute !== null) fixed.columns = columnsAttribute;
759
+ assign('enable', (value) => { explicitEnabled = boolean(value); fixed.enabled = explicitEnabled; });
760
+ assign('enabled', (value) => { explicitEnabled = boolean(value); fixed.enabled = explicitEnabled; });
761
+ assign('mode', (value) => { fixed.mode = value; });
762
+ assign('stack-gap', (value) => { fixed.stackGap = number(value, fixed.stackGap); });
763
+ fixed.mode = String(fixed.mode).toLowerCase() === 'stack' ? 'stack' : 'fixed';
764
+ fixed.stackGap = number(fixed.stackGap, 0);
765
+ const headerHasFixed = Boolean(this._element.tHead?.querySelector('[data-fixed], [data-fixed-column]'));
766
+ if (explicitEnabled === null && (columnsAttribute !== null || headerHasFixed || (fixed.columns && typeof fixed.columns === 'object'))) {
767
+ fixed.enabled = true;
768
+ }
769
+ }
770
+
771
+ /**
772
+ * Создаёт обязательную адаптивную обёртку, если её нет в разметке.
773
+ * @private
774
+ */
775
+ _ensureWrapper() {
776
+ const wrapper = this._element.closest(WRAPPER_SELECTOR);
777
+ if (wrapper) {
778
+ this._wrapper = wrapper;
779
+ return;
780
+ }
781
+
782
+ const generatedWrapper = this._element.ownerDocument.createElement('div');
783
+ generatedWrapper.className = WRAPPER_SELECTOR.slice(1);
784
+ generatedWrapper.setAttribute(GENERATED_WRAPPER_ATTRIBUTE, '');
785
+ this._element.before(generatedWrapper);
786
+ generatedWrapper.append(this._element);
787
+ this._wrapper = generatedWrapper;
788
+ this._ownsWrapper = true;
789
+ }
790
+
791
+ /**
792
+ * Снимает только созданную модулем обёртку и возвращает таблицу на её место.
793
+ * @private
794
+ */
795
+ _removeGeneratedWrapper() {
796
+ if (!this._ownsWrapper || !this._wrapper) return;
797
+
798
+ if (this._element.parentElement === this._wrapper) {
799
+ this._wrapper.before(this._element);
800
+ }
801
+ if (!this._wrapper.children.length) this._wrapper.remove();
802
+ this._wrapper = null;
803
+ this._ownsWrapper = false;
804
+ }
805
+
806
+ /**
807
+ * Создаёт обязательный внутренний scroll-контейнер таблицы, если его нет.
808
+ * @private
809
+ */
810
+ _ensureTableContainer() {
811
+ const container = this._element.closest(TABLE_CONTAINER_SELECTOR);
812
+ if (container && container.closest(WRAPPER_SELECTOR) === this._wrapper) {
813
+ this._tableContainer = container;
814
+ return;
815
+ }
816
+
817
+ const generatedContainer = this._element.ownerDocument.createElement('div');
818
+ generatedContainer.className = TABLE_CONTAINER_SELECTOR.slice(1);
819
+ generatedContainer.setAttribute(GENERATED_TABLE_CONTAINER_ATTRIBUTE, '');
820
+ this._element.before(generatedContainer);
821
+ generatedContainer.append(this._element);
822
+ this._tableContainer = generatedContainer;
823
+ this._ownsTableContainer = true;
824
+ }
825
+
826
+ /**
827
+ * Снимает только созданный модулем container, сохраняя пользовательскую разметку.
828
+ * @private
829
+ */
830
+ _removeGeneratedTableContainer() {
831
+ if (!this._ownsTableContainer || !this._tableContainer) return;
832
+
833
+ if (this._element.parentElement === this._tableContainer) {
834
+ this._tableContainer.before(this._element);
835
+ }
836
+ if (!this._tableContainer.children.length) this._tableContainer.remove();
837
+ this._tableContainer = null;
838
+ this._ownsTableContainer = false;
839
+ }
840
+
841
+ /**
842
+ * Сортировка
843
+ * @param column
844
+ * @param direction
845
+ * @returns {*|boolean|boolean}
846
+ */
847
+ setSort(column, direction = 'asc') {
848
+ const sorted = this._sorting ? this._sorting.setSort(column, direction) : false;
849
+ if (sorted) {
850
+ this._refreshPagination();
851
+ this._syncUrlState();
852
+ }
853
+ return sorted;
854
+ }
855
+
856
+ setSorts(sorts = []) {
857
+ const sorted = this._sorting ? this._sorting.setSorts(sorts) : false;
858
+ if (sorted) {
859
+ this._refreshPagination();
860
+ this._syncUrlState();
861
+ }
862
+ return sorted;
863
+ }
864
+
865
+ getSort() {
866
+ return this._sorting ? this._sorting.getSort() : null;
867
+ }
868
+
869
+ getSorts() {
870
+ return this._sorting ? this._sorting.getSorts() : [];
871
+ }
872
+
873
+ clearSort() {
874
+ if (this._sorting) {
875
+ this._sorting.clearSort();
876
+ this._refreshPagination();
877
+ this._syncUrlState();
878
+ }
879
+ }
880
+
881
+ /**
882
+ * Выборка строк
883
+ * @param row
884
+ * @param selected
885
+ * @param emit
886
+ * @returns {*|boolean|boolean}
887
+ */
888
+ selectRow(row, selected = true, emit = true) {
889
+ return this._selection ? this._selection.selectRow(row, selected, emit) : false;
890
+ }
891
+
892
+ toggleRow(row, emit = true) {
893
+ return this._selection ? this._selection.toggleRow(row, emit) : false;
894
+ }
895
+
896
+ getSelectedRows() {
897
+ return this._selection ? this._selection.getSelectedRows() : [];
898
+ }
899
+
900
+ clearSelection(emit = true) {
901
+ return this._selection ? this._selection.clearSelection(emit) : false;
902
+ }
903
+
904
+ /**
905
+ * Многоуровневые строки
906
+ */
907
+ toggleExpanded(row, expanded = null, emit = false) {
908
+ return this._expandable ? this._expandable.toggle(row, expanded, emit) : false;
909
+ }
910
+
911
+ expandRow(row, emit = false) {
912
+ return this._expandable ? this._expandable.expand(row, emit) : false;
913
+ }
914
+
915
+ collapseRow(row, emit = false) {
916
+ return this._expandable ? this._expandable.collapse(row, emit) : false;
917
+ }
918
+
919
+ getExpandable() {
920
+ return this._expandable ? this._expandable.getState() : null;
921
+ }
922
+
923
+ refreshExpandable() {
924
+ return this._expandable ? this._expandable.refresh() : null;
925
+ }
926
+
927
+ /**
928
+ * Пагинация
929
+ */
930
+ setPage(page, emit = false) {
931
+ return this._pagination ? this._pagination.setPage(page, emit) : false;
932
+ }
933
+
934
+ setPerPage(perPage, emit = false) {
935
+ return this._pagination ? this._pagination.setPerPage(perPage, emit) : false;
936
+ }
937
+
938
+ getPagination() {
939
+ return this._pagination ? this._pagination.getState() : null;
940
+ }
941
+
942
+ refreshPagination(resetPage = false) {
943
+ return this._pagination ? this._pagination.refresh(resetPage) : null;
944
+ }
945
+
946
+ /** Фильтры. */
947
+ getFilters() {
948
+ return this._filters ? this._filters.getState() : null;
949
+ }
950
+
951
+ setFilters(values = {}, emit = true) {
952
+ return this._filters ? this._filters.setValues(values, {emit, source: 'api'}) : null;
953
+ }
954
+
955
+ resetFilters(emit = true) {
956
+ return this._filters ? this._filters.reset({emit, source: 'api-reset'}) : null;
957
+ }
958
+
959
+ refreshFilters() {
960
+ return this._filters ? this._filters.refresh() : null;
961
+ }
962
+
963
+ /** Поиск. */
964
+ getSearch() {
965
+ return this._search ? this._search.getState() : null;
966
+ }
967
+
968
+ setSearch(value, emit = true) {
969
+ return this._search ? this._search.setValue(value, {emit, source: 'api'}) : null;
970
+ }
971
+
972
+ resetSearch(emit = true) {
973
+ return this._search ? this._search.reset({emit, source: 'api-reset'}) : null;
974
+ }
975
+
976
+ refreshSearch() {
977
+ return this._search ? this._search.refresh() : null;
978
+ }
979
+
980
+ /** Состояния таблицы. */
981
+ getTableState() {
982
+ return this._states?.getState() || null;
983
+ }
984
+
985
+ showTableState(type, message = '') {
986
+ return this._states?.render(type, message) || null;
987
+ }
988
+
989
+ clearTableState() {
990
+ return this._states?.clear() || null;
991
+ }
992
+
993
+ /** Общее состояние query string. */
994
+ getUrlState() {
995
+ return this._urlState?.getState() || null;
996
+ }
997
+
998
+ refreshUrlState() {
999
+ return this._urlState ? this._applyUrlState(this._urlState.read('api'), {source: 'api', clearMissing: true}) : null;
1000
+ }
1001
+
1002
+ /**
1003
+ * Remote data
1004
+ */
1005
+ reload(options = {}) {
1006
+ return this._remote ? this._remote.reload(options) : Promise.resolve(null);
1007
+ }
1008
+
1009
+ setRequestParams(params = {}, reload = true, replace = false) {
1010
+ if (!this._remote) return null;
1011
+ const next = this._remote.setParams(params, replace);
1012
+ if (reload) this._remote.reload({page: 1, source: 'params'});
1013
+ return next;
1014
+ }
1015
+
1016
+ getRequestState() {
1017
+ return this._remote ? this._remote.getState() : null;
1018
+ }
1019
+
1020
+ clearRequestCache() {
1021
+ this._remote?.clearCache();
1022
+ }
1023
+
1024
+ exportRemote(format = 'csv', options = {}) {
1025
+ return this._remote ? this._remote.export(format, options) : '';
1026
+ }
1027
+
1028
+ refreshStickyHeader() {
1029
+ return this._stickyHeader ? this._stickyHeader.refresh() : null;
1030
+ }
1031
+
1032
+ refreshFixedColumns() {
1033
+ return this._fixedColumns ? this._fixedColumns.refresh() : null;
1034
+ }
1035
+
1036
+ getFixedColumns() {
1037
+ return this._fixedColumns ? this._fixedColumns.getState() : null;
1038
+ }
1039
+
1040
+ /** Управление шириной, порядком и видимостью колонок. */
1041
+ getColumns() {
1042
+ return this._columns ? this._columns.getState() : null;
1043
+ }
1044
+
1045
+ setColumnWidth(column, width, emit = true) {
1046
+ return this._columns ? this._columns.setWidth(column, width, emit) : false;
1047
+ }
1048
+
1049
+ moveColumn(column, target, emit = true) {
1050
+ return this._columns ? this._columns.move(column, target, emit) : false;
1051
+ }
1052
+
1053
+ setColumnVisible(column, visible = true, emit = true) {
1054
+ return this._columns ? this._columns.setVisible(column, visible, emit) : false;
1055
+ }
1056
+
1057
+ resetColumns(emit = true) {
1058
+ return this._columns ? this._columns.reset(emit) : null;
1059
+ }
1060
+
1061
+ refreshColumns() {
1062
+ const state = this._columns?.refresh?.() || null;
1063
+ this._refreshColumnsLayout(true);
1064
+ return state;
1065
+ }
1066
+
1067
+ /** Самостоятельное управление порядком строк. */
1068
+ getRowOrder() {
1069
+ return this._rowReorder ? this._rowReorder.getState().order : [];
1070
+ }
1071
+
1072
+ moveRow(row, target, position = 'before', emit = true) {
1073
+ return this._rowReorder ? this._rowReorder.move(row, target, position, emit) : false;
1074
+ }
1075
+
1076
+ resetRows(emit = true) {
1077
+ return this._rowReorder ? this._rowReorder.reset(emit) : null;
1078
+ }
1079
+
1080
+ refreshRowReorder() {
1081
+ return this._rowReorder ? this._rowReorder.refresh() : null;
1082
+ }
1083
+
1084
+ _refreshPagination() {
1085
+ if (this._pagination) this._pagination.refresh();
1086
+ if (this._expandable) this._expandable.refresh();
1087
+ }
1088
+
1089
+ _isFixedColumnField(field) {
1090
+ const normalized = String(field || '').trim();
1091
+ if (!normalized) return false;
1092
+ const headerTable = this._stickyHeader?.getHeaderTable?.() || this._element;
1093
+ const header = Array.from(headerTable.tHead?.querySelectorAll('th') || [])
1094
+ .find((cell) => String(cell.getAttribute('data-field') || '').trim() === normalized);
1095
+ if (['left', 'right'].includes(String(header?.getAttribute('data-fixed') || header?.getAttribute('data-fixed-column') || '').toLowerCase())) return true;
1096
+ const configured = this._params.fixedColumns.columns;
1097
+ if (configured && typeof configured === 'object' && !Array.isArray(configured)) {
1098
+ return ['left', 'right'].some((side) => {
1099
+ const values = Array.isArray(configured[side]) ? configured[side] : String(configured[side] || '').split(',');
1100
+ return values.map((value) => String(value).trim()).includes(normalized);
1101
+ });
1102
+ }
1103
+ return String(configured || '').split(';').some((group) => {
1104
+ const [, values = ''] = group.split(':');
1105
+ return values.split(',').map((value) => value.trim()).includes(normalized);
1106
+ });
1107
+ }
1108
+
1109
+ _refreshColumnsLayout(structure = false) {
1110
+ this._sorting?.refresh?.(structure);
1111
+ this._stickyHeader?.refresh?.();
1112
+ this._fixedColumns?.refresh?.();
1113
+ }
1114
+
1115
+ _refreshRowsLayout() {
1116
+ this._pagination?.refresh?.(false);
1117
+ this._expandable?.refresh?.();
1118
+ this._stickyHeader?.refresh?.();
1119
+ this._fixedColumns?.refresh?.();
1120
+ }
1121
+
1122
+ _handleFiltersChange(state) {
1123
+ this._filtersState = state && typeof state === 'object'
1124
+ ? state
1125
+ : {filters: {}, params: {}, fields: [], meta: {count: 0}};
1126
+ const fullReset = ['reset', 'api-reset'].includes(String(this._filtersState.source || ''));
1127
+ if (fullReset && this._search) {
1128
+ this._search.reset({emit: false, source: 'filters-reset'});
1129
+ this._searchState = this._search.getState();
1130
+ }
1131
+ if (!this._isRemote) {
1132
+ this._applyLocalFilters(this._filtersState);
1133
+ this._syncUrlState();
1134
+ return;
1135
+ }
1136
+
1137
+ const params = this._filtersState.params || {};
1138
+ const patch = {};
1139
+ this._filterParamKeys.forEach((key) => { patch[key] = ''; });
1140
+ if (fullReset && this._searchParamKey) patch[this._searchParamKey] = '';
1141
+ Object.assign(patch, params);
1142
+ this._filterParamKeys = new Set(Object.keys(params));
1143
+ this._remote?.setParams(patch);
1144
+ this._syncUrlState();
1145
+ const pagination = this._pagination?.getState();
1146
+ if (this._params.filters.resetPage !== false && pagination?.page > 1) {
1147
+ this._pagination.setPage(1, false, 'filters');
1148
+ return;
1149
+ }
1150
+ this._remote?.load({source: 'filters'});
1151
+ }
1152
+
1153
+ _handleSearchChange(state) {
1154
+ this._searchState = state && typeof state === 'object'
1155
+ ? state
1156
+ : {value: '', param: 'q'};
1157
+ if (!this._isRemote) {
1158
+ this._applyLocalFilters(this._filtersState, this._params.search.resetPage !== false);
1159
+ this._syncUrlState();
1160
+ return;
1161
+ }
1162
+
1163
+ const param = String(this._searchState.param || 'q').trim() || 'q';
1164
+ const patch = {};
1165
+ if (this._searchParamKey) patch[this._searchParamKey] = '';
1166
+ if (this._searchState.value) patch[param] = this._searchState.value;
1167
+ this._searchParamKey = param;
1168
+ this._remote?.setParams(patch);
1169
+ this._syncUrlState();
1170
+ const pagination = this._pagination?.getState();
1171
+ if (this._params.search.resetPage !== false && pagination?.page > 1) {
1172
+ this._pagination.setPage(1, false, 'search');
1173
+ return;
1174
+ }
1175
+ this._remote?.load({source: 'search'});
1176
+ }
1177
+
1178
+ _applyLocalFilters(state, resetPage = this._params.filters.resetPage !== false) {
1179
+ const filters = state?.filters && typeof state.filters === 'object' ? state.filters : {};
1180
+ const headerTable = this._stickyHeader?.getHeaderTable?.() || this._element;
1181
+ const headerRows = Array.from(headerTable.tHead?.rows || []);
1182
+ const headers = headerRows.length ? Array.from(headerRows.at(-1).cells || []) : [];
1183
+ const fields = new Map(headers.map((header, index) => [String(header.getAttribute('data-field') || '').trim(), index]));
1184
+ Array.from(this._element.tBodies || []).flatMap((body) => Array.from(body.rows || []))
1185
+ .filter((row) => !row.hasAttribute('data-vg-table-state-row'))
1186
+ .forEach((row) => {
1187
+ const matchedFilters = Object.entries(filters).every(([key, descriptor]) => {
1188
+ const field = String(descriptor?.field || key || '').trim();
1189
+ const index = fields.has(field) ? fields.get(field) : -1;
1190
+ if (index < 0 || !row.cells[index]) return true;
1191
+ const cell = row.cells[index];
1192
+ const value = cell.getAttribute('data-filter-value') ?? cell.textContent ?? '';
1193
+ return this._matchesFilterValue(value, descriptor || {});
1194
+ });
1195
+ const matched = matchedFilters && this._matchesSearchRow(row, headers);
1196
+ row.toggleAttribute(FILTER_HIDDEN_ATTRIBUTE, !matched);
1197
+ if (!matched) row.hidden = true;
1198
+ else if (!this._pagination) row.hidden = row.getAttribute('data-vg-table-expand-hidden') === 'true';
1199
+ });
1200
+ if (this._pagination) this._pagination.refresh(resetPage);
1201
+ this._expandable?.refresh();
1202
+ this._stickyHeader?.refresh?.();
1203
+ this._fixedColumns?.refresh?.();
1204
+ this._states?.syncLocal(this._hasActiveQuery());
1205
+ }
1206
+
1207
+ _matchesSearchRow(row, headers) {
1208
+ const query = String(this._searchState?.value || '').toLocaleLowerCase();
1209
+ if (!query) return true;
1210
+ const configuredFields = new Set((this._params.search.fields || []).map((field) => String(field).trim()));
1211
+ const indexes = headers.reduce((result, header, index) => {
1212
+ const field = String(header.getAttribute('data-field') || '').trim();
1213
+ if (header.getAttribute('data-search-enabled') === 'false') return result;
1214
+ if (configuredFields.size && !configuredFields.has(field)) return result;
1215
+ result.push(index);
1216
+ return result;
1217
+ }, []);
1218
+ const rowValue = row.getAttribute('data-search-value');
1219
+ const value = rowValue !== null
1220
+ ? rowValue
1221
+ : indexes.map((index) => {
1222
+ const cell = row.cells[index];
1223
+ return cell?.getAttribute('data-search-value') ?? cell?.textContent ?? '';
1224
+ }).join(' ');
1225
+ return String(value).toLocaleLowerCase().includes(query);
1226
+ }
1227
+
1228
+ _matchesFilterValue(rawValue, descriptor) {
1229
+ const values = Array.isArray(descriptor.values) && descriptor.values.length
1230
+ ? descriptor.values
1231
+ : descriptor.value;
1232
+ const operator = String(descriptor.operator || descriptor.type || 'eq').toLowerCase().trim();
1233
+ const left = String(rawValue ?? '').trim();
1234
+ if (operator === 'empty') return left === '';
1235
+ if (operator === 'notempty') return left !== '';
1236
+ if (Array.isArray(values)) {
1237
+ const choices = values.map((value) => String(value ?? '').toLocaleLowerCase().trim()).filter(Boolean);
1238
+ const included = choices.includes(left.toLocaleLowerCase());
1239
+ return operator === 'notin' ? !included : included;
1240
+ }
1241
+ const right = String(values ?? '').trim();
1242
+ if (!right) return true;
1243
+ const leftText = left.toLocaleLowerCase();
1244
+ const rightText = right.toLocaleLowerCase();
1245
+ if (operator === 'contains') return leftText.includes(rightText);
1246
+ if (operator === 'starts') return leftText.startsWith(rightText);
1247
+ if (operator === 'ends') return leftText.endsWith(rightText);
1248
+ const leftNumber = Number.parseFloat(left.replace(',', '.'));
1249
+ const rightNumber = Number.parseFloat(right.replace(',', '.'));
1250
+ const numeric = Number.isFinite(leftNumber) && Number.isFinite(rightNumber);
1251
+ const leftDate = descriptor.type === 'date' ? Date.parse(left) : NaN;
1252
+ const rightDate = descriptor.type === 'date' ? Date.parse(right) : NaN;
1253
+ const comparableLeft = Number.isFinite(leftDate) && Number.isFinite(rightDate) ? leftDate : leftNumber;
1254
+ const comparableRight = Number.isFinite(leftDate) && Number.isFinite(rightDate) ? rightDate : rightNumber;
1255
+ const comparable = numeric || (Number.isFinite(leftDate) && Number.isFinite(rightDate));
1256
+ if (operator === 'neq') return comparable ? comparableLeft !== comparableRight : leftText !== rightText;
1257
+ if (operator === 'gt' && comparable) return comparableLeft > comparableRight;
1258
+ if (operator === 'gte' && comparable) return comparableLeft >= comparableRight;
1259
+ if (operator === 'lt' && comparable) return comparableLeft < comparableRight;
1260
+ if (operator === 'lte' && comparable) return comparableLeft <= comparableRight;
1261
+ return comparable ? comparableLeft === comparableRight : leftText === rightText;
1262
+ }
1263
+
1264
+ _clearLocalFilters() {
1265
+ Array.from(this._element.tBodies || []).flatMap((body) => Array.from(body.rows || [])).forEach((row) => {
1266
+ row.removeAttribute(FILTER_HIDDEN_ATTRIBUTE);
1267
+ row.hidden = row.getAttribute('data-vg-table-expand-hidden') === 'true'
1268
+ || row.getAttribute('data-vg-table-page-row') === 'hidden';
1269
+ });
1270
+ }
1271
+
1272
+ _handlePaginationChange(state) {
1273
+ if (this._applyingUrlState) return;
1274
+ this._syncUrlState();
1275
+ if (!this._isRemote) {
1276
+ this._states?.syncLocal(this._hasActiveQuery());
1277
+ return;
1278
+ }
1279
+ this._remote?.load({page: state.page, perPage: state.perPage, source: state.source});
1280
+ }
1281
+
1282
+ _handleLocalSortChange() {
1283
+ this._refreshPagination();
1284
+ this._syncUrlState();
1285
+ }
1286
+
1287
+ _hasActiveQuery() {
1288
+ return Boolean(this._searchState?.value || Object.keys(this._filtersState?.filters || {}).length);
1289
+ }
1290
+
1291
+ _collectUrlState() {
1292
+ const pagination = this._pagination?.getState() || {};
1293
+ return {
1294
+ page: pagination.page || 1,
1295
+ perPage: pagination.perPage || this._params.pagination.per,
1296
+ sort: this._sorting?.getSortState?.() || this._sorting?.getSort() || null,
1297
+ search: this._searchState?.value || '',
1298
+ filters: Object.assign({}, this._filtersState?.params || {}),
1299
+ };
1300
+ }
1301
+
1302
+ _syncUrlState() {
1303
+ if (this._applyingUrlState || !this._urlState) return null;
1304
+ return this._urlState.write(this._collectUrlState());
1305
+ }
1306
+
1307
+ _applyUrlState(state = {}, options = {}) {
1308
+ if (!state || typeof state !== 'object') return null;
1309
+ const clearMissing = options.clearMissing === true;
1310
+ const present = state.present || {};
1311
+ const previousFilterKeys = new Set(this._filterParamKeys);
1312
+ const previousSearchKey = this._searchParamKey;
1313
+ this._applyingUrlState = true;
1314
+ try {
1315
+ if (this._filters && (clearMissing || present.filters)) {
1316
+ this._filters.setValues(state.filters || {}, {emit: false});
1317
+ this._filtersState = this._filters.getState();
1318
+ this._filterParamKeys = new Set(Object.keys(this._filtersState.params || {}));
1319
+ }
1320
+ if (this._search && (clearMissing || present.search)) {
1321
+ this._search.setValue(state.search || '', {emit: false});
1322
+ this._searchState = this._search.getState();
1323
+ this._searchParamKey = this._searchState.param;
1324
+ }
1325
+ if (this._sorting && (clearMissing || present.sort)) {
1326
+ if (state.sort?.sorts?.length) this._sorting.setSorts(state.sort.sorts, false);
1327
+ else if (state.sort?.field) this._sorting.setSort(state.sort.field, state.sort.direction, false);
1328
+ else this._sorting.clearSort(false);
1329
+ }
1330
+ if (this._pagination) {
1331
+ const current = this._pagination.getState();
1332
+ const perPage = present.perPage ? state.perPage : (clearMissing ? this._params.pagination.per : current.perPage);
1333
+ const page = present.page ? state.page : (clearMissing ? this._params.pagination.page : current.page);
1334
+ if (this._isRemote) {
1335
+ this._pagination.setMeta({
1336
+ page: page || 1,
1337
+ per_page: perPage || current.perPage,
1338
+ total: current.totalRows,
1339
+ pages: Math.max(1, page || 1),
1340
+ });
1341
+ } else {
1342
+ if (perPage) this._pagination.setPerPage(perPage, false, 'urlstate');
1343
+ if (page) this._pagination.setPage(page, false, 'urlstate');
1344
+ }
1345
+ }
1346
+ if (!this._isRemote) this._applyLocalFilters(this._filtersState, false);
1347
+ if (this._remote) {
1348
+ const patch = {};
1349
+ previousFilterKeys.forEach((key) => { patch[key] = ''; });
1350
+ if (previousSearchKey) patch[previousSearchKey] = '';
1351
+ Object.assign(patch, this._filtersState.params || {});
1352
+ if (this._searchState.value) patch[this._searchState.param] = this._searchState.value;
1353
+ this._remote.setParams(patch);
1354
+ }
1355
+ } finally {
1356
+ this._applyingUrlState = false;
1357
+ }
1358
+ if (this._remote && options.source !== 'init') {
1359
+ const pagination = this._pagination?.getState() || {};
1360
+ this._remote.load({page: pagination.page, perPage: pagination.perPage, source: options.source || 'urlstate'});
1361
+ }
1362
+ return this._collectUrlState();
1363
+ }
1364
+
1365
+ _resetQueryControls() {
1366
+ this._applyingUrlState = true;
1367
+ try {
1368
+ this._filters?.reset({emit: false});
1369
+ this._search?.reset({emit: false});
1370
+ this._filtersState = this._filters?.getState() || {filters: {}, params: {}, fields: [], meta: {count: 0}};
1371
+ this._searchState = this._search?.getState() || {value: '', param: this._searchParamKey || 'q'};
1372
+ const patch = {};
1373
+ this._filterParamKeys.forEach((key) => { patch[key] = ''; });
1374
+ if (this._searchParamKey) patch[this._searchParamKey] = '';
1375
+ this._filterParamKeys = new Set();
1376
+ this._remote?.setParams(patch);
1377
+ if (this._pagination?.getState().page > 1) this._pagination.setPage(1, false, 'state-reset');
1378
+ if (!this._isRemote) this._applyLocalFilters(this._filtersState, true);
1379
+ } finally {
1380
+ this._applyingUrlState = false;
1381
+ }
1382
+ this._syncUrlState();
1383
+ if (this._remote) this._remote.load({page: 1, source: 'state-reset'});
1384
+ }
1385
+
1386
+ _handleRemoteSortChange() {
1387
+ if (!this._remote) return;
1388
+ this._syncUrlState();
1389
+ const state = this._pagination?.getState();
1390
+ if (state && state.page !== 1) {
1391
+ this._pagination.setPage(1, false, 'sort');
1392
+ return;
1393
+ }
1394
+ this._remote.reload({page: 1, source: 'sort'});
1395
+ }
1396
+
1397
+ _afterRemoteRender(detail = {}) {
1398
+ if (detail.renderedCount > 0) this._states?.clear();
1399
+ this._columns?.refresh?.();
1400
+ this._sorting?.refresh?.();
1401
+ this._expandable?.refresh?.();
1402
+ this._rowReorder?.refresh?.();
1403
+ this._stickyHeader?.refresh?.();
1404
+ this._fixedColumns?.refresh?.();
1405
+ }
1406
+
1407
+ /**
1408
+ * Очистка ресурсов
1409
+ */
1410
+ dispose() {
1411
+ if (this._remote) this._remote.dispose();
1412
+ if (this._skeleton) this._skeleton.dispose();
1413
+ if (this._urlState) this._urlState.dispose();
1414
+ if (this._states) this._states.dispose();
1415
+ if (this._search) this._search.dispose();
1416
+ if (this._filters) this._filters.dispose();
1417
+ this._clearLocalFilters();
1418
+ if (this._fixedColumns) this._fixedColumns.dispose();
1419
+ if (this._sorting) this._sorting.dispose();
1420
+ if (this._columns) this._columns.dispose();
1421
+ if (this._stickyHeader) this._stickyHeader.dispose();
1422
+ if (this._panning) this._panning.dispose();
1423
+ if (this._selection) this._selection.dispose();
1424
+ if (this._rowReorder) this._rowReorder.dispose();
1425
+ if (this._expandable) this._expandable.dispose();
1426
+ this._element.removeEventListener('sortchange.vg.table', this._boundPaginationRefresh);
1427
+ this._element.removeEventListener('sortchange.vg.table', this._boundLocalSortChange);
1428
+ this._element.removeEventListener('sortchange.vg.table', this._boundRemoteSortChange);
1429
+ if (this._pagination) this._pagination.dispose();
1430
+ this._element.classList.remove('vg-table-complex');
1431
+ this._element.removeAttribute('data-vg-table-complex');
1432
+ this._removeGeneratedTableContainer();
1433
+ this._removeGeneratedWrapper();
1434
+ super.dispose();
1435
+ }
1436
+ }
1437
+
1438
+ EventHandler.on(document, 'DOMContentLoaded', () => {
1439
+ Selectors.findAll(SELECTOR_DATA_TOGGLE).forEach((el) => {
1440
+ VGTable.getOrCreateInstance(el).init();
1441
+ })
1442
+ });
1443
+
1444
+ export default VGTable;