vgapp 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +66 -7
  2. package/README.md +2 -1
  3. package/agents.md +2 -2
  4. package/app/modules/base-module.js +87 -10
  5. package/app/modules/vgalert/js/vgalert.js +133 -202
  6. package/app/modules/vgalert/readme.md +105 -46
  7. package/app/modules/vgalert/scss/vgalert.scss +18 -0
  8. package/app/modules/vgdynamictable/index.js +5 -0
  9. package/app/modules/vgdynamictable/js/editable.js +438 -0
  10. package/app/modules/vgdynamictable/js/expandable.js +248 -0
  11. package/app/modules/vgdynamictable/js/filters.js +450 -0
  12. package/app/modules/vgdynamictable/js/fixed.js +566 -0
  13. package/app/modules/vgdynamictable/js/options.js +646 -0
  14. package/app/modules/vgdynamictable/js/pagination.js +623 -0
  15. package/app/modules/vgdynamictable/js/search.js +82 -0
  16. package/app/modules/vgdynamictable/js/skeleton.js +136 -0
  17. package/app/modules/vgdynamictable/js/sortable.js +442 -0
  18. package/app/modules/vgdynamictable/js/summary-footer.js +284 -0
  19. package/app/modules/vgdynamictable/js/table-remote.js +821 -0
  20. package/app/modules/vgdynamictable/js/table-state.js +243 -0
  21. package/app/modules/vgdynamictable/js/table-url-state.js +444 -0
  22. package/app/modules/vgdynamictable/js/utils/common.js +48 -0
  23. package/app/modules/vgdynamictable/js/vgdynamictable.js +3829 -0
  24. package/app/modules/vgdynamictable/js/viewport.js +322 -0
  25. package/app/modules/vgdynamictable/readme.md +251 -0
  26. package/app/modules/vgdynamictable/scss/_actions.scss +193 -0
  27. package/app/modules/vgdynamictable/scss/_pagination.scss +183 -0
  28. package/app/modules/vgdynamictable/scss/_skeleton.scss +60 -0
  29. package/app/modules/vgdynamictable/scss/_sortable.scss +63 -0
  30. package/app/modules/vgdynamictable/scss/_table.scss +157 -0
  31. package/app/modules/vgdynamictable/scss/_variables.scss +130 -0
  32. package/app/modules/vgdynamictable/scss/vgdynamictable.scss +267 -0
  33. package/app/modules/vgrangeslider/index.js +3 -0
  34. package/app/modules/vgrangeslider/js/skins.js +222 -0
  35. package/app/modules/vgrangeslider/js/vgrangeslider.js +704 -0
  36. package/app/modules/vgrangeslider/readme.md +523 -0
  37. package/app/modules/vgrangeslider/scss/_variables.scss +53 -0
  38. package/app/modules/vgrangeslider/scss/vgrangeslider.scss +240 -0
  39. package/app/modules/vgtoast/js/vgtoast.js +111 -111
  40. package/app/modules/vgtooltip/index.js +3 -0
  41. package/app/modules/vgtooltip/js/vgtooltip.js +493 -0
  42. package/app/modules/vgtooltip/readme.md +181 -0
  43. package/app/modules/vgtooltip/scss/_variables.scss +15 -0
  44. package/app/modules/vgtooltip/scss/vgtooltip.scss +64 -0
  45. package/app/utils/js/components/ajax.js +116 -7
  46. package/app/utils/js/components/placement.js +112 -41
  47. package/build/vgapp.css +1 -1
  48. package/build/vgapp.css.map +1 -1
  49. package/index.js +20 -17
  50. package/index.scss +9 -0
  51. package/package.json +1 -1
@@ -0,0 +1,3829 @@
1
+ import BaseModule from "../../base-module";
2
+ import fixedColumnsMethods from "./fixed";
3
+ import viewportMethods from "./viewport";
4
+ import summaryFooterMethods from "./summary-footer";
5
+ import skeletonMethods from "./skeleton";
6
+ import tableRemoteMethods from "./table-remote";
7
+ import tableUrlStateMethods from "./table-url-state";
8
+ import tableStateMethods from "./table-state";
9
+ import DEFAULT_OPTIONS from "./options";
10
+ import Pagination from "./pagination";
11
+ import Sortable from "./sortable";
12
+ import Expandable from "./expandable";
13
+ import Search from "./search";
14
+ import Filters from "./filters";
15
+ import Selectors from "../../../utils/js/dom/selectors";
16
+ import EventHandler from "../../../utils/js/dom/event";
17
+ import {mergeDeepObject} from "../../../utils/js/functions";
18
+
19
+ /**
20
+ * Константы
21
+ */
22
+ const NAME = 'dynamicTable';
23
+ const NAME_KEY = 'vg.' + NAME;
24
+
25
+ const MAIN_SELECTOR_CLASS = 'vg-dynamic-table';
26
+ const SELECTOR_DATA_TOGGLE = '[data-vg-table]';
27
+
28
+ /**
29
+ * Карта колбеков
30
+ */
31
+ const ACTION_CALLBACK_MAP = {
32
+ init: 'onInit',
33
+ beforeload: 'onBeforeLoad',
34
+ dataloaded: 'onDataLoaded',
35
+ error: 'onError',
36
+ requestsuccess: 'onRequestSuccess',
37
+ requesterror: 'onRequestError',
38
+ staleresponse: 'onStaleResponse',
39
+ urlstateread: 'onUrlStateRead',
40
+ urlstatewrite: 'onUrlStateWrite',
41
+ urlstateerror: 'onUrlStateError',
42
+ emptyresult: 'onEmptyResult',
43
+ afterrender: 'onAfterRender',
44
+ statesync: 'onStateSync',
45
+ sortchange: 'onSortChange',
46
+ pagechange: 'onPageChange',
47
+ perpagechange: 'onPerPageChange',
48
+ search: 'onSearch',
49
+ filterschange: 'onFiltersChange',
50
+ reset: 'onReset',
51
+ export: 'onExport',
52
+ viewchange: 'onViewChange',
53
+ rowupdate: 'onRowUpdate',
54
+ rowupdateerror: 'onRowUpdateError',
55
+ bulkaction: 'onBulkAction',
56
+ rowdelete: 'onRowDelete',
57
+ rowcheck: 'onRowCheck',
58
+ checkall: 'onCheckAll',
59
+ selectionchange: 'onSelectionChange',
60
+ rowexpand: 'onRowExpand',
61
+ rowcollapse: 'onRowCollapse',
62
+ rowtoggle: 'onRowToggle',
63
+ rowreorder: 'onRowReorder',
64
+ rowreordererror: 'onRowReorderError',
65
+ columnreorder: 'onColumnReorder',
66
+ columnresize: 'onColumnResize',
67
+ columnfixed: 'onColumnFixed',
68
+ };
69
+
70
+ class VGDynamicTable extends BaseModule {
71
+ constructor(element, params = {}) {
72
+ super(element, params);
73
+ this._params = this._getParams(element, mergeDeepObject(DEFAULT_OPTIONS, params));
74
+ this._parent = null;
75
+ this._tableViewport = null;
76
+ this._pagination = null;
77
+ this._rows = [];
78
+ this._originalRows = [];
79
+ this._requestToken = 0;
80
+ this._isRemote = false;
81
+ this._fields = [];
82
+ this._isInitialized = false;
83
+ this._sortable = null;
84
+ this._search = null;
85
+ this._filters = null;
86
+ this._sortState = { field: '', dir: 'asc', columnIndex: -1, sorts: [] };
87
+ this._pageState = { page: 1, perPage: 10 };
88
+ this._remoteParams = {};
89
+ this._activeFilters = {};
90
+ this._lastRemoteMeta = {};
91
+ this._liveRegion = null;
92
+ this._summaryNode = null;
93
+ this._panHintNode = null;
94
+ this._cloneStickyState = null;
95
+ this._boundPopState = this._handlePopState.bind(this);
96
+ this._panState = null;
97
+ this._boundPanHintResize = this._updatePanHintVisibility.bind(this);
98
+ this._boundPanHintDismiss = this._handlePanHintDismiss.bind(this);
99
+ this._boundCloneStickyResize = this._refreshStickyAndFixedLayout.bind(this);
100
+ this._boundFixedColumnsScroll = this._syncFixedColumnsScroll.bind(this);
101
+ this._fixedColumnsScrollBound = false;
102
+ this._fixedColumnsCellsCache = [];
103
+ this._fixedColumnsSyncFrame = 0;
104
+ this._fixedColumnsLastScrollLeft = 0;
105
+ this._layoutResizeObserver = null;
106
+ this._layoutRefreshFrame = 0;
107
+ this._layoutRefreshReasons = [];
108
+ this._layoutRefreshScheduled = false;
109
+ this._requestAbortController = null;
110
+ this._activeRemoteRequestId = '';
111
+ this._remotePageCache = new Map();
112
+ this._fixedColumnsSuppressed = false;
113
+ this._searchInputCacheSelector = '';
114
+ this._searchInputCacheNode = null;
115
+ this._filtersFormCacheSelector = '';
116
+ this._filtersFormCacheNode = null;
117
+ this._filterParamKeysCache = null;
118
+ this._columnReorderState = {
119
+ enabled: false,
120
+ fromIndex: -1,
121
+ toIndex: -1,
122
+ isDragging: false,
123
+ };
124
+ this._columnResizeState = {
125
+ enabled: false,
126
+ bound: false,
127
+ active: false,
128
+ index: -1,
129
+ startX: 0,
130
+ startWidth: 0,
131
+ nextWidth: 0,
132
+ frame: 0,
133
+ };
134
+ this._virtualState = {
135
+ enabled: false,
136
+ bound: false,
137
+ lastPage: -1,
138
+ lastPerPage: -1,
139
+ start: 0,
140
+ end: 0,
141
+ page: -1,
142
+ perPage: -1,
143
+ frame: 0,
144
+ lastScrollTop: -1,
145
+ rowHeight: 44,
146
+ };
147
+ this._virtualSpacers = {
148
+ top: null,
149
+ bottom: null,
150
+ };
151
+ this._boundVirtualScroll = this._handleVirtualScroll.bind(this);
152
+ this._expandable = null;
153
+ }
154
+
155
+ static get NAME() { return NAME; }
156
+
157
+ static get NAME_KEY() { return NAME_KEY; }
158
+
159
+ static initAll(params = {}) {
160
+ Array.from(Selectors.findAll(SELECTOR_DATA_TOGGLE)).forEach((element) => {
161
+ const instance = this.getInstance(element) || new this(element, params);
162
+ instance.init();
163
+ });
164
+ }
165
+
166
+ _emitAction(action, payload = {}) {
167
+ const normalizedAction = String(action || '').trim();
168
+ if (!normalizedAction) {
169
+ return;
170
+ }
171
+
172
+ const detail = Object.assign({
173
+ action: normalizedAction,
174
+ table: this._element,
175
+ isRemote: this._isRemote,
176
+ }, payload);
177
+
178
+ const callbacks = this._params.callbacks || {};
179
+ const normalizedActionKey = normalizedAction.toLowerCase();
180
+ const callbackName = ACTION_CALLBACK_MAP[normalizedActionKey] || '';
181
+ const callback = callbackName ? callbacks[callbackName] : null;
182
+ if (typeof callback === 'function') {
183
+ callback(detail, this);
184
+ }
185
+
186
+ const events = this._params.events || {};
187
+ const eventsEnabled = events.enable === undefined ? true : Boolean(events.enable);
188
+ if (!eventsEnabled || typeof window === 'undefined' || typeof window.CustomEvent !== 'function') {
189
+ return;
190
+ }
191
+
192
+ const prefix = String(events.prefix || 'vgdt').trim() || 'vgdt';
193
+ const eventName = `${prefix}:${normalizedActionKey}`;
194
+ const bubbles = events.bubbles === undefined ? true : Boolean(events.bubbles);
195
+ this._element.dispatchEvent(new CustomEvent(eventName, {
196
+ detail,
197
+ bubbles,
198
+ }));
199
+ }
200
+
201
+ updateRemoteParams(nextParams = {}, options = {}) {
202
+ if (!this._isRemote) {
203
+ return;
204
+ }
205
+
206
+ const shouldReplace = options && Object.prototype.hasOwnProperty.call(options, 'replace')
207
+ ? Boolean(options.replace)
208
+ : false;
209
+ const shouldReload = options && Object.prototype.hasOwnProperty.call(options, 'reload')
210
+ ? Boolean(options.reload)
211
+ : true;
212
+ const shouldResetPage = options && Object.prototype.hasOwnProperty.call(options, 'resetPage')
213
+ ? Boolean(options.resetPage)
214
+ : true;
215
+ const trigger = options && typeof options.trigger === 'string'
216
+ ? String(options.trigger).toLowerCase().trim()
217
+ : '';
218
+ const filtersState = options && options.filtersState && typeof options.filtersState === 'object'
219
+ ? this._normalizeFiltersEventState(options.filtersState)
220
+ : null;
221
+
222
+ const source = nextParams && typeof nextParams === 'object' ? nextParams : {};
223
+ const current = shouldReplace ? {} : Object.assign({}, this._remoteParams);
224
+ Object.keys(source).forEach((key) => {
225
+ const value = source[key];
226
+ if (Array.isArray(value)) {
227
+ const normalized = value
228
+ .map((item) => String(item || '').trim())
229
+ .filter((item) => item !== '');
230
+ if (!normalized.length) {
231
+ delete current[key];
232
+ return;
233
+ }
234
+ current[key] = normalized;
235
+ return;
236
+ }
237
+ if (value === undefined || value === null || value === '') {
238
+ delete current[key];
239
+ return;
240
+ }
241
+ current[key] = value;
242
+ });
243
+ this._remoteParams = current;
244
+
245
+ if (!shouldReload) {
246
+ this._syncUrlState();
247
+ return;
248
+ }
249
+
250
+ const perPage = this._pageState.perPage || this._normalizePositiveInt(this._params.pagination.perPage, 10);
251
+ const page = shouldResetPage ? 1 : (this._pageState.page || 1);
252
+ this._pageState = { page, perPage };
253
+ this._loadRemotePage(page, perPage, null, {
254
+ trigger,
255
+ filtersState,
256
+ });
257
+ }
258
+
259
+ setLocale(locale) {
260
+ const normalized = String(locale || '').toLowerCase().trim() || 'ru';
261
+ this._params.locale = normalized;
262
+ this._writePersistentState('locale', normalized);
263
+ return normalized;
264
+ }
265
+
266
+ setFiltersApplyMode(mode) {
267
+ const normalized = this._normalizeFiltersApplyMode(mode);
268
+ if (!this._params.filters || typeof this._params.filters !== 'object') {
269
+ this._params.filters = {};
270
+ }
271
+ this._params.filters.apply = normalized;
272
+ this._writePersistentState('filtersApplyMode', normalized);
273
+ if (this._isInitialized && this._isRemote) {
274
+ this._initFilters();
275
+ }
276
+ return normalized;
277
+ }
278
+
279
+ _getViewsStorageKey() {
280
+ const explicitAttr = String(this._element.getAttribute('data-views-storage-key') || '').trim();
281
+ if (explicitAttr) {
282
+ return explicitAttr;
283
+ }
284
+ const viewsOptions = this._params.views && typeof this._params.views === 'object'
285
+ ? this._params.views
286
+ : {};
287
+ const nestedStorage = viewsOptions.storage && typeof viewsOptions.storage === 'object'
288
+ ? viewsOptions.storage
289
+ : {};
290
+ const nestedOption = String(nestedStorage.key || viewsoptions.storageKey || '').trim();
291
+ if (nestedOption) {
292
+ return nestedOption;
293
+ }
294
+ return `${this._getPerPageStorageKey()}:views`;
295
+ }
296
+
297
+ _readViewsStorage() {
298
+ try {
299
+ const raw = String(window.localStorage.getItem(this._getViewsStorageKey()) || '').trim();
300
+ if (!raw) {
301
+ return {};
302
+ }
303
+ const parsed = JSON.parse(raw);
304
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
305
+ } catch (error) {
306
+ return {};
307
+ }
308
+ }
309
+
310
+ _writeViewsStorage(data) {
311
+ try {
312
+ window.localStorage.setItem(this._getViewsStorageKey(), JSON.stringify(data || {}));
313
+ } catch (error) {
314
+ // Ignore storage errors.
315
+ }
316
+ }
317
+
318
+ _getHiddenFields() {
319
+ return this._getHeaderCells()
320
+ .filter((header) => String(header.getAttribute('data-hidden') || '') === '1')
321
+ .map((header) => String(header.getAttribute('data-field') || '').trim())
322
+ .filter(Boolean);
323
+ }
324
+
325
+ _setFieldHidden(field, hidden) {
326
+ const targetField = String(field || '').trim();
327
+ if (!targetField) {
328
+ return;
329
+ }
330
+ const headers = this._getHeaderCells();
331
+ const index = headers.findIndex((header) => String(header.getAttribute('data-field') || '').trim() === targetField);
332
+ if (index < 0) {
333
+ return;
334
+ }
335
+ const sections = [];
336
+ if (this._element.tHead) {
337
+ sections.push(this._element.tHead);
338
+ }
339
+ if (this._element.tBodies && this._element.tBodies.length) {
340
+ sections.push(...Array.from(this._element.tBodies));
341
+ }
342
+ if (this._element.tFoot) {
343
+ sections.push(this._element.tFoot);
344
+ }
345
+ sections.forEach((section) => {
346
+ Array.from(section.rows || []).forEach((row) => {
347
+ const cell = row.cells && row.cells[index] ? row.cells[index] : null;
348
+ if (!cell) {
349
+ return;
350
+ }
351
+ cell.style.display = hidden ? 'none' : '';
352
+ if (hidden) {
353
+ cell.setAttribute('data-hidden', '1');
354
+ } else {
355
+ cell.removeAttribute('data-hidden');
356
+ }
357
+ });
358
+ });
359
+ const header = headers[index];
360
+ if (header) {
361
+ if (hidden) {
362
+ header.setAttribute('data-hidden', '1');
363
+ } else {
364
+ header.removeAttribute('data-hidden');
365
+ }
366
+ }
367
+ }
368
+
369
+ getCurrentViewState() {
370
+ return {
371
+ filters: Object.assign({}, this._remoteParams || {}),
372
+ sorts: this._getNormalizedSorts(this._sortState.sorts || []),
373
+ perPage: this._pageState.perPage || this._getInitialPerPage(),
374
+ columnOrder: this._getCurrentColumnOrder(),
375
+ hiddenFields: this._getHiddenFields(),
376
+ };
377
+ }
378
+
379
+ listViews() {
380
+ return Object.keys(this._readViewsStorage()).sort();
381
+ }
382
+
383
+ saveView(name) {
384
+ const cleanName = String(name || '').trim();
385
+ if (!cleanName) {
386
+ return false;
387
+ }
388
+ const storage = this._readViewsStorage();
389
+ storage[cleanName] = this.getCurrentViewState();
390
+ this._writeViewsStorage(storage);
391
+ this._emitAction('viewchange', {
392
+ mode: 'save',
393
+ name: cleanName,
394
+ state: storage[cleanName],
395
+ });
396
+ return true;
397
+ }
398
+
399
+ applyView(name) {
400
+ const cleanName = String(name || '').trim();
401
+ if (!cleanName) {
402
+ return false;
403
+ }
404
+ const storage = this._readViewsStorage();
405
+ const state = storage[cleanName];
406
+ if (!state || typeof state !== 'object') {
407
+ return false;
408
+ }
409
+
410
+ const columnOrder = Array.isArray(state.columnOrder) ? state.columnOrder.filter(Boolean) : [];
411
+ columnOrder.forEach((field, targetIndex) => {
412
+ const currentIndex = this._findHeaderIndexByField(field);
413
+ if (currentIndex < 0 || currentIndex === targetIndex) {
414
+ return;
415
+ }
416
+ this._moveColumn(currentIndex, targetIndex);
417
+ });
418
+
419
+ const hiddenFields = Array.isArray(state.hiddenFields) ? state.hiddenFields : [];
420
+ const hiddenSet = new Set(hiddenFields.map((field) => String(field || '').trim()).filter(Boolean));
421
+ this._getCurrentColumnOrder().forEach((field) => {
422
+ this._setFieldHidden(field, hiddenSet.has(field));
423
+ });
424
+
425
+ const sorts = this._getNormalizedSorts(state.sorts || []);
426
+ if (this._sortable) {
427
+ this._sortable.setState({ sorts });
428
+ }
429
+ const firstSort = sorts[0] || null;
430
+ this._sortState = Object.assign({}, this._sortState, {
431
+ field: firstSort ? firstSort.field : '',
432
+ dir: firstSort ? firstSort.dir : 'asc',
433
+ sorts,
434
+ });
435
+
436
+ const nextPerPage = this._clampPerPage(state.perPage, this._getInitialPerPage());
437
+ if (this._pagination) {
438
+ this._pagination.setMeta({
439
+ page: 1,
440
+ perPage: nextPerPage,
441
+ totalPages: this._isRemote ? this._normalizePositiveInt(this._lastRemoteMeta.pages, 1) : this._getTotalPages(nextPerPage),
442
+ });
443
+ }
444
+ this._pageState = { page: 1, perPage: nextPerPage };
445
+
446
+ if (this._isRemote) {
447
+ this._remoteParams = state.filters && typeof state.filters === 'object'
448
+ ? Object.assign({}, state.filters)
449
+ : {};
450
+ if (this._filters && typeof this._filters.setValues === 'function') {
451
+ this._filters.setValues(this._getInitialFilterValues(), { emit: false });
452
+ }
453
+ const filtersRequestMeta = this._getFiltersRequestMetaIfActive();
454
+ this._loadRemotePage(1, nextPerPage, null, filtersRequestMeta);
455
+ } else {
456
+ this._renderPageRows(1, nextPerPage);
457
+ }
458
+
459
+ this._afterColumnReorder();
460
+ this._emitAction('viewchange', {
461
+ mode: 'apply',
462
+ name: cleanName,
463
+ state,
464
+ });
465
+ return true;
466
+ }
467
+
468
+ deleteView(name) {
469
+ const cleanName = String(name || '').trim();
470
+ if (!cleanName) {
471
+ return false;
472
+ }
473
+ const storage = this._readViewsStorage();
474
+ if (!Object.prototype.hasOwnProperty.call(storage, cleanName)) {
475
+ return false;
476
+ }
477
+ delete storage[cleanName];
478
+ this._writeViewsStorage(storage);
479
+ this._emitAction('viewchange', {
480
+ mode: 'delete',
481
+ name: cleanName,
482
+ });
483
+ return true;
484
+ }
485
+
486
+ init() {
487
+ if (this._isInitialized) {
488
+ return;
489
+ }
490
+
491
+ const directParent = this._element.parentElement;
492
+ const nearestWrapper = this._element.closest(`.${MAIN_SELECTOR_CLASS}`);
493
+ const isWrapped = nearestWrapper && nearestWrapper.contains(this._element);
494
+ if (!isWrapped) {
495
+ const wrapper = document.createElement('div');
496
+ wrapper.className = MAIN_SELECTOR_CLASS;
497
+ this._moveTableClassesToWrapper(wrapper);
498
+ this._element.parentNode.insertBefore(wrapper, this._element);
499
+ wrapper.appendChild(this._element);
500
+
501
+ this._parent = wrapper;
502
+ } else {
503
+ this._parent = nearestWrapper;
504
+ if (this._parent) {
505
+ this._parent.classList.add(MAIN_SELECTOR_CLASS);
506
+ this._moveTableClassesToWrapper(this._parent);
507
+ }
508
+ }
509
+
510
+ if (!this._parent) return;
511
+
512
+ this._ensureTableViewport(directParent);
513
+ this._bindViewportPan();
514
+ this._restorePersistentState();
515
+ this._ensureLiveRegion();
516
+ this._ensureSummaryNode();
517
+ this._ensurePanHintNode();
518
+ this._applyFooterVisibility();
519
+
520
+ const sortableEnabled = Boolean(this._params.sortable && this._params.sortable.enable);
521
+ const paginationEnabled = this._isPaginationEnabled();
522
+ const cloneStickyEnabled = this._isCloneStickyHeaderEnabled();
523
+ const stickyHeaderEnabled = cloneStickyEnabled ? false : this._isStickyHeaderEnabled();
524
+ this._element.classList.toggle('table-sortable', sortableEnabled);
525
+ this._element.classList.toggle('table-pagination', paginationEnabled);
526
+ this._element.classList.toggle('table-sticky-head', stickyHeaderEnabled);
527
+ this._element.classList.toggle('table-sticky-clone', cloneStickyEnabled);
528
+ this._parent.classList.toggle('table-sortable', sortableEnabled);
529
+ this._parent.classList.toggle('table-pagination', paginationEnabled);
530
+ this._parent.classList.toggle('table-sticky-head', stickyHeaderEnabled);
531
+ this._parent.classList.toggle('table-sticky-clone', cloneStickyEnabled);
532
+ this._applyStickyHeader(stickyHeaderEnabled);
533
+ this._applyCloneStickyHeader(cloneStickyEnabled);
534
+
535
+ const route = this._getRoute();
536
+ this._isRemote = Boolean(route);
537
+ this._fields = this._getFields();
538
+ this._sortState = this._getInitialSortState();
539
+ this._remoteParams = this._getInitialRemoteParams();
540
+ this._isInitialized = true;
541
+
542
+ if (!this._isRemote) {
543
+ this._rows = this._getTableRows();
544
+ this._originalRows = this._rows.slice();
545
+ }
546
+
547
+ this._initSorting();
548
+ this._initColumnReorder();
549
+ this._initColumnResize();
550
+ this._initExpandable();
551
+ this._initSearch();
552
+ this._initFilters();
553
+ this._bindTableStateActions();
554
+ this._bindPopState();
555
+ this._refreshStickyAndFixedLayout();
556
+
557
+ if (paginationEnabled) {
558
+ this.setPagination();
559
+ this._emitAction('init', {
560
+ page: this._pageState.page,
561
+ perPage: this._pageState.perPage,
562
+ });
563
+ return;
564
+ }
565
+
566
+ if (this._isRemote) {
567
+ const page = this._getInitialPage();
568
+ const perPage = this._getInitialPerPage();
569
+ this._pageState = { page, perPage };
570
+ const filtersRequestMeta = this._getFiltersRequestMetaIfActive();
571
+ this._loadRemotePage(page, perPage, null, filtersRequestMeta);
572
+ } else {
573
+ this._renderFooterFromCurrentState();
574
+ this._refreshExpandable();
575
+ }
576
+
577
+ this._emitAction('init', {
578
+ page: this._pageState.page,
579
+ perPage: this._pageState.perPage,
580
+ });
581
+ }
582
+
583
+ setPagination() {
584
+ if (this._isRemote) {
585
+ this._setRemotePagination();
586
+ return;
587
+ }
588
+
589
+ this._rows = this._getTableRows();
590
+ if (!this._originalRows.length) {
591
+ this._originalRows = this._rows.slice();
592
+ }
593
+
594
+ const page = this._getInitialPage();
595
+ const perPage = this._getInitialPerPage();
596
+ const totalPages = this._getTotalPages(perPage);
597
+ const initialPage = Math.min(page, totalPages);
598
+ this._pageState = { page: initialPage, perPage };
599
+
600
+ const userOnChange = this._params.pagination.onChange;
601
+ const userOnPerPageChange = this._params.pagination.onPerPageChange;
602
+ const paginationI18n = this._getI18nSection('pagination');
603
+
604
+ const paginationOptions = Object.assign({}, this._params.pagination, paginationI18n, {
605
+ page: initialPage,
606
+ perPage,
607
+ maxPerPage: this._getMaxPerPage(),
608
+ totalPages,
609
+ onChange: (payload) => {
610
+ const nextTotalPages = this._getTotalPages(payload.perPage);
611
+ const nextPage = Math.min(payload.page, nextTotalPages);
612
+ this._pageState = { page: nextPage, perPage: payload.perPage };
613
+ this._storePage(nextPage);
614
+ this._storePerPage(payload.perPage);
615
+
616
+ this._renderPageRows(nextPage, payload.perPage);
617
+ this._pagination.setMeta({
618
+ page: nextPage,
619
+ perPage: payload.perPage,
620
+ totalPages: nextTotalPages,
621
+ });
622
+ if (payload.source !== 'ellipsis') {
623
+ this._scrollToPaginationTop();
624
+ }
625
+ this._syncUrlState();
626
+
627
+ if (typeof userOnChange === 'function') {
628
+ userOnChange(Object.assign({}, payload, {
629
+ page: nextPage,
630
+ totalPages: nextTotalPages,
631
+ }));
632
+ }
633
+
634
+ this._emitAction('pagechange', {
635
+ page: nextPage,
636
+ perPage: payload.perPage,
637
+ totalPages: nextTotalPages,
638
+ source: 'local',
639
+ });
640
+ },
641
+ onPerPageChange: (payload) => {
642
+ if (typeof userOnPerPageChange === 'function') {
643
+ userOnPerPageChange(payload);
644
+ }
645
+ this._emitAction('perpagechange', Object.assign({}, payload, {
646
+ source: 'local',
647
+ }));
648
+ },
649
+ });
650
+
651
+ const pagination = new Pagination(this._parent, paginationOptions);
652
+ pagination.init();
653
+ this._pagination = pagination;
654
+
655
+ this._renderPageRows(initialPage, perPage);
656
+ this._renderFooterFromCurrentState();
657
+ }
658
+
659
+ _setRemotePagination() {
660
+ const page = this._getInitialPage();
661
+ const perPage = this._getInitialPerPage();
662
+ this._pageState = { page, perPage };
663
+
664
+ const userOnChange = this._params.pagination.onChange;
665
+ const userOnPerPageChange = this._params.pagination.onPerPageChange;
666
+ const paginationI18n = this._getI18nSection('pagination');
667
+
668
+ const paginationOptions = Object.assign({}, this._params.pagination, paginationI18n, {
669
+ page,
670
+ perPage,
671
+ maxPerPage: this._getMaxPerPage(),
672
+ totalPages: 1,
673
+ onChange: async (payload) => {
674
+ this._pageState = { page: payload.page, perPage: payload.perPage };
675
+ this._storePage(payload.page);
676
+ this._storePerPage(payload.perPage);
677
+ if (payload.source !== 'ellipsis') {
678
+ this._scrollToPaginationTop();
679
+ }
680
+ await this._loadRemotePage(payload.page, payload.perPage, userOnChange);
681
+ const totalPages = this._normalizePositiveInt(this._lastRemoteMeta.pages, 1);
682
+ this._emitAction('pagechange', {
683
+ page: payload.page,
684
+ perPage: payload.perPage,
685
+ totalPages,
686
+ source: 'remote',
687
+ });
688
+ },
689
+ onPerPageChange: (payload) => {
690
+ if (typeof userOnPerPageChange === 'function') {
691
+ userOnPerPageChange(payload);
692
+ }
693
+ this._emitAction('perpagechange', Object.assign({}, payload, {
694
+ source: 'remote',
695
+ }));
696
+ },
697
+ });
698
+
699
+ const pagination = new Pagination(this._parent, paginationOptions);
700
+ pagination.init();
701
+ this._pagination = pagination;
702
+
703
+ const filtersRequestMeta = this._getFiltersRequestMetaIfActive();
704
+ this._loadRemotePage(page, perPage, null, filtersRequestMeta);
705
+ }
706
+
707
+ _getTableRows() {
708
+ const body = this._element.tBodies && this._element.tBodies[0];
709
+ if (!body) {
710
+ return [];
711
+ }
712
+ return Array.from(body.rows).filter((row) => String(row.getAttribute('data-virtual-spacer') || '') !== '1');
713
+ }
714
+
715
+ _getTotalPages(perPage) {
716
+ if (!this._rows.length) {
717
+ return 1;
718
+ }
719
+ return Math.max(1, Math.ceil(this._rows.length / perPage));
720
+ }
721
+
722
+ _renderPageRows(page, perPage) {
723
+ if (!this._rows.length) {
724
+ return;
725
+ }
726
+
727
+ const isVirtualEnabled = this._isVirtualEnabled();
728
+ const start = (page - 1) * perPage;
729
+ const end = start + perPage;
730
+
731
+ this._rows.forEach((row, index) => {
732
+ row.hidden = index < start || index >= end;
733
+ });
734
+ if (isVirtualEnabled) {
735
+ this._virtualState.lastPage = page;
736
+ this._virtualState.lastPerPage = perPage;
737
+ this._applyVirtualWindow(page, perPage);
738
+ } else {
739
+ this._teardownVirtualWindow();
740
+ }
741
+ this._refreshExpandable();
742
+ this._renderFooterFromCurrentState();
743
+ this._refreshStickyAndFixedLayout();
744
+ }
745
+
746
+ _isVirtualEnabled() {
747
+ const options = this._params.virtual || {};
748
+ const attr = this._element.getAttribute('data-virtual-enable');
749
+ const enabled = attr !== null
750
+ ? this._isTruthy(attr)
751
+ : Boolean(options.enable);
752
+ if (!enabled) {
753
+ return false;
754
+ }
755
+ const thresholdRaw = this._element.getAttribute('data-virtual-threshold');
756
+ const threshold = this._normalizePositiveInt(
757
+ thresholdRaw !== null ? thresholdRaw : options.threshold,
758
+ 500
759
+ );
760
+ return this._rows.length >= threshold;
761
+ }
762
+
763
+ _initVirtualization() {
764
+ if (!this._tableViewport || this._virtualState.bound) {
765
+ return;
766
+ }
767
+ this._tableViewport.addEventListener('scroll', this._boundVirtualScroll, { passive: true });
768
+ this._virtualState.bound = true;
769
+ }
770
+
771
+ _teardownVirtualizationBinding() {
772
+ if (!this._tableViewport || !this._virtualState.bound) {
773
+ return;
774
+ }
775
+ this._tableViewport.removeEventListener('scroll', this._boundVirtualScroll);
776
+ this._virtualState.bound = false;
777
+ }
778
+
779
+ _handleVirtualScroll() {
780
+ if (!this._isVirtualEnabled()) {
781
+ return;
782
+ }
783
+ if (this._virtualState.lastPage <= 0 || this._virtualState.lastPerPage <= 0) {
784
+ return;
785
+ }
786
+ const nextScrollTop = this._tableViewport ? (this._tableViewport.scrollTop || 0) : 0;
787
+ if (nextScrollTop === this._virtualState.lastScrollTop && this._virtualState.frame === 0) {
788
+ return;
789
+ }
790
+ this._virtualState.lastScrollTop = nextScrollTop;
791
+ if (this._virtualState.frame) {
792
+ return;
793
+ }
794
+ if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') {
795
+ this._applyVirtualWindow(this._virtualState.lastPage, this._virtualState.lastPerPage);
796
+ return;
797
+ }
798
+ this._virtualState.frame = window.requestAnimationFrame(() => {
799
+ this._virtualState.frame = 0;
800
+ this._applyVirtualWindow(this._virtualState.lastPage, this._virtualState.lastPerPage);
801
+ });
802
+ }
803
+
804
+ _applyVirtualWindow(page, perPage) {
805
+ if (!this._isVirtualEnabled() || !this._tableViewport) {
806
+ this._teardownVirtualWindow();
807
+ return;
808
+ }
809
+
810
+ this._initVirtualization();
811
+ const body = this._getBody();
812
+ if (!body) {
813
+ this._teardownVirtualWindow();
814
+ return;
815
+ }
816
+ const options = this._params.virtual || {};
817
+ const rowHeight = this._resolveVirtualRowHeight(page, perPage, options);
818
+ const overscanRaw = this._element.getAttribute('data-virtual-overscan');
819
+ const overscan = Math.max(0, this._normalizePositiveInt(overscanRaw !== null ? overscanRaw : options.overscan, 8));
820
+ const visibleCount = Math.max(1, Math.ceil((this._tableViewport.clientHeight || 0) / rowHeight));
821
+ const pageStart = (page - 1) * perPage;
822
+ const pageEnd = Math.min(pageStart + perPage, this._rows.length);
823
+
824
+ const scrolledRows = Math.floor((this._tableViewport.scrollTop || 0) / rowHeight);
825
+ const windowStart = Math.max(pageStart, pageStart + scrolledRows - overscan);
826
+ const windowEnd = Math.min(pageEnd, pageStart + scrolledRows + visibleCount + overscan);
827
+
828
+ const topSpacerHeight = Math.max(0, (windowStart - pageStart) * rowHeight);
829
+ const bottomSpacerHeight = Math.max(0, (pageEnd - windowEnd) * rowHeight);
830
+ if (
831
+ this._virtualState.start === windowStart
832
+ && this._virtualState.end === windowEnd
833
+ && this._virtualState.page === page
834
+ && this._virtualState.perPage === perPage
835
+ ) {
836
+ this._syncVirtualSpacers(body, windowStart, windowEnd, topSpacerHeight, bottomSpacerHeight);
837
+ return;
838
+ }
839
+
840
+ const isSameContext = this._virtualState.page === page && this._virtualState.perPage === perPage;
841
+ const prevStart = this._virtualState.start;
842
+ const prevEnd = this._virtualState.end;
843
+ const updateFrom = isSameContext
844
+ ? Math.max(pageStart, Math.min(prevStart, windowStart))
845
+ : pageStart;
846
+ const updateTo = isSameContext
847
+ ? Math.min(pageEnd, Math.max(prevEnd, windowEnd))
848
+ : pageEnd;
849
+ for (let index = updateFrom; index < updateTo; index += 1) {
850
+ const row = this._rows[index];
851
+ if (!row) {
852
+ continue;
853
+ }
854
+ row.hidden = index < windowStart || index >= windowEnd;
855
+ }
856
+ this._syncVirtualSpacers(body, windowStart, windowEnd, topSpacerHeight, bottomSpacerHeight);
857
+ this._virtualState.start = windowStart;
858
+ this._virtualState.end = windowEnd;
859
+ this._virtualState.page = page;
860
+ this._virtualState.perPage = perPage;
861
+ this._virtualState.rowHeight = rowHeight;
862
+ }
863
+
864
+ _resolveVirtualRowHeight(page, perPage, options = {}) {
865
+ const attrRowHeight = this._element.getAttribute('data-virtual-row-height');
866
+ if (attrRowHeight !== null && String(attrRowHeight).trim() !== '') {
867
+ return Math.max(24, this._normalizePositiveInt(attrRowHeight, 44));
868
+ }
869
+
870
+ const optionRowHeight = this._normalizePositiveInt(options.rowheight, 0);
871
+ const pageStart = Math.max(0, (page - 1) * perPage);
872
+ const pageEnd = Math.min(pageStart + perPage, this._rows.length);
873
+ const measuredHeight = this._measureVirtualRowHeight(pageStart, pageEnd);
874
+ if (measuredHeight > 0) {
875
+ return measuredHeight;
876
+ }
877
+ if (optionRowHeight > 0) {
878
+ return Math.max(24, optionRowHeight);
879
+ }
880
+ const cachedHeight = this._normalizePositiveInt(this._virtualState.rowHeight, 44);
881
+ return Math.max(24, cachedHeight);
882
+ }
883
+
884
+ _measureVirtualRowHeight(pageStart, pageEnd) {
885
+ const samples = [];
886
+ for (let index = pageStart; index < pageEnd; index += 1) {
887
+ const row = this._rows[index];
888
+ if (!row || row.hidden) {
889
+ continue;
890
+ }
891
+ const rect = row.getBoundingClientRect();
892
+ const height = Math.round(rect && rect.height ? rect.height : 0);
893
+ if (height > 0) {
894
+ samples.push(height);
895
+ }
896
+ if (samples.length >= 6) {
897
+ break;
898
+ }
899
+ }
900
+ if (!samples.length) {
901
+ return 0;
902
+ }
903
+ const total = samples.reduce((sum, value) => sum + value, 0);
904
+ return Math.max(24, Math.round(total / samples.length));
905
+ }
906
+
907
+ _syncVirtualSpacers(body, windowStart, windowEnd, topHeight, bottomHeight) {
908
+ const columns = this._getRenderedColumnsCount();
909
+ const topSpacer = this._ensureVirtualSpacer('top', columns);
910
+ const bottomSpacer = this._ensureVirtualSpacer('bottom', columns);
911
+ const firstVisibleRow = this._rows[windowStart] || null;
912
+ const afterVisibleRow = this._rows[windowEnd] || null;
913
+
914
+ this._updateVirtualSpacer(topSpacer, topHeight, columns);
915
+ this._updateVirtualSpacer(bottomSpacer, bottomHeight, columns);
916
+
917
+ if (firstVisibleRow) {
918
+ body.insertBefore(topSpacer, firstVisibleRow);
919
+ } else if (!topSpacer.parentElement) {
920
+ body.appendChild(topSpacer);
921
+ }
922
+
923
+ if (afterVisibleRow) {
924
+ body.insertBefore(bottomSpacer, afterVisibleRow);
925
+ } else {
926
+ body.appendChild(bottomSpacer);
927
+ }
928
+ }
929
+
930
+ _ensureVirtualSpacer(position, columns) {
931
+ const current = this._virtualSpacers && this._virtualSpacers[position]
932
+ ? this._virtualSpacers[position]
933
+ : null;
934
+ if (current && current.isConnected) {
935
+ this._syncVirtualSpacerColumns(current, columns);
936
+ return current;
937
+ }
938
+
939
+ const spacer = document.createElement('tr');
940
+ spacer.setAttribute('data-virtual-spacer', '1');
941
+ spacer.setAttribute('data-virtual-spacer-position', position);
942
+ spacer.setAttribute('data-group-header', '1');
943
+ spacer.setAttribute('aria-hidden', 'true');
944
+ const cell = document.createElement('td');
945
+ cell.setAttribute('data-virtual-spacer-cell', '1');
946
+ cell.colSpan = Math.max(1, columns);
947
+ spacer.appendChild(cell);
948
+ this._virtualSpacers[position] = spacer;
949
+ return spacer;
950
+ }
951
+
952
+ _syncVirtualSpacerColumns(spacer, columns) {
953
+ if (!spacer || !spacer.cells || !spacer.cells[0]) {
954
+ return;
955
+ }
956
+ spacer.cells[0].colSpan = Math.max(1, columns);
957
+ }
958
+
959
+ _updateVirtualSpacer(spacer, height, columns) {
960
+ if (!spacer || !spacer.cells || !spacer.cells[0]) {
961
+ return;
962
+ }
963
+ const safeHeight = Math.max(0, Math.round(height));
964
+ this._syncVirtualSpacerColumns(spacer, columns);
965
+ const cell = spacer.cells[0];
966
+ cell.style.height = `${safeHeight}px`;
967
+ spacer.hidden = safeHeight <= 0;
968
+ }
969
+
970
+ _teardownVirtualWindow() {
971
+ this._teardownVirtualizationBinding();
972
+ if (this._virtualState.frame && typeof window !== 'undefined' && typeof window.cancelAnimationFrame === 'function') {
973
+ window.cancelAnimationFrame(this._virtualState.frame);
974
+ }
975
+ this._virtualState.start = 0;
976
+ this._virtualState.end = 0;
977
+ this._virtualState.page = -1;
978
+ this._virtualState.perPage = -1;
979
+ this._virtualState.frame = 0;
980
+ this._virtualState.lastScrollTop = -1;
981
+ Object.keys(this._virtualSpacers || {}).forEach((key) => {
982
+ const spacer = this._virtualSpacers[key];
983
+ if (spacer && spacer.parentElement) {
984
+ spacer.parentElement.removeChild(spacer);
985
+ }
986
+ });
987
+ }
988
+
989
+
990
+ _normalizePositiveInt(value, fallback) {
991
+ const parsed = Number.parseInt(value, 10);
992
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
993
+ }
994
+
995
+ _getRoute() {
996
+ const requestOptions = this._params.request || {};
997
+ const attrRoute = this._element.getAttribute('data-request-route');
998
+ const requestRoute = String(requestOptions.route || attrRoute || '').trim();
999
+ if (requestRoute) {
1000
+ return requestRoute;
1001
+ }
1002
+ return this._getFiltersRoute();
1003
+ }
1004
+
1005
+ _restorePersistentState() {
1006
+ const storedLocale = this._readPersistentState('locale');
1007
+ if (storedLocale) {
1008
+ this._params.locale = String(storedLocale).toLowerCase().trim();
1009
+ }
1010
+
1011
+ const storedFiltersMode = this._readPersistentState('filtersApplyMode');
1012
+ if (storedFiltersMode) {
1013
+ if (!this._params.filters || typeof this._params.filters !== 'object') {
1014
+ this._params.filters = {};
1015
+ }
1016
+ this._params.filters.apply = this._normalizeFiltersApplyMode(storedFiltersMode);
1017
+ }
1018
+ }
1019
+
1020
+ _isPersistenceEnabled(key) {
1021
+ const persistence = this._params.persistence || {};
1022
+ const attrEnabled = this._element.getAttribute('data-persistence-enable');
1023
+ const globalEnabled = attrEnabled !== null
1024
+ ? this._isTruthy(attrEnabled)
1025
+ : Boolean(persistence.enable);
1026
+ if (!globalEnabled) {
1027
+ return false;
1028
+ }
1029
+
1030
+ const attrNameMap = {
1031
+ locale: 'data-persistence-locale',
1032
+ filtersApplyMode: 'data-persistence-filters-apply-mode',
1033
+ };
1034
+ const attrName = attrNameMap[key];
1035
+ if (attrName) {
1036
+ const attrValue = this._element.getAttribute(attrName);
1037
+ if (attrValue !== null) {
1038
+ return this._isTruthy(attrValue);
1039
+ }
1040
+ }
1041
+
1042
+ return Boolean(persistence[key]);
1043
+ }
1044
+
1045
+ _getPersistenceStorageKey(key) {
1046
+ const persistence = this._params.persistence || {};
1047
+ const explicit = String(persistence.storageKey || this._element.getAttribute('data-persistence-storage-key') || '').trim();
1048
+ const base = explicit || this._getPerPageStorageKey().replace('perPage', 'state');
1049
+ return `${base}:${key}`;
1050
+ }
1051
+
1052
+ _readPersistentState(key) {
1053
+ if (!this._isPersistenceEnabled(key)) {
1054
+ return '';
1055
+ }
1056
+ try {
1057
+ return String(window.localStorage.getItem(this._getPersistenceStorageKey(key)) || '');
1058
+ } catch (error) {
1059
+ return '';
1060
+ }
1061
+ }
1062
+
1063
+ _writePersistentState(key, value) {
1064
+ if (!this._isPersistenceEnabled(key)) {
1065
+ return;
1066
+ }
1067
+ try {
1068
+ window.localStorage.setItem(this._getPersistenceStorageKey(key), String(value ?? ''));
1069
+ } catch (error) {
1070
+ // Ignore storage errors.
1071
+ }
1072
+ }
1073
+
1074
+ _isTruthy(value) {
1075
+ const normalized = String(value).toLowerCase().trim();
1076
+ return normalized !== 'false' && normalized !== '0' && normalized !== '';
1077
+ }
1078
+
1079
+ _getFields() {
1080
+ const headerFields = Array.from(this._element.querySelectorAll('thead th[data-field]'))
1081
+ .map((cell) => (cell.getAttribute('data-field') || '').trim())
1082
+ .filter(Boolean);
1083
+
1084
+ if (headerFields.length) {
1085
+ return headerFields;
1086
+ }
1087
+
1088
+ return ['sku', 'name', 'category', 'price', 'stock', 'rating'];
1089
+ }
1090
+
1091
+ _getBody() {
1092
+ if (this._element.tBodies && this._element.tBodies[0]) {
1093
+ return this._element.tBodies[0];
1094
+ }
1095
+
1096
+ const body = document.createElement('tbody');
1097
+ this._element.appendChild(body);
1098
+ return body;
1099
+ }
1100
+
1101
+ _getRemoteResponseMode() {
1102
+ const requestOptions = this._params.request || {};
1103
+ const attrResponseMode = this._element.getAttribute('data-request-responsemode');
1104
+ const raw = attrResponseMode !== null ? attrResponseMode : requestOptions.responsemode;
1105
+ const normalized = String(raw || 'data').toLowerCase().trim();
1106
+ if (normalized === 'view' || normalized === 'auto') {
1107
+ return normalized;
1108
+ }
1109
+ return 'data';
1110
+ }
1111
+
1112
+ _getRemoteViewParamName() {
1113
+ const requestOptions = this._params.request || {};
1114
+ const attr = this._element.getAttribute('data-request-viewparam');
1115
+ const raw = attr !== null ? attr : requestOptions.viewparam;
1116
+ const normalized = String(raw || '').trim();
1117
+ return normalized || '';
1118
+ }
1119
+
1120
+ _getRemoteViewParamValue() {
1121
+ const requestOptions = this._params.request || {};
1122
+ const attr = this._element.getAttribute('data-request-viewvalue');
1123
+ const raw = attr !== null ? attr : requestOptions.viewvalue;
1124
+ const normalized = String(raw || '').trim();
1125
+ return normalized || '';
1126
+ }
1127
+
1128
+ _getRemoteFieldsParamName() {
1129
+ const requestOptions = this._params.request || {};
1130
+ const attr = this._element.getAttribute('data-request-fieldsparam');
1131
+ const raw = attr !== null ? attr : requestOptions.fieldsparam;
1132
+ const normalized = String(raw || '').trim();
1133
+ return normalized || '';
1134
+ }
1135
+
1136
+ _getRemoteDataRoute() {
1137
+ const filtersRoute = this._getFiltersRoute();
1138
+ if (filtersRoute) {
1139
+ return filtersRoute;
1140
+ }
1141
+ return this._getRoute();
1142
+ }
1143
+
1144
+ _getRequestParamMap() {
1145
+ const requestOptions = this._params.request || {};
1146
+ const fromOptions = requestOptions.parammap;
1147
+ if (fromOptions && typeof fromOptions === 'object' && !Array.isArray(fromOptions)) {
1148
+ return fromOptions;
1149
+ }
1150
+
1151
+ const attr = this._element.getAttribute('data-request-parammap') || '';
1152
+ if (!attr) {
1153
+ return {};
1154
+ }
1155
+
1156
+ try {
1157
+ const parsed = JSON.parse(attr);
1158
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
1159
+ return parsed;
1160
+ }
1161
+ } catch (error) {
1162
+ return {};
1163
+ }
1164
+
1165
+ return {};
1166
+ }
1167
+
1168
+ _mapRemoteRequestParams(params = {}) {
1169
+ const map = this._getRequestParamMap();
1170
+ const result = {};
1171
+ Object.keys(params || {}).forEach((key) => {
1172
+ const target = Object.prototype.hasOwnProperty.call(map, key)
1173
+ ? String(map[key] || '').trim()
1174
+ : key;
1175
+ const nextKey = target || key;
1176
+ result[nextKey] = params[key];
1177
+ });
1178
+ return result;
1179
+ }
1180
+
1181
+ _getRequestPath(type) {
1182
+ const requestOptions = this._params.request || {};
1183
+ if (type === 'data') {
1184
+ const attrData = this._element.getAttribute('data-request-datapath');
1185
+ const raw = attrData !== null ? attrData : requestOptions.datapath;
1186
+ return String(raw || '').trim();
1187
+ }
1188
+ if (type === 'meta') {
1189
+ const attrMeta = this._element.getAttribute('data-request-metapath');
1190
+ const raw = attrMeta !== null ? attrMeta : requestOptions.metapath;
1191
+ return String(raw || '').trim();
1192
+ }
1193
+ if (type === 'view') {
1194
+ const attrView = this._element.getAttribute('data-request-viewpath');
1195
+ const raw = attrView !== null ? attrView : requestOptions.viewpath;
1196
+ return String(raw || '').trim();
1197
+ }
1198
+ return '';
1199
+ }
1200
+
1201
+ _readPath(source, path) {
1202
+ if (!source || typeof source !== 'object') {
1203
+ return undefined;
1204
+ }
1205
+ const normalized = String(path || '').trim();
1206
+ if (!normalized) {
1207
+ return undefined;
1208
+ }
1209
+ const parts = normalized.split('.').map((item) => String(item || '').trim()).filter(Boolean);
1210
+ if (!parts.length) {
1211
+ return undefined;
1212
+ }
1213
+ let current = source;
1214
+ for (let i = 0; i < parts.length; i += 1) {
1215
+ if (!current || typeof current !== 'object' || !Object.prototype.hasOwnProperty.call(current, parts[i])) {
1216
+ return undefined;
1217
+ }
1218
+ current = current[parts[i]];
1219
+ }
1220
+ return current;
1221
+ }
1222
+
1223
+ _extractRemoteRows(response) {
1224
+ const configured = this._readPath(response, this._getRequestPath('data'));
1225
+ if (Array.isArray(configured)) {
1226
+ return configured;
1227
+ }
1228
+ return Array.isArray(response && response.data) ? response.data : [];
1229
+ }
1230
+
1231
+ _extractRemoteMeta(response) {
1232
+ const configured = this._readPath(response, this._getRequestPath('meta'));
1233
+ if (configured && typeof configured === 'object' && !Array.isArray(configured)) {
1234
+ return configured;
1235
+ }
1236
+ return response && typeof response === 'object' && response.meta && typeof response.meta === 'object'
1237
+ ? response.meta
1238
+ : {};
1239
+ }
1240
+
1241
+ _extractRemoteViewRowsHtml(response) {
1242
+ if (!response || typeof response !== 'object') {
1243
+ return '';
1244
+ }
1245
+
1246
+ const configured = this._readPath(response, this._getRequestPath('view'));
1247
+ if (typeof configured === 'string') {
1248
+ return configured;
1249
+ }
1250
+
1251
+ if (typeof response.view === 'string') {
1252
+ return response.view;
1253
+ }
1254
+
1255
+ const view = response.view && typeof response.view === 'object' ? response.view : null;
1256
+ if (view && typeof view.tbody === 'string') {
1257
+ return view.tbody;
1258
+ }
1259
+ if (view && typeof view.rows === 'string') {
1260
+ return view.rows;
1261
+ }
1262
+
1263
+ if (typeof response.tbody === 'string') {
1264
+ return response.tbody;
1265
+ }
1266
+
1267
+ return '';
1268
+ }
1269
+
1270
+ _renderRemoteViewIfConfigured(viewRowsHtml, rows) {
1271
+ const mode = this._getRemoteResponseMode();
1272
+ const canUseView = typeof viewRowsHtml === 'string' && viewRowsHtml.trim() !== '';
1273
+ const shouldUseView = mode === 'view' || (mode === 'auto' && canUseView);
1274
+ if (!shouldUseView) {
1275
+ return false;
1276
+ }
1277
+
1278
+ if (!canUseView) {
1279
+ if (Array.isArray(rows) && rows.length) {
1280
+ return false;
1281
+ }
1282
+ const message = this._getTableMessage('stateEmpty', 'Ничего нет');
1283
+ this._renderStateRow(message, 'empty');
1284
+ return true;
1285
+ }
1286
+
1287
+ const body = this._getBody();
1288
+ body.innerHTML = viewRowsHtml;
1289
+ if (!this._hasRenderableRowsInBody(body)) {
1290
+ this._renderStateRow(this._getTableMessage('stateEmpty', 'Ничего нет'), 'empty');
1291
+ return true;
1292
+ }
1293
+ this._setFixedColumnsSuppressed(false);
1294
+ this._clearStateMode();
1295
+ return true;
1296
+ }
1297
+
1298
+ _initSorting() {
1299
+ const sortOptions = this._params.sortable || {};
1300
+ if (!sortOptions.enable) {
1301
+ return;
1302
+ }
1303
+ const sortableI18n = this._getI18nSection('sortable');
1304
+ const attrMulti = this._element.getAttribute('data-sort-multi');
1305
+ const multiSort = attrMulti === null
1306
+ ? Boolean(sortOptions.multi)
1307
+ : String(attrMulti).toLowerCase().trim() !== 'false';
1308
+ const attrMultiWithShift = this._element.getAttribute('data-sort-multi-with-shift');
1309
+ const multiSortWithShift = attrMultiWithShift === null
1310
+ ? (sortOptions.multiWithShift !== undefined ? Boolean(sortOptions.multiWithShift) : true)
1311
+ : String(attrMultiWithShift).toLowerCase().trim() !== 'false';
1312
+ const attrHideUnsortedArrows = this._element.getAttribute('data-sort-hide-unsorted-arrows');
1313
+ const hideUnsortedArrows = attrHideUnsortedArrows === null
1314
+ ? Boolean(sortOptions.hideUnsortedArrows)
1315
+ : String(attrHideUnsortedArrows).toLowerCase().trim() !== 'false';
1316
+
1317
+ this._sortable = new Sortable(this._element, {
1318
+ initialField: this._sortState.field,
1319
+ initialDir: this._sortState.dir,
1320
+ ascLabel: sortableI18n.ascLabel,
1321
+ descLabel: sortableI18n.descLabel,
1322
+ multiSort,
1323
+ multiSortWithShift,
1324
+ hideUnsortedArrows,
1325
+ isColumnSortable: (header, index) => {
1326
+ if (!this._isHeaderSortableByConfig(header, index, sortOptions)) {
1327
+ return false;
1328
+ }
1329
+ const userCheck = typeof sortOptions.isColumnSortable === 'function'
1330
+ ? Boolean(sortOptions.isColumnSortable(header))
1331
+ : true;
1332
+ return userCheck;
1333
+ },
1334
+ onChange: (payload) => this._handleSortChange(payload),
1335
+ });
1336
+ this._sortable.init();
1337
+ if (this._sortState.sorts && this._sortState.sorts.length) {
1338
+ this._sortable.setState({ sorts: this._sortState.sorts });
1339
+ }
1340
+ this._refreshStickyAndFixedLayout();
1341
+ }
1342
+
1343
+ _initColumnReorder() {
1344
+ const enabled = this._isColumnReorderEnabled();
1345
+ this._columnReorderState.enabled = enabled;
1346
+ if (!enabled) {
1347
+ return;
1348
+ }
1349
+
1350
+ this._applyStoredColumnOrder();
1351
+ this._syncColumnReorderHeaders();
1352
+ this._bindColumnReorder();
1353
+ }
1354
+
1355
+ _initColumnResize() {
1356
+ const enabled = this._isColumnResizeEnabled();
1357
+ this._columnResizeState.enabled = enabled;
1358
+ if (!enabled) {
1359
+ return;
1360
+ }
1361
+ this._syncColumnResizeHeaders();
1362
+ this._bindColumnResize();
1363
+ }
1364
+
1365
+ _initExpandable() {
1366
+ if (!this._isExpandableEnabled()) {
1367
+ if (this._expandable && typeof this._expandable.destroy === 'function') {
1368
+ this._expandable.destroy();
1369
+ }
1370
+ this._expandable = null;
1371
+ return;
1372
+ }
1373
+
1374
+ const config = this._getExpandableConfig();
1375
+ if (this._expandable && typeof this._expandable.destroy === 'function') {
1376
+ this._expandable.destroy();
1377
+ }
1378
+ this._expandable = new Expandable(this._element, Object.assign({}, config, {
1379
+ onToggle: (payload) => {
1380
+ this._emitAction('rowtoggle', payload);
1381
+ this._emitAction(payload.collapsed ? 'rowcollapse' : 'rowexpand', payload);
1382
+ this._renderFooterFromCurrentState();
1383
+ this._refreshStickyAndFixedLayout();
1384
+ },
1385
+ }));
1386
+ this._expandable.init();
1387
+ }
1388
+
1389
+ _refreshExpandable() {
1390
+ if (!this._expandable || typeof this._expandable.refresh !== 'function') {
1391
+ return;
1392
+ }
1393
+ this._expandable.refresh();
1394
+ }
1395
+
1396
+ _isExpandableEnabled() {
1397
+ const options = this._params.expandable || {};
1398
+ const attr = this._element.getAttribute('data-expandable-enable');
1399
+ if (attr !== null) {
1400
+ return this._isTruthy(attr);
1401
+ }
1402
+ return Boolean(options.enable);
1403
+ }
1404
+
1405
+ _getExpandableConfig() {
1406
+ const options = this._params.expandable || {};
1407
+ const attrId = this._element.getAttribute('data-expandable-id-attr');
1408
+ const attrParent = this._element.getAttribute('data-expandable-parent-attr');
1409
+ const attrToggle = this._element.getAttribute('data-expandable-toggle-selector');
1410
+ const attrCollapsed = this._element.getAttribute('data-expandable-collapsed');
1411
+
1412
+ return {
1413
+ enable: true,
1414
+ idAttr: String(attrId !== null ? attrId : options.idAttr || 'data-expand-id').trim() || 'data-expand-id',
1415
+ parentAttr: String(attrParent !== null ? attrParent : options.parentAttr || 'data-expand-parent-id').trim() || 'data-expand-parent-id',
1416
+ toggleSelector: String(attrToggle !== null ? attrToggle : options.toggleSelector || '[data-expand-toggle]').trim() || '[data-expand-toggle]',
1417
+ collapsed: attrCollapsed !== null ? this._isTruthy(attrCollapsed) : Boolean(options.collapsed),
1418
+ };
1419
+ }
1420
+
1421
+
1422
+ _isColumnResizeEnabled() {
1423
+ const options = this._params.columnResize || {};
1424
+ const attr = this._element.getAttribute('data-column-resize-enable');
1425
+ if (attr !== null) {
1426
+ return this._isTruthy(attr);
1427
+ }
1428
+ return Boolean(options.enable);
1429
+ }
1430
+
1431
+ _syncColumnResizeHeaders() {
1432
+ const headers = this._getHeaderCells();
1433
+ headers.forEach((header) => {
1434
+ if (!header.querySelector('[data-col-resize-handle]')) {
1435
+ const handle = document.createElement('span');
1436
+ handle.className = 'vgdt-col-resize-handle';
1437
+ handle.setAttribute('data-col-resize-handle', '1');
1438
+ handle.setAttribute('role', 'separator');
1439
+ handle.setAttribute('aria-hidden', 'true');
1440
+ header.appendChild(handle);
1441
+ }
1442
+ header.setAttribute('data-col-resize', '1');
1443
+ });
1444
+ }
1445
+
1446
+ _bindColumnResize() {
1447
+ if (this._columnResizeState.bound) {
1448
+ return;
1449
+ }
1450
+ const state = this._columnResizeState;
1451
+
1452
+ state.onMouseDown = (event) => {
1453
+ const handle = event.target && event.target.closest ? event.target.closest('[data-col-resize-handle]') : null;
1454
+ if (!handle) {
1455
+ return;
1456
+ }
1457
+ const header = handle.closest('th');
1458
+ const index = this._findHeaderIndex(header);
1459
+ if (index < 0) {
1460
+ return;
1461
+ }
1462
+ event.preventDefault();
1463
+ state.active = true;
1464
+ state.index = index;
1465
+ state.startX = event.clientX;
1466
+ state.startWidth = Math.max(1, header.getBoundingClientRect().width || header.offsetWidth || 1);
1467
+ this._element.classList.add('is-col-resizing');
1468
+ };
1469
+
1470
+ state.onMouseMove = (event) => {
1471
+ if (!state.active || state.index < 0) {
1472
+ return;
1473
+ }
1474
+ const options = this._params.columnResize || {};
1475
+ const minWidth = Math.max(40, this._normalizePositiveInt(options.minwidth, 80));
1476
+ const maxWidth = Math.max(minWidth, this._normalizePositiveInt(options.maxwidth, 600));
1477
+ state.nextWidth = Math.min(maxWidth, Math.max(minWidth, Math.round(state.startWidth + (event.clientX - state.startX))));
1478
+ if (state.frame) {
1479
+ return;
1480
+ }
1481
+ if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') {
1482
+ this._applyColumnWidth(state.index, state.nextWidth);
1483
+ return;
1484
+ }
1485
+ state.frame = window.requestAnimationFrame(() => {
1486
+ state.frame = 0;
1487
+ if (!state.active || state.index < 0) {
1488
+ return;
1489
+ }
1490
+ this._applyColumnWidth(state.index, state.nextWidth);
1491
+ });
1492
+ };
1493
+
1494
+ state.onMouseUp = () => {
1495
+ if (!state.active) {
1496
+ return;
1497
+ }
1498
+ if (state.frame && typeof window !== 'undefined' && typeof window.cancelAnimationFrame === 'function') {
1499
+ window.cancelAnimationFrame(state.frame);
1500
+ state.frame = 0;
1501
+ }
1502
+ if (state.index >= 0 && state.nextWidth > 0) {
1503
+ this._applyColumnWidth(state.index, state.nextWidth);
1504
+ }
1505
+ state.active = false;
1506
+ state.index = -1;
1507
+ state.nextWidth = 0;
1508
+ this._element.classList.remove('is-col-resizing');
1509
+ this._refreshStickyAndFixedLayout();
1510
+ this._emitAction('columnresize', {});
1511
+ };
1512
+
1513
+ this._element.addEventListener('mousedown', state.onMouseDown);
1514
+ document.addEventListener('mousemove', state.onMouseMove);
1515
+ document.addEventListener('mouseup', state.onMouseUp);
1516
+ state.bound = true;
1517
+ }
1518
+
1519
+ _applyColumnWidth(index, widthPx) {
1520
+ const sections = [];
1521
+ if (this._element.tHead) {
1522
+ sections.push(this._element.tHead);
1523
+ }
1524
+ if (this._element.tBodies && this._element.tBodies.length) {
1525
+ sections.push(...Array.from(this._element.tBodies));
1526
+ }
1527
+ if (this._element.tFoot) {
1528
+ sections.push(this._element.tFoot);
1529
+ }
1530
+
1531
+ sections.forEach((section) => {
1532
+ Array.from(section.rows || []).forEach((row) => {
1533
+ const cell = row.cells && row.cells[index] ? row.cells[index] : null;
1534
+ if (!cell) {
1535
+ return;
1536
+ }
1537
+ cell.style.width = `${widthPx}px`;
1538
+ cell.style.minWidth = `${widthPx}px`;
1539
+ cell.style.maxWidth = `${widthPx}px`;
1540
+ });
1541
+ });
1542
+ }
1543
+
1544
+ _isColumnReorderEnabled() {
1545
+ const options = this._params.columnReorder || {};
1546
+ const attr = this._element.getAttribute('data-column-reorder-enable');
1547
+ if (attr !== null) {
1548
+ const normalized = String(attr).toLowerCase().trim();
1549
+ return normalized !== 'false' && normalized !== '0';
1550
+ }
1551
+ return Boolean(options.enable);
1552
+ }
1553
+
1554
+ _isColumnReorderPersistEnabled() {
1555
+ const options = this._params.columnReorder || {};
1556
+ const attr = this._element.getAttribute('data-column-reorder-persist');
1557
+ if (attr !== null) {
1558
+ const normalized = String(attr).toLowerCase().trim();
1559
+ return normalized !== 'false' && normalized !== '0';
1560
+ }
1561
+ return Boolean(options.persist);
1562
+ }
1563
+
1564
+ _getColumnReorderStorageKey() {
1565
+ const options = this._params.columnReorder || {};
1566
+ const attrStorageKey = this._element.getAttribute('data-column-reorder-storage-key') || '';
1567
+ const explicit = String(options.storageKey || attrStorageKey).trim();
1568
+ if (explicit) {
1569
+ return explicit;
1570
+ }
1571
+ return `${this._getPerPageStorageKey()}:columnOrder`;
1572
+ }
1573
+
1574
+ _bindColumnReorder() {
1575
+ if (this._columnReorderState.bound) {
1576
+ return;
1577
+ }
1578
+
1579
+ this._columnReorderState.onDragStart = (event) => {
1580
+ const th = event.target && event.target.closest ? event.target.closest('th[data-col-reorder="1"]') : null;
1581
+ if (!th || !this._element.contains(th)) {
1582
+ return;
1583
+ }
1584
+ if (this._isHeaderFixedForColumnReorder(th)) {
1585
+ return;
1586
+ }
1587
+
1588
+ const fromIndex = this._findHeaderIndex(th);
1589
+ if (fromIndex < 0) {
1590
+ return;
1591
+ }
1592
+
1593
+ this._columnReorderState.fromIndex = fromIndex;
1594
+ this._columnReorderState.toIndex = fromIndex;
1595
+ this._columnReorderState.isDragging = true;
1596
+ this._element.classList.add('is-col-dragging');
1597
+ th.setAttribute('data-col-dragging', '1');
1598
+
1599
+ if (event.dataTransfer) {
1600
+ event.dataTransfer.effectAllowed = 'move';
1601
+ try {
1602
+ event.dataTransfer.setData('text/plain', String(fromIndex));
1603
+ } catch (error) {
1604
+ // Ignore setData errors.
1605
+ }
1606
+ }
1607
+ };
1608
+
1609
+ this._columnReorderState.onDragOver = (event) => {
1610
+ if (!this._columnReorderState.isDragging) {
1611
+ return;
1612
+ }
1613
+ const th = event.target && event.target.closest ? event.target.closest('th[data-col-reorder="1"]') : null;
1614
+ if (!th || !this._element.contains(th)) {
1615
+ return;
1616
+ }
1617
+ if (this._isHeaderFixedForColumnReorder(th)) {
1618
+ return;
1619
+ }
1620
+ event.preventDefault();
1621
+ const toIndex = this._findHeaderIndex(th);
1622
+ if (toIndex < 0) {
1623
+ return;
1624
+ }
1625
+ this._columnReorderState.toIndex = toIndex;
1626
+ this._syncColumnDragOverMarker(toIndex);
1627
+ };
1628
+
1629
+ this._columnReorderState.onDrop = (event) => {
1630
+ if (!this._columnReorderState.isDragging) {
1631
+ return;
1632
+ }
1633
+ event.preventDefault();
1634
+ const th = event.target && event.target.closest ? event.target.closest('th[data-col-reorder="1"]') : null;
1635
+ if (!th || !this._element.contains(th)) {
1636
+ return;
1637
+ }
1638
+ if (this._isHeaderFixedForColumnReorder(th)) {
1639
+ this._resetColumnDragMarkers();
1640
+ return;
1641
+ }
1642
+ const toIndex = this._findHeaderIndex(th);
1643
+ if (toIndex < 0) {
1644
+ return;
1645
+ }
1646
+
1647
+ const fromIndex = this._columnReorderState.fromIndex;
1648
+ this._resetColumnDragMarkers();
1649
+ this._finalizeColumnReorder(fromIndex, toIndex);
1650
+ };
1651
+
1652
+ this._columnReorderState.onDragEnd = () => {
1653
+ this._resetColumnDragMarkers();
1654
+ };
1655
+
1656
+ this._element.addEventListener('dragstart', this._columnReorderState.onDragStart);
1657
+ this._element.addEventListener('dragover', this._columnReorderState.onDragOver);
1658
+ this._element.addEventListener('drop', this._columnReorderState.onDrop);
1659
+ this._element.addEventListener('dragend', this._columnReorderState.onDragEnd);
1660
+ this._columnReorderState.bound = true;
1661
+ }
1662
+
1663
+ _syncColumnReorderHeaders() {
1664
+ if (!this._columnReorderState.enabled) {
1665
+ return;
1666
+ }
1667
+ const headers = this._getHeaderCells();
1668
+ headers.forEach((header) => {
1669
+ header.setAttribute('data-col-reorder', '1');
1670
+ const draggable = this._isHeaderFixedForColumnReorder(header) ? 'false' : 'true';
1671
+ header.setAttribute('draggable', draggable);
1672
+ });
1673
+ }
1674
+
1675
+ _syncColumnDragOverMarker(index) {
1676
+ const headers = this._getHeaderCells();
1677
+ headers.forEach((header, headerIndex) => {
1678
+ if (headerIndex === index) {
1679
+ header.setAttribute('data-col-drag-over', '1');
1680
+ } else {
1681
+ header.removeAttribute('data-col-drag-over');
1682
+ }
1683
+ });
1684
+ const cloneHeaders = this._cloneStickyState && this._cloneStickyState.head
1685
+ ? Array.from(this._cloneStickyState.head.querySelectorAll('th'))
1686
+ : [];
1687
+ cloneHeaders.forEach((header, headerIndex) => {
1688
+ if (headerIndex === index) {
1689
+ header.setAttribute('data-col-drag-over', '1');
1690
+ } else {
1691
+ header.removeAttribute('data-col-drag-over');
1692
+ }
1693
+ });
1694
+ }
1695
+
1696
+ _resetColumnDragMarkers() {
1697
+ const headers = this._getHeaderCells();
1698
+ headers.forEach((header) => {
1699
+ header.removeAttribute('data-col-drag-over');
1700
+ header.removeAttribute('data-col-dragging');
1701
+ });
1702
+ const cloneHeaders = this._cloneStickyState && this._cloneStickyState.head
1703
+ ? Array.from(this._cloneStickyState.head.querySelectorAll('th'))
1704
+ : [];
1705
+ cloneHeaders.forEach((header) => {
1706
+ header.removeAttribute('data-col-drag-over');
1707
+ header.removeAttribute('data-col-dragging');
1708
+ });
1709
+ this._columnReorderState.fromIndex = -1;
1710
+ this._columnReorderState.toIndex = -1;
1711
+ this._columnReorderState.isDragging = false;
1712
+ this._element.classList.remove('is-col-dragging');
1713
+ }
1714
+
1715
+ _finalizeColumnReorder(fromIndex, toIndex) {
1716
+ if (!Number.isInteger(fromIndex) || !Number.isInteger(toIndex) || fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) {
1717
+ return;
1718
+ }
1719
+
1720
+ this._moveColumn(fromIndex, toIndex);
1721
+ this._normalizeFixedColumnsOrder();
1722
+ this._afterColumnReorder();
1723
+ }
1724
+
1725
+ _moveColumn(fromIndex, toIndex) {
1726
+ const sections = [];
1727
+ if (this._element.tHead) {
1728
+ sections.push(this._element.tHead);
1729
+ }
1730
+ if (this._element.tBodies && this._element.tBodies.length) {
1731
+ sections.push(...Array.from(this._element.tBodies));
1732
+ }
1733
+ if (this._element.tFoot) {
1734
+ sections.push(this._element.tFoot);
1735
+ }
1736
+
1737
+ sections.forEach((section) => {
1738
+ Array.from(section.rows || []).forEach((row) => {
1739
+ const cells = Array.from(row.cells || []);
1740
+ if (fromIndex < 0 || fromIndex >= cells.length || toIndex < 0 || toIndex >= cells.length) {
1741
+ return;
1742
+ }
1743
+ const movingCell = cells[fromIndex];
1744
+ const referenceCell = fromIndex < toIndex ? cells[toIndex].nextSibling : cells[toIndex];
1745
+ row.insertBefore(movingCell, referenceCell || null);
1746
+ });
1747
+ });
1748
+ }
1749
+
1750
+ _afterColumnReorder() {
1751
+ this._fields = this._getFields();
1752
+
1753
+ const normalizedSorts = this._getNormalizedSorts(this._sortState.sorts || []);
1754
+ const primarySort = normalizedSorts[0] || null;
1755
+ this._sortState = {
1756
+ field: primarySort ? primarySort.field : '',
1757
+ dir: primarySort ? primarySort.dir : this._normalizeSortDir(this._sortState.dir),
1758
+ columnIndex: primarySort ? primarySort.columnIndex : -1,
1759
+ sorts: normalizedSorts,
1760
+ };
1761
+
1762
+ if (this._sortable && typeof this._sortable.refresh === 'function') {
1763
+ this._sortable.refresh({ sorts: normalizedSorts });
1764
+ }
1765
+
1766
+ this._syncColumnReorderHeaders();
1767
+ this._refreshStickyAndFixedLayout();
1768
+ this._syncUrlState();
1769
+ this._storeColumnOrder();
1770
+ const order = this._getCurrentColumnOrder();
1771
+ this._emitAction('columnreorder', { order });
1772
+ }
1773
+
1774
+ _getCurrentColumnOrder() {
1775
+ return this._getHeaderCells()
1776
+ .map((header) => String(header.getAttribute('data-field') || '').trim())
1777
+ .filter(Boolean);
1778
+ }
1779
+
1780
+ _storeColumnOrder() {
1781
+ if (!this._isColumnReorderPersistEnabled()) {
1782
+ return;
1783
+ }
1784
+ const order = this._getCurrentColumnOrder();
1785
+ if (!order.length) {
1786
+ return;
1787
+ }
1788
+ try {
1789
+ window.localStorage.setItem(this._getColumnReorderStorageKey(), order.join(','));
1790
+ } catch (error) {
1791
+ // Ignore storage errors.
1792
+ }
1793
+ }
1794
+
1795
+ _readStoredColumnOrder() {
1796
+ if (!this._isColumnReorderPersistEnabled()) {
1797
+ return [];
1798
+ }
1799
+ try {
1800
+ const raw = String(window.localStorage.getItem(this._getColumnReorderStorageKey()) || '').trim();
1801
+ if (!raw) {
1802
+ return [];
1803
+ }
1804
+ return raw.split(',').map((item) => item.trim()).filter(Boolean);
1805
+ } catch (error) {
1806
+ return [];
1807
+ }
1808
+ }
1809
+
1810
+ _applyStoredColumnOrder() {
1811
+ const order = this._readStoredColumnOrder();
1812
+ if (!order.length) {
1813
+ return;
1814
+ }
1815
+
1816
+ let changed = false;
1817
+ order.forEach((field, targetIndex) => {
1818
+ const currentIndex = this._findHeaderIndexByField(field);
1819
+ if (currentIndex < 0 || currentIndex === targetIndex) {
1820
+ return;
1821
+ }
1822
+ this._moveColumn(currentIndex, targetIndex);
1823
+ changed = true;
1824
+ });
1825
+ if (this._normalizeFixedColumnsOrder()) {
1826
+ changed = true;
1827
+ }
1828
+
1829
+ if (changed) {
1830
+ this._afterColumnReorder();
1831
+ }
1832
+ }
1833
+
1834
+ _isHeaderFixedForColumnReorder(header) {
1835
+ if (!header) {
1836
+ return false;
1837
+ }
1838
+ const fixedSide = String(header.getAttribute('data-fixed-side') || '').toLowerCase().trim();
1839
+ if (fixedSide === 'left' || fixedSide === 'right') {
1840
+ return true;
1841
+ }
1842
+ const fixedAttr = String(header.getAttribute('data-fixed') || '').toLowerCase().trim();
1843
+ const fixedColumnAttr = String(header.getAttribute('data-fixed-column') || '').toLowerCase().trim();
1844
+ if (fixedAttr === 'left' || fixedAttr === 'right' || fixedColumnAttr === 'left' || fixedColumnAttr === 'right') {
1845
+ return true;
1846
+ }
1847
+ const index = this._findHeaderIndex(header);
1848
+ if (index < 0) {
1849
+ return false;
1850
+ }
1851
+ const fixed = this._getFixedColumnsByHeaders(this._getHeaderCells(), {
1852
+ parseTableAttr: true,
1853
+ fallbackByPosition: true,
1854
+ });
1855
+ return fixed.left.includes(index) || fixed.right.includes(index);
1856
+ }
1857
+
1858
+ _normalizeFixedColumnsOrder() {
1859
+ const headers = this._getHeaderCells();
1860
+ if (!headers.length) {
1861
+ return false;
1862
+ }
1863
+
1864
+ const fixed = this._getFixedColumnsByHeaders(headers, {
1865
+ parseTableAttr: true,
1866
+ fallbackByPosition: true,
1867
+ });
1868
+ if (!fixed.left.length && !fixed.right.length) {
1869
+ return false;
1870
+ }
1871
+
1872
+ const leftHeaders = fixed.left.map((index) => headers[index]).filter(Boolean);
1873
+ const rightHeaders = fixed.right.map((index) => headers[index]).filter(Boolean);
1874
+ let changed = false;
1875
+ let targetLeft = 0;
1876
+ leftHeaders.forEach((header) => {
1877
+ const currentIndex = this._findHeaderIndex(header);
1878
+ if (currentIndex >= 0 && currentIndex !== targetLeft) {
1879
+ this._moveColumn(currentIndex, targetLeft);
1880
+ changed = true;
1881
+ }
1882
+ targetLeft += 1;
1883
+ });
1884
+
1885
+ let targetRight = this._getHeaderCells().length - 1;
1886
+ for (let index = rightHeaders.length - 1; index >= 0; index -= 1) {
1887
+ const header = rightHeaders[index];
1888
+ const currentIndex = this._findHeaderIndex(header);
1889
+ if (currentIndex >= 0 && currentIndex !== targetRight) {
1890
+ this._moveColumn(currentIndex, targetRight);
1891
+ changed = true;
1892
+ }
1893
+ targetRight -= 1;
1894
+ }
1895
+
1896
+ return changed;
1897
+ }
1898
+
1899
+ _initSearch() {
1900
+ if (!this._isRemote) {
1901
+ return;
1902
+ }
1903
+
1904
+ const searchOptions = this._params.search || {};
1905
+ if (!this._isSearchEnabled()) {
1906
+ return;
1907
+ }
1908
+
1909
+ const inputSelector = this._getSearchInputSelector();
1910
+ if (!inputSelector) {
1911
+ return;
1912
+ }
1913
+
1914
+ const attrParam = this._element.getAttribute('data-search-param') || '';
1915
+ const param = String(searchOptions.param || attrParam || 'q').trim() || 'q';
1916
+ if (Object.prototype.hasOwnProperty.call(this._remoteParams, param)) {
1917
+ const input = this._getSearchInputNode(inputSelector);
1918
+ if (input && input.value !== this._remoteParams[param]) {
1919
+ input.value = this._remoteParams[param];
1920
+ }
1921
+ }
1922
+
1923
+ if (this._search && typeof this._search.destroy === 'function') {
1924
+ this._search.destroy();
1925
+ }
1926
+
1927
+ this._search = new Search({
1928
+ enabled: true,
1929
+ debounceMs: searchOptions.debounce,
1930
+ inputSelector,
1931
+ param,
1932
+ onSearch: ({ value, param: searchParam }) => {
1933
+ const next = {};
1934
+ next[searchParam] = value;
1935
+ const filtersState = this._getCurrentFiltersEventState();
1936
+ const emitFullContext = this._isFiltersEmitFullContextEnabled();
1937
+ const isSearched = this._isSearchedValue(value);
1938
+ this._emitAction('search', {
1939
+ param: searchParam,
1940
+ value,
1941
+ });
1942
+ if (!this._isRemote || !emitFullContext) {
1943
+ this._emitAction('filterschange', {
1944
+ filters: Object.assign({}, filtersState.filters || {}),
1945
+ params: Object.assign({}, filtersState.params || {}),
1946
+ fields: Array.isArray(filtersState.fields) ? filtersState.fields.slice() : [],
1947
+ meta: Object.assign({}, filtersState.meta || {}),
1948
+ phase: 'change',
1949
+ isFiltered: this._isFilteredState(filtersState) || isSearched,
1950
+ isSearched,
1951
+ });
1952
+ }
1953
+ this.updateRemoteParams(next, {
1954
+ replace: false,
1955
+ resetPage: true,
1956
+ reload: true,
1957
+ trigger: emitFullContext ? 'filterschange' : '',
1958
+ filtersState: emitFullContext ? filtersState : null,
1959
+ });
1960
+ },
1961
+ });
1962
+ this._search.init();
1963
+ }
1964
+
1965
+ _getSearchInputSelector() {
1966
+ const searchOptions = this._params.search || {};
1967
+ const attrSelector = this._element.getAttribute('data-search-input') || '';
1968
+ return String(searchOptions.input || attrSelector).trim();
1969
+ }
1970
+
1971
+ _getSearchInputNode(selector = '') {
1972
+ const nextSelector = String(selector || this._getSearchInputSelector() || '').trim();
1973
+ if (!nextSelector) {
1974
+ this._searchInputCacheSelector = '';
1975
+ this._searchInputCacheNode = null;
1976
+ return null;
1977
+ }
1978
+ const shouldRefresh = this._searchInputCacheSelector !== nextSelector
1979
+ || !this._searchInputCacheNode
1980
+ || !this._searchInputCacheNode.isConnected;
1981
+ if (shouldRefresh) {
1982
+ this._searchInputCacheSelector = nextSelector;
1983
+ this._searchInputCacheNode = document.querySelector(nextSelector);
1984
+ }
1985
+ return this._searchInputCacheNode;
1986
+ }
1987
+
1988
+ _initFilters() {
1989
+ const filterOptions = this._params.filters || {};
1990
+ if (!this._isFiltersEnabled()) {
1991
+ return;
1992
+ }
1993
+
1994
+ const formNode = filterOptions.form && filterOptions.form.nodeType === 1
1995
+ ? filterOptions.form
1996
+ : null;
1997
+ const attrSelector = this._element.getAttribute('data-filters-form') || '';
1998
+ const formSelector = formNode
1999
+ ? ''
2000
+ : (typeof filterOptions.form === 'string'
2001
+ ? String(filterOptions.form).trim()
2002
+ : String(attrSelector).trim());
2003
+ if (!formNode && !formSelector) {
2004
+ return;
2005
+ }
2006
+ const attrApplyMode = this._element.getAttribute('data-filters-apply') || '';
2007
+ const applyMode = this._normalizeFiltersApplyMode(filterOptions.apply || attrApplyMode || 'auto');
2008
+ this._writePersistentState('filtersApplyMode', applyMode);
2009
+ const initialValues = this._getInitialFilterValues();
2010
+ const button = filterOptions.button && typeof filterOptions.button === 'object'
2011
+ ? filterOptions.button
2012
+ : {};
2013
+
2014
+ if (this._filters && typeof this._filters.destroy === 'function') {
2015
+ this._filters.destroy();
2016
+ }
2017
+
2018
+ this._filters = new Filters({
2019
+ enabled: true,
2020
+ formSelector,
2021
+ formNode,
2022
+ debounceMs: filterOptions.debounce,
2023
+ applyMode,
2024
+ button: {
2025
+ apply: String(
2026
+ button.apply
2027
+ || this._element.getAttribute('data-filters-button-apply')
2028
+ || '[data-filter-apply]'
2029
+ ).trim(),
2030
+ reset: String(
2031
+ button.reset
2032
+ || this._element.getAttribute('data-filters-button-reset')
2033
+ || '[data-filter-reset]'
2034
+ ).trim(),
2035
+ },
2036
+ fieldAttr: filterOptions.fieldAttr,
2037
+ partAttr: filterOptions.partAttr,
2038
+ typeAttr: filterOptions.typeAttr,
2039
+ valueAttr: filterOptions.valueAttr,
2040
+ operatorAttr: filterOptions.operatorAttr,
2041
+ defaultOperator: filterOptions.defaultOperator,
2042
+ skipEmpty: filterOptions.skipEmpty,
2043
+ trimValues: filterOptions.trimValues,
2044
+ initialValues,
2045
+ onReset: () => {
2046
+ const searchSelector = this._getSearchInputSelector();
2047
+ if (!searchSelector) {
2048
+ return;
2049
+ }
2050
+ const searchInput = this._getSearchInputNode(searchSelector);
2051
+ if (searchInput) {
2052
+ searchInput.value = '';
2053
+ }
2054
+ const searchParam = this._getSearchParamName();
2055
+ if (!searchParam) {
2056
+ return;
2057
+ }
2058
+ delete this._remoteParams[searchParam];
2059
+ },
2060
+ onChange: (state) => {
2061
+ const normalizedState = this._normalizeFiltersEventState(state);
2062
+ this._activeFilters = Object.assign({}, normalizedState.filters || {});
2063
+ if (!this._isRemote || !this._isFiltersEmitFullContextEnabled()) {
2064
+ this._emitAction('filterschange', {
2065
+ filters: Object.assign({}, normalizedState.filters || {}),
2066
+ params: Object.assign({}, normalizedState.params || {}),
2067
+ fields: Array.isArray(normalizedState.fields) ? normalizedState.fields.slice() : [],
2068
+ meta: Object.assign({}, normalizedState.meta || {}),
2069
+ phase: 'change',
2070
+ isFiltered: this._isFilteredState(normalizedState) || this._isSearchActiveFromParams(this._remoteParams || {}),
2071
+ isSearched: this._isSearchActiveFromParams(this._remoteParams || {}),
2072
+ });
2073
+ }
2074
+ this._handleFiltersStateChange(normalizedState);
2075
+ },
2076
+ });
2077
+ this._filters.init();
2078
+ }
2079
+
2080
+ _handleFiltersStateChange(state) {
2081
+ const rawNextParams = state && state.params && typeof state.params === 'object'
2082
+ ? state.params
2083
+ : {};
2084
+ const nextParams = this._buildFilterRemoteParamsPatch(rawNextParams);
2085
+ const filterOptions = this._params.filters || {};
2086
+ const resetPage = filterOptions.resetPageOnChange === undefined
2087
+ ? true
2088
+ : Boolean(filterOptions.resetPageOnChange);
2089
+
2090
+ if (this._isRemote) {
2091
+ const emitFullContext = this._isFiltersEmitFullContextEnabled();
2092
+ this.updateRemoteParams(nextParams, {
2093
+ replace: false,
2094
+ resetPage,
2095
+ reload: true,
2096
+ trigger: emitFullContext ? 'filterschange' : '',
2097
+ filtersState: emitFullContext ? this._normalizeFiltersEventState(state) : null,
2098
+ });
2099
+ return;
2100
+ }
2101
+
2102
+ this._remoteParams = Object.assign({}, nextParams);
2103
+ this._applyLocalFilters();
2104
+ if (this._isPaginationEnabled() && this._pagination) {
2105
+ const perPage = this._pageState.perPage || this._normalizePositiveInt(this._params.pagination.perPage, 10);
2106
+ const nextPage = resetPage ? 1 : this._pageState.page;
2107
+ const totalPages = this._getTotalPages(perPage);
2108
+ const safePage = Math.max(1, Math.min(nextPage, totalPages));
2109
+ this._pageState = { page: safePage, perPage };
2110
+ this._pagination.setMeta({
2111
+ page: safePage,
2112
+ perPage,
2113
+ totalPages,
2114
+ });
2115
+ this._renderPageRows(safePage, perPage);
2116
+ } else {
2117
+ const page = this._pageState.page || 1;
2118
+ const perPage = this._pageState.perPage || this._normalizePositiveInt(this._params.pagination.perPage, 10);
2119
+ this._renderPageRows(page, perPage);
2120
+ }
2121
+ this._refreshStickyAndFixedLayout();
2122
+ this._syncUrlState();
2123
+ }
2124
+
2125
+ _buildFilterRemoteParamsPatch(nextFilterParams = {}) {
2126
+ const source = nextFilterParams && typeof nextFilterParams === 'object'
2127
+ ? nextFilterParams
2128
+ : {};
2129
+ const patch = {};
2130
+ const keys = this._getFilterFieldKeys();
2131
+
2132
+ keys.forEach((field) => {
2133
+ const normalizedField = String(field || '').trim();
2134
+ if (!normalizedField) {
2135
+ return;
2136
+ }
2137
+
2138
+ if (Object.prototype.hasOwnProperty.call(source, normalizedField)) {
2139
+ patch[normalizedField] = source[normalizedField];
2140
+ } else {
2141
+ patch[normalizedField] = '';
2142
+ }
2143
+
2144
+ const operatorKey = `${normalizedField}_op`;
2145
+ if (Object.prototype.hasOwnProperty.call(source, operatorKey)) {
2146
+ patch[operatorKey] = source[operatorKey];
2147
+ } else {
2148
+ patch[operatorKey] = '';
2149
+ }
2150
+ });
2151
+
2152
+ Object.keys(source).forEach((key) => {
2153
+ if (!Object.prototype.hasOwnProperty.call(patch, key)) {
2154
+ patch[key] = source[key];
2155
+ }
2156
+ });
2157
+
2158
+ return patch;
2159
+ }
2160
+
2161
+ _normalizeFiltersEventState(state) {
2162
+ const source = state && typeof state === 'object' ? state : {};
2163
+ const filters = source.filters && typeof source.filters === 'object'
2164
+ ? Object.assign({}, source.filters)
2165
+ : {};
2166
+ const params = source.params && typeof source.params === 'object'
2167
+ ? Object.assign({}, source.params)
2168
+ : {};
2169
+ const explicitFields = Array.isArray(source.fields)
2170
+ ? source.fields.slice().map((field) => String(field || '').trim()).filter(Boolean)
2171
+ : Object.keys(filters);
2172
+ const inferredFromParams = Object.keys(params)
2173
+ .map((key) => String(key || '').trim())
2174
+ .filter((key) => key !== '' && !/_op$/i.test(key))
2175
+ .filter((key) => this._isNonEmptyFilterParamValue(params[key]))
2176
+ .map((key) => key.replace(/_value$/i, ''));
2177
+ const fields = Array.from(new Set(explicitFields.concat(inferredFromParams)));
2178
+ const meta = source.meta && typeof source.meta === 'object'
2179
+ ? Object.assign({}, source.meta)
2180
+ : {};
2181
+ if (!Object.prototype.hasOwnProperty.call(meta, 'count')) {
2182
+ meta.count = fields.length;
2183
+ }
2184
+ return {
2185
+ filters,
2186
+ params,
2187
+ fields,
2188
+ meta,
2189
+ };
2190
+ }
2191
+
2192
+ _isFiltersEmitFullContextEnabled() {
2193
+ const filters = this._params.filters || {};
2194
+ const attr = this._element.getAttribute('data-filters-emit-full-context');
2195
+ if (attr !== null) {
2196
+ return this._isTruthy(attr);
2197
+ }
2198
+ if (filters.emitFullContext === undefined || filters.emitFullContext === null) {
2199
+ return true;
2200
+ }
2201
+ return Boolean(filters.emitFullContext);
2202
+ }
2203
+
2204
+ _isFilteredState(state) {
2205
+ const normalizedState = this._normalizeFiltersEventState(state);
2206
+ if (normalizedState.fields.length > 0) {
2207
+ return true;
2208
+ }
2209
+ return Object.keys(normalizedState.params || {})
2210
+ .some((key) => {
2211
+ const normalizedKey = String(key || '').trim();
2212
+ if (!normalizedKey || /_op$/i.test(normalizedKey)) {
2213
+ return false;
2214
+ }
2215
+ return this._isNonEmptyFilterParamValue(normalizedState.params[normalizedKey]);
2216
+ });
2217
+ }
2218
+
2219
+ _getCurrentFiltersEventState() {
2220
+ let state = null;
2221
+ if (this._filters && typeof this._filters.getState === 'function') {
2222
+ state = this._normalizeFiltersEventState(this._filters.getState());
2223
+ this._activeFilters = Object.assign({}, state.filters || {});
2224
+ }
2225
+
2226
+ if (!state) {
2227
+ const params = this._extractFilterParamsFromRemoteParams(this._remoteParams || {});
2228
+ state = this._normalizeFiltersEventState({
2229
+ filters: Object.assign({}, this._activeFilters || {}),
2230
+ params,
2231
+ });
2232
+ }
2233
+
2234
+ return state;
2235
+ }
2236
+
2237
+ _extractFilterParamsFromRemoteParams(sourceParams = {}) {
2238
+ const source = sourceParams && typeof sourceParams === 'object'
2239
+ ? sourceParams
2240
+ : {};
2241
+ const next = {};
2242
+ const keySet = new Set();
2243
+
2244
+ this._getFilterFieldKeys().forEach((key) => keySet.add(key));
2245
+ Object.keys(this._activeFilters || {}).forEach((key) => keySet.add(String(key || '').trim()));
2246
+
2247
+ keySet.forEach((key) => {
2248
+ const normalizedKey = String(key || '').trim();
2249
+ if (!normalizedKey) {
2250
+ return;
2251
+ }
2252
+ if (Object.prototype.hasOwnProperty.call(source, normalizedKey)) {
2253
+ next[normalizedKey] = source[normalizedKey];
2254
+ }
2255
+ const operatorKey = `${normalizedKey}_op`;
2256
+ if (Object.prototype.hasOwnProperty.call(source, operatorKey)) {
2257
+ next[operatorKey] = source[operatorKey];
2258
+ }
2259
+ });
2260
+
2261
+ return next;
2262
+ }
2263
+
2264
+ _getFiltersRequestMetaIfActive() {
2265
+ const filtersState = this._getCurrentFiltersEventState();
2266
+ if (!this._isFilteredState(filtersState)) {
2267
+ return null;
2268
+ }
2269
+ return {
2270
+ trigger: 'filterschange',
2271
+ filtersState,
2272
+ };
2273
+ }
2274
+
2275
+ _isNonEmptyFilterParamValue(value) {
2276
+ if (Array.isArray(value)) {
2277
+ return value.some((item) => String(item || '').trim() !== '');
2278
+ }
2279
+ return value !== undefined && value !== null && String(value).trim() !== '';
2280
+ }
2281
+
2282
+ _isSearchedValue(value) {
2283
+ return this._isNonEmptyFilterParamValue(value);
2284
+ }
2285
+
2286
+ _isSearchActiveFromParams(sourceParams = {}) {
2287
+ if (!this._isSearchEnabled()) {
2288
+ return false;
2289
+ }
2290
+ const params = sourceParams && typeof sourceParams === 'object'
2291
+ ? sourceParams
2292
+ : {};
2293
+ const searchParam = this._getSearchParamName();
2294
+ if (!searchParam || !Object.prototype.hasOwnProperty.call(params, searchParam)) {
2295
+ return false;
2296
+ }
2297
+ return this._isSearchedValue(params[searchParam]);
2298
+ }
2299
+
2300
+ _isSearchEnabled() {
2301
+ const search = this._params.search || {};
2302
+ if (search.enable !== undefined && search.enable !== null) {
2303
+ const normalized = String(search.enable).toLowerCase().trim();
2304
+ if (normalized === 'true' || normalized === '1') {
2305
+ return true;
2306
+ }
2307
+ if (normalized === 'false' || normalized === '0') {
2308
+ return false;
2309
+ }
2310
+ return Boolean(search.enable);
2311
+ }
2312
+
2313
+ const attr = this._element.getAttribute('data-search-enable');
2314
+ if (attr !== null) {
2315
+ const normalized = String(attr).toLowerCase().trim();
2316
+ return normalized !== 'false' && normalized !== '0';
2317
+ }
2318
+
2319
+ return false;
2320
+ }
2321
+
2322
+ _isFiltersEnabled() {
2323
+ const filters = this._params.filters || {};
2324
+ if (filters.enable !== undefined && filters.enable !== null) {
2325
+ const normalized = String(filters.enable).toLowerCase().trim();
2326
+ if (normalized === 'true' || normalized === '1') {
2327
+ return true;
2328
+ }
2329
+ if (normalized === 'false' || normalized === '0') {
2330
+ return false;
2331
+ }
2332
+ return Boolean(filters.enable);
2333
+ }
2334
+
2335
+ const attr = this._element.getAttribute('data-filters-enable');
2336
+ if (attr !== null) {
2337
+ const normalized = String(attr).toLowerCase().trim();
2338
+ return normalized !== 'false' && normalized !== '0';
2339
+ }
2340
+
2341
+ return false;
2342
+ }
2343
+
2344
+ _normalizeFiltersApplyMode(value) {
2345
+ const normalized = String(value || 'auto').toLowerCase().trim();
2346
+ return normalized === 'manual' ? 'manual' : 'auto';
2347
+ }
2348
+
2349
+ _handleSortChange(payload) {
2350
+ const normalizedSorts = this._getNormalizedSorts(Array.isArray(payload.sorts) ? payload.sorts : []);
2351
+ const primarySort = normalizedSorts[0] || null;
2352
+ this._sortState = {
2353
+ field: primarySort ? primarySort.field : (payload.field || ''),
2354
+ dir: primarySort ? primarySort.dir : this._normalizeSortDir(payload.dir),
2355
+ columnIndex: primarySort && Number.isInteger(primarySort.columnIndex) ? primarySort.columnIndex : (Number.isInteger(payload.columnIndex) ? payload.columnIndex : -1),
2356
+ sorts: normalizedSorts,
2357
+ };
2358
+ this._emitAction('sortchange', {
2359
+ sort: this._sortState.field,
2360
+ dir: this._sortState.dir,
2361
+ sorts: this._sortState.sorts.slice(),
2362
+ isRemote: this._isRemote,
2363
+ });
2364
+
2365
+ if (this._isRemote) {
2366
+ const perPage = this._pageState.perPage || this._normalizePositiveInt(this._params.pagination.perPage, 10);
2367
+ this._pageState = { page: 1, perPage };
2368
+ this._loadRemotePage(1, perPage);
2369
+ return;
2370
+ }
2371
+
2372
+ if (!this._sortState.sorts.length && this._sortState.columnIndex < 0) {
2373
+ this._resetLocalRowsOrder();
2374
+ } else {
2375
+ this._sortLocalRows(this._sortState.sorts.length ? this._sortState.sorts : [{
2376
+ field: this._sortState.field,
2377
+ dir: this._sortState.dir,
2378
+ columnIndex: this._sortState.columnIndex,
2379
+ }]);
2380
+ }
2381
+
2382
+ if (this._isPaginationEnabled() && this._pagination) {
2383
+ const perPage = this._pageState.perPage || this._normalizePositiveInt(this._params.pagination.perPage, 10);
2384
+ const totalPages = this._getTotalPages(perPage);
2385
+ this._pageState = { page: 1, perPage };
2386
+ this._storePage(1);
2387
+ this._pagination.setMeta({
2388
+ page: 1,
2389
+ perPage,
2390
+ totalPages,
2391
+ });
2392
+ this._renderPageRows(1, perPage);
2393
+ }
2394
+ this._refreshExpandable();
2395
+ this._renderFooterFromCurrentState();
2396
+ this._refreshStickyAndFixedLayout();
2397
+ this._syncUrlState();
2398
+ }
2399
+
2400
+ _sortLocalRows(sortList) {
2401
+ if (!this._rows.length) {
2402
+ this._rows = this._getTableRows();
2403
+ }
2404
+
2405
+ const normalizedSorts = this._getNormalizedSorts(sortList);
2406
+ if (!normalizedSorts.length || !this._rows.length) {
2407
+ return;
2408
+ }
2409
+
2410
+ this._rows.sort((leftRow, rightRow) => {
2411
+ for (let index = 0; index < normalizedSorts.length; index += 1) {
2412
+ const sort = normalizedSorts[index];
2413
+ const factor = sort.dir === 'desc' ? -1 : 1;
2414
+ const columnIndex = sort.columnIndex;
2415
+ if (!Number.isInteger(columnIndex) || columnIndex < 0) {
2416
+ continue;
2417
+ }
2418
+ const leftValue = leftRow.cells[columnIndex] ? leftRow.cells[columnIndex].textContent : '';
2419
+ const rightValue = rightRow.cells[columnIndex] ? rightRow.cells[columnIndex].textContent : '';
2420
+ const compared = this._compareSortableValues(leftValue, rightValue) * factor;
2421
+ if (compared !== 0) {
2422
+ return compared;
2423
+ }
2424
+ }
2425
+ return 0;
2426
+ });
2427
+
2428
+ const body = this._getBody();
2429
+ this._rows.forEach((row) => {
2430
+ body.appendChild(row);
2431
+ });
2432
+ }
2433
+
2434
+ _resetLocalRowsOrder() {
2435
+ if (!this._originalRows.length) {
2436
+ this._originalRows = this._getTableRows();
2437
+ }
2438
+ this._rows = this._originalRows.slice();
2439
+
2440
+ const body = this._getBody();
2441
+ this._rows.forEach((row) => {
2442
+ body.appendChild(row);
2443
+ });
2444
+ }
2445
+
2446
+ _applyLocalFilters() {
2447
+ if (!this._originalRows.length) {
2448
+ this._originalRows = this._getTableRows();
2449
+ }
2450
+ const filters = this._activeFilters && typeof this._activeFilters === 'object'
2451
+ ? this._activeFilters
2452
+ : {};
2453
+ const filterKeys = Object.keys(filters);
2454
+ if (!filterKeys.length) {
2455
+ this._rows = this._originalRows.slice();
2456
+ this._rows.forEach((row) => {
2457
+ row.hidden = false;
2458
+ });
2459
+ return;
2460
+ }
2461
+
2462
+ const fieldIndexMap = this._getFieldIndexMap();
2463
+ const filteredRows = [];
2464
+ this._originalRows.forEach((row) => {
2465
+ const matched = this._rowMatchesFilters(row, filters, fieldIndexMap);
2466
+ row.hidden = !matched;
2467
+ if (matched) {
2468
+ filteredRows.push(row);
2469
+ }
2470
+ });
2471
+ this._rows = filteredRows;
2472
+ }
2473
+
2474
+ _getFieldIndexMap() {
2475
+ const map = {};
2476
+ this._getHeaderCells().forEach((header, index) => {
2477
+ const field = String(header.getAttribute('data-field') || '').trim();
2478
+ if (field) {
2479
+ map[field] = index;
2480
+ }
2481
+ });
2482
+ return map;
2483
+ }
2484
+
2485
+ _rowMatchesFilters(row, filters, fieldIndexMap) {
2486
+ const keys = Object.keys(filters || {});
2487
+ for (let i = 0; i < keys.length; i += 1) {
2488
+ const key = keys[i];
2489
+ const descriptor = filters[key];
2490
+ if (!descriptor || typeof descriptor !== 'object') {
2491
+ continue;
2492
+ }
2493
+ const field = String(descriptor.field || key || '').trim();
2494
+ const columnIndex = Object.prototype.hasOwnProperty.call(fieldIndexMap, field)
2495
+ ? fieldIndexMap[field]
2496
+ : -1;
2497
+ if (columnIndex < 0) {
2498
+ continue;
2499
+ }
2500
+ const cell = row.cells && row.cells[columnIndex] ? row.cells[columnIndex] : null;
2501
+ const cellValue = cell ? String(cell.textContent || '').trim() : '';
2502
+ if (!this._matchesFilterValue(cellValue, descriptor)) {
2503
+ return false;
2504
+ }
2505
+ }
2506
+ return true;
2507
+ }
2508
+
2509
+ _matchesFilterValue(rawCellValue, descriptor) {
2510
+ const value = descriptor.values && descriptor.values.length
2511
+ ? descriptor.values
2512
+ : descriptor.value;
2513
+ const operator = String(descriptor.operator || descriptor.type || 'eq').toLowerCase().trim();
2514
+ const left = String(rawCellValue || '');
2515
+
2516
+ if (Array.isArray(value)) {
2517
+ const normalizedValues = value.map((item) => String(item || '').toLowerCase().trim()).filter(Boolean);
2518
+ if (!normalizedValues.length) {
2519
+ return true;
2520
+ }
2521
+ return normalizedValues.includes(left.toLowerCase().trim());
2522
+ }
2523
+
2524
+ const right = String(value || '');
2525
+ if (right === '') {
2526
+ return true;
2527
+ }
2528
+
2529
+ const leftLower = left.toLowerCase();
2530
+ const rightLower = right.toLowerCase();
2531
+ const leftNumber = Number.parseFloat(left.replace(',', '.'));
2532
+ const rightNumber = Number.parseFloat(right.replace(',', '.'));
2533
+ const hasNumbers = Number.isFinite(leftNumber) && Number.isFinite(rightNumber);
2534
+
2535
+ if (operator === 'contains') {
2536
+ return leftLower.includes(rightLower);
2537
+ }
2538
+ if (operator === 'starts') {
2539
+ return leftLower.startsWith(rightLower);
2540
+ }
2541
+ if (operator === 'ends') {
2542
+ return leftLower.endsWith(rightLower);
2543
+ }
2544
+ if (operator === 'neq') {
2545
+ if (hasNumbers) {
2546
+ return leftNumber !== rightNumber;
2547
+ }
2548
+ return leftLower !== rightLower;
2549
+ }
2550
+ if (operator === 'gt' && hasNumbers) {
2551
+ return leftNumber > rightNumber;
2552
+ }
2553
+ if (operator === 'gte' && hasNumbers) {
2554
+ return leftNumber >= rightNumber;
2555
+ }
2556
+ if (operator === 'lt' && hasNumbers) {
2557
+ return leftNumber < rightNumber;
2558
+ }
2559
+ if (operator === 'lte' && hasNumbers) {
2560
+ return leftNumber <= rightNumber;
2561
+ }
2562
+ return hasNumbers ? leftNumber === rightNumber : leftLower === rightLower;
2563
+ }
2564
+
2565
+ _compareSortableValues(left, right) {
2566
+ const leftNumber = this._extractNumber(left);
2567
+ const rightNumber = this._extractNumber(right);
2568
+
2569
+ const leftIsNumber = Number.isFinite(leftNumber);
2570
+ const rightIsNumber = Number.isFinite(rightNumber);
2571
+ if (leftIsNumber && rightIsNumber) {
2572
+ return leftNumber - rightNumber;
2573
+ }
2574
+
2575
+ const leftString = String(left || '').trim();
2576
+ const rightString = String(right || '').trim();
2577
+ return leftString.localeCompare(rightString, 'ru', {
2578
+ numeric: true,
2579
+ sensitivity: 'base',
2580
+ });
2581
+ }
2582
+
2583
+ _extractNumber(value) {
2584
+ const prepared = String(value || '')
2585
+ .replace(/\u00A0/g, '')
2586
+ .replace(/\s+/g, '')
2587
+ .replace(',', '.')
2588
+ .replace(/[^\d.-]/g, '');
2589
+ if (prepared === '' || prepared === '-' || prepared === '.') {
2590
+ return NaN;
2591
+ }
2592
+ return Number.parseFloat(prepared);
2593
+ }
2594
+
2595
+ _getInitialSortState() {
2596
+ const sortOptions = this._params.sortable || {};
2597
+ const requestOptions = this._params.request || {};
2598
+ const requestParams = requestOptions.params || {};
2599
+ const urlParams = this._getUrlParams();
2600
+ const remoteFieldParam = 'sort';
2601
+ const remoteDirParam = 'dir';
2602
+ const field = String(
2603
+ sortOptions.initialField
2604
+ || urlParams.get(remoteFieldParam)
2605
+ || requestParams[remoteFieldParam]
2606
+ || this._element.getAttribute('data-sort')
2607
+ || ''
2608
+ ).trim();
2609
+ const dirRaw = (
2610
+ sortOptions.initialDir
2611
+ || urlParams.get(remoteDirParam)
2612
+ || requestParams[remoteDirParam]
2613
+ || this._element.getAttribute('data-dir')
2614
+ || 'asc'
2615
+ );
2616
+ const dir = this._normalizeSortDir(dirRaw);
2617
+ const sorts = this._parseSortsFromStrings(field, dirRaw);
2618
+
2619
+ return {
2620
+ field,
2621
+ dir,
2622
+ columnIndex: sorts[0] && Number.isInteger(sorts[0].columnIndex) ? sorts[0].columnIndex : -1,
2623
+ sorts,
2624
+ };
2625
+ }
2626
+
2627
+ _parseSortsFromStrings(sortRaw, dirRaw) {
2628
+ const fields = String(sortRaw || '')
2629
+ .split(',')
2630
+ .map((item) => item.trim())
2631
+ .filter(Boolean);
2632
+ const dirs = String(dirRaw || '')
2633
+ .split(',')
2634
+ .map((item) => this._normalizeSortDir(item));
2635
+
2636
+ const sorts = [];
2637
+ fields.forEach((field, index) => {
2638
+ const columnIndex = this._findHeaderIndexByField(field);
2639
+ sorts.push({
2640
+ field,
2641
+ dir: dirs[index] || dirs[0] || 'asc',
2642
+ columnIndex,
2643
+ });
2644
+ });
2645
+ return this._getNormalizedSorts(sorts);
2646
+ }
2647
+
2648
+ _getNormalizedSorts(items) {
2649
+ const normalized = [];
2650
+ (Array.isArray(items) ? items : []).forEach((item) => {
2651
+ const field = String(item && item.field ? item.field : '').trim();
2652
+ const columnIndex = Number.isInteger(item && item.columnIndex)
2653
+ ? item.columnIndex
2654
+ : Number.parseInt(item && item.columnIndex, 10);
2655
+ const dir = this._normalizeSortDir(item && item.dir ? item.dir : 'asc');
2656
+
2657
+ if (!field && !Number.isInteger(columnIndex)) {
2658
+ return;
2659
+ }
2660
+
2661
+ const header = this._resolveHeaderForSort(field, columnIndex);
2662
+ if (!header) {
2663
+ return;
2664
+ }
2665
+ const resolvedColumnIndex = this._findHeaderIndex(header);
2666
+ if (resolvedColumnIndex < 0) {
2667
+ return;
2668
+ }
2669
+ if (!this._isHeaderSortableByConfig(header, resolvedColumnIndex, this._params.sortable || {})) {
2670
+ return;
2671
+ }
2672
+
2673
+ const resolvedField = String(field || header.getAttribute('data-field') || '').trim();
2674
+ if (this._isRemote && !resolvedField) {
2675
+ return;
2676
+ }
2677
+
2678
+ if (normalized.some((entry) => {
2679
+ if (resolvedField && entry.field) {
2680
+ return entry.field === resolvedField;
2681
+ }
2682
+ return entry.columnIndex === resolvedColumnIndex;
2683
+ })) {
2684
+ return;
2685
+ }
2686
+ normalized.push({
2687
+ field: resolvedField,
2688
+ dir,
2689
+ columnIndex: resolvedColumnIndex,
2690
+ });
2691
+ });
2692
+ return normalized;
2693
+ }
2694
+
2695
+ _isHeaderSortableByConfig(header, index, sortOptions = {}) {
2696
+ if (!header) {
2697
+ return false;
2698
+ }
2699
+
2700
+ const explicitAttr = header.getAttribute('data-sortable');
2701
+ if (explicitAttr !== null) {
2702
+ const normalized = String(explicitAttr).toLowerCase().trim();
2703
+ if (normalized === 'false' || normalized === '0') {
2704
+ return false;
2705
+ }
2706
+ }
2707
+
2708
+ const whitelist = this._getSortableFieldsWhitelist(sortOptions);
2709
+ if (whitelist && whitelist.size > 0) {
2710
+ const field = String(header.getAttribute('data-field') || '').trim();
2711
+ if (!field || !whitelist.has(field)) {
2712
+ return false;
2713
+ }
2714
+ }
2715
+
2716
+ if (this._isRemote) {
2717
+ return Boolean(String(header.getAttribute('data-field') || '').trim());
2718
+ }
2719
+
2720
+ return true;
2721
+ }
2722
+
2723
+ _getSortableFieldsWhitelist(sortOptions = {}) {
2724
+ const attrFields = this._element.getAttribute('data-sort-fields');
2725
+ const source = attrFields !== null ? attrFields : sortOptions.fields;
2726
+ const list = this._normalizeSortableFields(source);
2727
+ if (!list.length) {
2728
+ return null;
2729
+ }
2730
+ return new Set(list);
2731
+ }
2732
+
2733
+ _normalizeSortableFields(value) {
2734
+ if (Array.isArray(value)) {
2735
+ return value
2736
+ .map((item) => String(item || '').trim())
2737
+ .filter(Boolean);
2738
+ }
2739
+
2740
+ return String(value || '')
2741
+ .split(',')
2742
+ .map((item) => item.trim())
2743
+ .filter(Boolean);
2744
+ }
2745
+
2746
+ _getHeaderCells() {
2747
+ const head = this._element.tHead || this._element.querySelector('thead');
2748
+ if (!head || !head.rows || !head.rows.length) {
2749
+ return [];
2750
+ }
2751
+ return Array.from(head.rows[0].cells || []);
2752
+ }
2753
+
2754
+ _findHeaderIndex(header) {
2755
+ if (!header) {
2756
+ return -1;
2757
+ }
2758
+ const headers = this._getHeaderCells();
2759
+ return headers.indexOf(header);
2760
+ }
2761
+
2762
+ _findHeaderIndexByField(field) {
2763
+ const target = String(field || '').trim();
2764
+ if (!target) {
2765
+ return -1;
2766
+ }
2767
+ const headers = this._getHeaderCells();
2768
+ return headers.findIndex((header) => String(header.getAttribute('data-field') || '').trim() === target);
2769
+ }
2770
+
2771
+ _resolveHeaderForSort(field, columnIndex) {
2772
+ const headers = this._getHeaderCells();
2773
+ const normalizedField = String(field || '').trim();
2774
+ if (normalizedField) {
2775
+ const byField = headers.find((header) => String(header.getAttribute('data-field') || '').trim() === normalizedField);
2776
+ if (byField) {
2777
+ return byField;
2778
+ }
2779
+ }
2780
+
2781
+ if (Number.isInteger(columnIndex) && columnIndex >= 0 && columnIndex < headers.length) {
2782
+ return headers[columnIndex];
2783
+ }
2784
+
2785
+ return null;
2786
+ }
2787
+
2788
+ _getInitialPage() {
2789
+ const fromUrl = this._normalizePositiveInt(this._getUrlParams().get('page'), 0);
2790
+ if (fromUrl > 0) {
2791
+ return fromUrl;
2792
+ }
2793
+ const requestOptions = this._params.request || {};
2794
+ const requestParams = requestOptions.params || {};
2795
+ const fromRequest = this._normalizePositiveInt(requestParams.page, 0);
2796
+ if (fromRequest > 0) {
2797
+ return fromRequest;
2798
+ }
2799
+ const fromStorage = this._readStoredPage();
2800
+ if (fromStorage > 0) {
2801
+ return fromStorage;
2802
+ }
2803
+ return this._normalizePositiveInt(this._params.pagination.page, 1);
2804
+ }
2805
+
2806
+ _getInitialPerPage() {
2807
+ const fromUrl = this._normalizePositiveInt(this._getUrlParams().get('per_page'), 0);
2808
+ if (fromUrl > 0) {
2809
+ return this._clampPerPage(fromUrl, 10);
2810
+ }
2811
+ const requestOptions = this._params.request || {};
2812
+ const requestParams = requestOptions.params || {};
2813
+ const fromRequest = this._normalizePositiveInt(requestParams.per_page, 0);
2814
+ if (fromRequest > 0) {
2815
+ return this._clampPerPage(fromRequest, 10);
2816
+ }
2817
+ const fromStorage = this._readStoredPerPage();
2818
+ if (fromStorage > 0) {
2819
+ return this._clampPerPage(fromStorage, 10);
2820
+ }
2821
+ const configured = this._getConfiguredPerPage();
2822
+ if (configured > 0) {
2823
+ return this._clampPerPage(configured, 10);
2824
+ }
2825
+ return this._clampPerPage(this._params.pagination.perPage, 10);
2826
+ }
2827
+
2828
+ _getConfiguredPerPage() {
2829
+ const perPageAttr = this._element.getAttribute('data-pagination-per-page');
2830
+ const fromAttr = this._normalizePositiveInt(perPageAttr, 0);
2831
+ if (fromAttr > 0) {
2832
+ return fromAttr;
2833
+ }
2834
+ return this._normalizePositiveInt(this._params.pagination.perPage, 0);
2835
+ }
2836
+
2837
+ _getMaxPerPage() {
2838
+ const pagination = this._params.pagination || {};
2839
+ const maxPerPageAttr = this._element.getAttribute('data-pagination-max-per-page');
2840
+ const fromAttr = this._normalizePositiveInt(maxPerPageAttr, 0);
2841
+ if (fromAttr > 0) {
2842
+ return fromAttr;
2843
+ }
2844
+
2845
+ const fromOptions = this._normalizePositiveInt(pagination.maxPerPage, 0);
2846
+ if (fromOptions > 0) {
2847
+ return fromOptions;
2848
+ }
2849
+
2850
+ return 100;
2851
+ }
2852
+
2853
+ _clampPerPage(value, fallback = 10) {
2854
+ const normalized = this._normalizePositiveInt(value, fallback);
2855
+ const maxPerPage = this._getMaxPerPage();
2856
+ return Math.max(1, Math.min(normalized, maxPerPage));
2857
+ }
2858
+
2859
+ _getFiltersRoute() {
2860
+ const filterOptions = this._params.filters || {};
2861
+ const attrRoute = this._element.getAttribute('data-filters-route');
2862
+ return String(filterOptions.route || attrRoute || '').trim();
2863
+ }
2864
+
2865
+ _isPerPagePersistenceEnabled() {
2866
+ const pagination = this._params.pagination || {};
2867
+ const attr = this._element.getAttribute('data-pagination-persist-per-page');
2868
+ if (attr !== null) {
2869
+ const normalized = String(attr).toLowerCase().trim();
2870
+ return normalized !== 'false' && normalized !== '0';
2871
+ }
2872
+ if ((pagination.persist || {}).perPage === undefined || (pagination.persist || {}).perPage === null) {
2873
+ return true;
2874
+ }
2875
+ return Boolean((pagination.persist || {}).perPage);
2876
+ }
2877
+
2878
+ _isPagePersistenceEnabled() {
2879
+ const pagination = this._params.pagination || {};
2880
+ const attr = this._element.getAttribute('data-pagination-persist-page');
2881
+ if (attr !== null) {
2882
+ const normalized = String(attr).toLowerCase().trim();
2883
+ return normalized !== 'false' && normalized !== '0';
2884
+ }
2885
+ if ((pagination.persist || {}).page === undefined || (pagination.persist || {}).page === null) {
2886
+ return true;
2887
+ }
2888
+ return Boolean((pagination.persist || {}).page);
2889
+ }
2890
+
2891
+ _getPerPageStorageKey() {
2892
+ const pagination = this._params.pagination || {};
2893
+ const explicit = String(((pagination.storage || {}).key) || this._element.getAttribute('data-pagination-storage-key') || '').trim();
2894
+ if (explicit) {
2895
+ return explicit;
2896
+ }
2897
+ const tableId = String(this._element.id || '').trim();
2898
+ if (tableId) {
2899
+ return `vgdt:perPage:${tableId}`;
2900
+ }
2901
+ const route = String(this._getRoute() || '').trim();
2902
+ if (route) {
2903
+ return `vgdt:perPage:${route}`;
2904
+ }
2905
+ const dataToggle = String(this._element.getAttribute('data-vg-table') || '').trim();
2906
+ if (dataToggle) {
2907
+ return `vgdt:perPage:${window.location.pathname}:${dataToggle}`;
2908
+ }
2909
+ const tables = Array.from(document.querySelectorAll('[data-vg-table]'));
2910
+ const index = tables.indexOf(this._element);
2911
+ return `vgdt:perPage:${window.location.pathname}:${index >= 0 ? index : 0}`;
2912
+ }
2913
+
2914
+ _getPageStorageKey() {
2915
+ const pagination = this._params.pagination || {};
2916
+ const explicit = String((((pagination.pageStorage || {}).storage || {}).key) || this._element.getAttribute('data-pagination-page-storage-key') || '').trim();
2917
+ if (explicit) {
2918
+ return explicit;
2919
+ }
2920
+ return `${this._getPerPageStorageKey()}:page`;
2921
+ }
2922
+
2923
+ _readStoredPage() {
2924
+ if (!this._isPagePersistenceEnabled()) {
2925
+ return 0;
2926
+ }
2927
+ try {
2928
+ const raw = window.localStorage.getItem(this._getPageStorageKey());
2929
+ return this._normalizePositiveInt(raw, 0);
2930
+ } catch (error) {
2931
+ return 0;
2932
+ }
2933
+ }
2934
+
2935
+ _storePage(page) {
2936
+ if (!this._isPagePersistenceEnabled()) {
2937
+ return;
2938
+ }
2939
+ const normalized = this._normalizePositiveInt(page, 0);
2940
+ if (normalized <= 0) {
2941
+ return;
2942
+ }
2943
+ try {
2944
+ window.localStorage.setItem(this._getPageStorageKey(), String(normalized));
2945
+ } catch (error) {
2946
+ // Ignore storage errors.
2947
+ }
2948
+ }
2949
+
2950
+ _readStoredPerPage() {
2951
+ if (!this._isPerPagePersistenceEnabled()) {
2952
+ return 0;
2953
+ }
2954
+ try {
2955
+ const raw = window.localStorage.getItem(this._getPerPageStorageKey());
2956
+ return this._normalizePositiveInt(raw, 0);
2957
+ } catch (error) {
2958
+ return 0;
2959
+ }
2960
+ }
2961
+
2962
+ _storePerPage(perPage) {
2963
+ if (!this._isPerPagePersistenceEnabled()) {
2964
+ return;
2965
+ }
2966
+ const normalized = this._normalizePositiveInt(perPage, 0);
2967
+ if (normalized <= 0) {
2968
+ return;
2969
+ }
2970
+ try {
2971
+ window.localStorage.setItem(this._getPerPageStorageKey(), String(normalized));
2972
+ } catch (error) {
2973
+ // Ignore storage errors.
2974
+ }
2975
+ }
2976
+
2977
+ _normalizeSortDir(value) {
2978
+ const normalized = String(value)
2979
+ .split(',')[0]
2980
+ .toLowerCase()
2981
+ .trim();
2982
+ return normalized === 'desc' ? 'desc' : 'asc';
2983
+ }
2984
+
2985
+ _isPaginationEnabled() {
2986
+ const pagination = this._params.pagination || {};
2987
+ if (pagination.enable !== undefined && pagination.enable !== null) {
2988
+ const normalized = String(pagination.enable).toLowerCase().trim();
2989
+ if (normalized === 'true' || normalized === '1') {
2990
+ return true;
2991
+ }
2992
+ if (normalized === 'false' || normalized === '0') {
2993
+ return false;
2994
+ }
2995
+ return Boolean(pagination.enable);
2996
+ }
2997
+
2998
+ const explicitAttr = this._element.getAttribute('data-pagination-enable');
2999
+ if (explicitAttr !== null) {
3000
+ const normalized = String(explicitAttr).toLowerCase().trim();
3001
+ return normalized !== 'false' && normalized !== '0';
3002
+ }
3003
+
3004
+ return false;
3005
+ }
3006
+
3007
+ _isStickyHeaderEnabled() {
3008
+ const sticky = this._params.stickyHeader || {};
3009
+ const enableAttr = this._element.getAttribute('data-sticky-header-enable');
3010
+ if (enableAttr !== null) {
3011
+ const normalized = String(enableAttr).toLowerCase().trim();
3012
+ return normalized !== 'false' && normalized !== '0';
3013
+ }
3014
+
3015
+ if (sticky.enable !== undefined && sticky.enable !== null) {
3016
+ const normalized = String(sticky.enable).toLowerCase().trim();
3017
+ if (normalized === 'true' || normalized === '1') {
3018
+ return true;
3019
+ }
3020
+ if (normalized === 'false' || normalized === '0') {
3021
+ return false;
3022
+ }
3023
+ return Boolean(sticky.enable);
3024
+ }
3025
+
3026
+ return false;
3027
+ }
3028
+
3029
+ _isCloneStickyHeaderEnabled() {
3030
+ if (this._parent && this._parent.classList.contains('table-sticky')) {
3031
+ return true;
3032
+ }
3033
+ return Boolean(this._element && this._element.classList.contains('table-sticky'));
3034
+ }
3035
+
3036
+ _refreshStickyAndFixedLayout(options = {}) {
3037
+ const immediate = Boolean(options && options.immediate);
3038
+ const reason = String((options && options.reason) || '').trim();
3039
+ if (reason) {
3040
+ this._layoutRefreshReasons.push(reason);
3041
+ if (this._layoutRefreshReasons.length > 25) {
3042
+ this._layoutRefreshReasons.shift();
3043
+ }
3044
+ }
3045
+ if (immediate || typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') {
3046
+ if (this._layoutRefreshFrame && typeof window !== 'undefined' && typeof window.cancelAnimationFrame === 'function') {
3047
+ window.cancelAnimationFrame(this._layoutRefreshFrame);
3048
+ }
3049
+ this._layoutRefreshFrame = 0;
3050
+ this._layoutRefreshScheduled = false;
3051
+ this._refreshStickyAndFixedLayoutNow();
3052
+ return;
3053
+ }
3054
+ if (this._layoutRefreshScheduled) {
3055
+ return;
3056
+ }
3057
+ this._layoutRefreshScheduled = true;
3058
+ this._layoutRefreshFrame = window.requestAnimationFrame(() => {
3059
+ this._layoutRefreshFrame = 0;
3060
+ this._layoutRefreshScheduled = false;
3061
+ this._refreshStickyAndFixedLayoutNow();
3062
+ });
3063
+ }
3064
+
3065
+ _refreshStickyAndFixedLayoutNow() {
3066
+ this._layoutRefreshReasons = [];
3067
+ if (!this._element || !this._element.isConnected) {
3068
+ return;
3069
+ }
3070
+ this._applyFixedColumnsLayout();
3071
+ this._syncCloneStickyHeader();
3072
+ this._updatePanHintVisibility();
3073
+ }
3074
+
3075
+ _updateLayoutResizeBinding() {
3076
+ if (typeof window === 'undefined') {
3077
+ return;
3078
+ }
3079
+ const shouldListen = this._isCloneStickyHeaderEnabled() || this._hasFixedColumnsConfig();
3080
+ window.removeEventListener('resize', this._boundCloneStickyResize);
3081
+ this._teardownLayoutResizeObserver();
3082
+ if (shouldListen) {
3083
+ window.addEventListener('resize', this._boundCloneStickyResize);
3084
+ this._setupLayoutResizeObserver();
3085
+ }
3086
+ }
3087
+
3088
+ _setupLayoutResizeObserver() {
3089
+ if (typeof ResizeObserver === 'undefined') {
3090
+ return;
3091
+ }
3092
+
3093
+ if (!this._layoutResizeObserver) {
3094
+ this._layoutResizeObserver = new ResizeObserver(() => {
3095
+ this._boundCloneStickyResize();
3096
+ });
3097
+ }
3098
+
3099
+ this._getLayoutResizeTargets().forEach((target) => {
3100
+ this._layoutResizeObserver.observe(target);
3101
+ });
3102
+ }
3103
+
3104
+ _teardownLayoutResizeObserver() {
3105
+ if (!this._layoutResizeObserver) {
3106
+ return;
3107
+ }
3108
+ this._layoutResizeObserver.disconnect();
3109
+ }
3110
+
3111
+ _getLayoutResizeTargets() {
3112
+ const seen = new Set();
3113
+ const pushUnique = (target) => {
3114
+ if (!target || seen.has(target)) {
3115
+ return;
3116
+ }
3117
+ seen.add(target);
3118
+ };
3119
+
3120
+ pushUnique(this._parent);
3121
+ pushUnique(this._tableViewport);
3122
+
3123
+ return Array.from(seen);
3124
+ }
3125
+
3126
+ _getStickyHeaderOffsets() {
3127
+ const sticky = this._params.stickyHeader || {};
3128
+ const attrTop = this._element.getAttribute('data-sticky-header-top');
3129
+ const topRaw = attrTop !== null && String(attrTop).trim() !== '' ? attrTop : sticky.top;
3130
+ const top = Number.isFinite(Number.parseInt(topRaw, 10)) ? Number.parseInt(topRaw, 10) : 0;
3131
+ const attrMaxHeight = this._element.getAttribute('data-sticky-header-max');
3132
+ const maxHeightRaw = attrMaxHeight !== null && String(attrMaxHeight).trim() !== '' ? attrMaxHeight : sticky.max;
3133
+ const maxHeight = Number.isFinite(Number.parseInt(maxHeightRaw, 10)) ? Number.parseInt(maxHeightRaw, 10) : 0;
3134
+ return {
3135
+ top: Math.max(0, top),
3136
+ maxHeight: Math.max(0, maxHeight),
3137
+ };
3138
+ }
3139
+
3140
+ _getCloneStickyHeaderOffsets() {
3141
+ return {
3142
+ top: this._readCssSizeVar('--vgdt-sticky-top', 0),
3143
+ maxHeight: this._readCssSizeVar('--vgdt-sticky-max-height', 0),
3144
+ };
3145
+ }
3146
+
3147
+ _readCssSizeVar(name, fallback = 0) {
3148
+ const sources = [this._parent, this._element, this._tableViewport].filter(Boolean);
3149
+ for (let index = 0; index < sources.length; index += 1) {
3150
+ const node = sources[index];
3151
+ const value = window.getComputedStyle(node).getPropertyValue(name);
3152
+ const parsed = this._resolveCssLengthToPx(value, node);
3153
+ if (Number.isFinite(parsed)) {
3154
+ return Math.max(0, parsed);
3155
+ }
3156
+ }
3157
+ return Math.max(0, fallback);
3158
+ }
3159
+
3160
+ _resolveCssLengthToPx(value, referenceNode = null) {
3161
+ const raw = String(value || '').trim();
3162
+ if (!raw) {
3163
+ return NaN;
3164
+ }
3165
+
3166
+ const direct = Number.parseFloat(raw);
3167
+ if (Number.isFinite(direct)) {
3168
+ return direct;
3169
+ }
3170
+
3171
+ if (typeof document === 'undefined') {
3172
+ return NaN;
3173
+ }
3174
+
3175
+ const host = referenceNode instanceof Element
3176
+ ? referenceNode
3177
+ : (document.body || document.documentElement);
3178
+ if (!host) {
3179
+ return NaN;
3180
+ }
3181
+
3182
+ const probe = document.createElement('div');
3183
+ probe.style.position = 'absolute';
3184
+ probe.style.visibility = 'hidden';
3185
+ probe.style.pointerEvents = 'none';
3186
+ probe.style.left = '-99999px';
3187
+ probe.style.top = '0';
3188
+ probe.style.height = raw;
3189
+ host.appendChild(probe);
3190
+
3191
+ const computed = window.getComputedStyle(probe).height;
3192
+ host.removeChild(probe);
3193
+
3194
+ const resolved = Number.parseFloat(String(computed || '').trim());
3195
+ return Number.isFinite(resolved) ? resolved : NaN;
3196
+ }
3197
+
3198
+ _applyStickyHeader(enabled) {
3199
+ if (!this._element) {
3200
+ return;
3201
+ }
3202
+
3203
+ const offsets = this._getStickyHeaderOffsets();
3204
+
3205
+ if (enabled) {
3206
+ this._element.style.setProperty('--vgdt-sticky-top', `${offsets.top}px`);
3207
+ const scrollTarget = this._tableViewport || this._parent;
3208
+ if (scrollTarget) {
3209
+ if (offsets.maxHeight > 0) {
3210
+ scrollTarget.classList.add('table-sticky-scroll');
3211
+ scrollTarget.style.setProperty('--vgdt-sticky-max-height', `${offsets.maxHeight}px`);
3212
+ } else {
3213
+ scrollTarget.classList.remove('table-sticky-scroll');
3214
+ scrollTarget.style.removeProperty('--vgdt-sticky-max-height');
3215
+ }
3216
+ }
3217
+ return;
3218
+ }
3219
+
3220
+ this._element.style.removeProperty('--vgdt-sticky-top');
3221
+ const scrollTarget = this._tableViewport || this._parent;
3222
+ if (scrollTarget) {
3223
+ scrollTarget.classList.remove('table-sticky-scroll');
3224
+ scrollTarget.style.removeProperty('--vgdt-sticky-max-height');
3225
+ }
3226
+ }
3227
+
3228
+ _applyCloneStickyHeader(enabled) {
3229
+ const scrollTarget = this._tableViewport || this._parent;
3230
+ const state = this._cloneStickyState;
3231
+
3232
+ if (!enabled) {
3233
+ if (state && state.viewport && state.onScroll) {
3234
+ state.viewport.removeEventListener('scroll', state.onScroll);
3235
+ }
3236
+ if (state && state.headerWrap && state.headerWrap.parentElement) {
3237
+ state.headerWrap.parentElement.removeChild(state.headerWrap);
3238
+ }
3239
+ this._cloneStickyState = null;
3240
+ this._parent.style.removeProperty('--vgdt-sticky-clone-height');
3241
+ if (scrollTarget && !this._isStickyHeaderEnabled()) {
3242
+ scrollTarget.classList.remove('table-sticky-scroll');
3243
+ scrollTarget.style.removeProperty('--vgdt-sticky-max-height');
3244
+ }
3245
+ this._updateLayoutResizeBinding();
3246
+ return;
3247
+ }
3248
+
3249
+ if (!this._parent || !this._tableViewport || !this._element.tHead) {
3250
+ return;
3251
+ }
3252
+
3253
+ const offsets = this._getCloneStickyHeaderOffsets();
3254
+
3255
+ if (scrollTarget) {
3256
+ if (offsets.maxHeight > 0) {
3257
+ scrollTarget.classList.add('table-sticky-scroll');
3258
+ scrollTarget.style.setProperty('--vgdt-sticky-max-height', `${offsets.maxHeight}px`);
3259
+ } else {
3260
+ scrollTarget.classList.remove('table-sticky-scroll');
3261
+ scrollTarget.style.removeProperty('--vgdt-sticky-max-height');
3262
+ }
3263
+ }
3264
+
3265
+ if (!this._cloneStickyState) {
3266
+ const headerWrap = document.createElement('div');
3267
+ headerWrap.className = 'vgdt-sticky-clone';
3268
+ const cloneTable = document.createElement('table');
3269
+ cloneTable.className = 'vgdt-sticky-clone__table';
3270
+ if (this._element.classList.contains('table-sortable')) {
3271
+ cloneTable.classList.add('table-sortable');
3272
+ }
3273
+ const cloneHead = document.createElement('thead');
3274
+ cloneTable.appendChild(cloneHead);
3275
+ headerWrap.appendChild(cloneTable);
3276
+ this._parent.insertBefore(headerWrap, this._tableViewport);
3277
+
3278
+ const stateNode = {
3279
+ headerWrap,
3280
+ table: cloneTable,
3281
+ head: cloneHead,
3282
+ lastHeadHtml: '',
3283
+ cloneCells: [],
3284
+ viewport: this._tableViewport,
3285
+ onScroll: () => {
3286
+ this._syncCloneStickyScroll();
3287
+ },
3288
+ onClick: (event) => {
3289
+ this._forwardCloneStickyInteraction(event, 'click');
3290
+ },
3291
+ onChange: (event) => {
3292
+ this._forwardCloneStickyInteraction(event, 'change');
3293
+ },
3294
+ onDragStart: (event) => {
3295
+ this._forwardCloneStickyInteraction(event, 'dragstart');
3296
+ },
3297
+ onDragOver: (event) => {
3298
+ this._forwardCloneStickyInteraction(event, 'dragover');
3299
+ },
3300
+ onDrop: (event) => {
3301
+ this._forwardCloneStickyInteraction(event, 'drop');
3302
+ },
3303
+ onDragEnd: (event) => {
3304
+ this._forwardCloneStickyInteraction(event, 'dragend');
3305
+ },
3306
+ };
3307
+
3308
+ stateNode.viewport.addEventListener('scroll', stateNode.onScroll, { passive: true });
3309
+ stateNode.head.addEventListener('click', stateNode.onClick);
3310
+ stateNode.head.addEventListener('change', stateNode.onChange);
3311
+ stateNode.head.addEventListener('dragstart', stateNode.onDragStart);
3312
+ stateNode.head.addEventListener('dragover', stateNode.onDragOver);
3313
+ stateNode.head.addEventListener('drop', stateNode.onDrop);
3314
+ stateNode.head.addEventListener('dragend', stateNode.onDragEnd);
3315
+ this._cloneStickyState = stateNode;
3316
+ }
3317
+
3318
+ this._updateLayoutResizeBinding();
3319
+ this._syncCloneStickyHeader();
3320
+ }
3321
+
3322
+ _syncCloneStickyHeader() {
3323
+ const state = this._cloneStickyState;
3324
+ if (!state || !this._element || !this._element.tHead) {
3325
+ return;
3326
+ }
3327
+
3328
+ const sourceHead = this._element.tHead;
3329
+ const sourceRows = Array.from(sourceHead.rows || []);
3330
+ if (!sourceRows.length) {
3331
+ state.headerWrap.hidden = true;
3332
+ return;
3333
+ }
3334
+ if (this._element.classList.contains('table-sortable')) {
3335
+ state.table.classList.add('table-sortable');
3336
+ } else {
3337
+ state.table.classList.remove('table-sortable');
3338
+ }
3339
+ const hideUnsortedAttr = this._element.getAttribute('data-sort-hide-unsorted-arrows');
3340
+ if (hideUnsortedAttr !== null) {
3341
+ state.table.setAttribute('data-sort-hide-unsorted-arrows', hideUnsortedAttr);
3342
+ } else {
3343
+ state.table.removeAttribute('data-sort-hide-unsorted-arrows');
3344
+ }
3345
+
3346
+ let cloneHeadChanged = false;
3347
+ const nextHeadHtml = sourceHead.innerHTML;
3348
+ if (state.lastHeadHtml !== nextHeadHtml) {
3349
+ state.head.innerHTML = nextHeadHtml;
3350
+ this._sanitizeCloneStickyHeadIds(state.head);
3351
+ state.lastHeadHtml = nextHeadHtml;
3352
+ state.cloneCells = Array.from(state.head.querySelectorAll('th'));
3353
+ cloneHeadChanged = true;
3354
+ }
3355
+ const sourceCells = Array.from(sourceHead.querySelectorAll('th'));
3356
+ const cloneCells = Array.isArray(state.cloneCells) && state.cloneCells.length
3357
+ ? state.cloneCells
3358
+ : Array.from(state.head.querySelectorAll('th'));
3359
+ this._syncCloneStickySelectAllState(sourceHead, state.head);
3360
+ const sourceTableWidth = this._element.getBoundingClientRect().width;
3361
+ const viewportMetrics = this._getViewportWidthMetrics();
3362
+ const hasHorizontalOverflow = this._tableViewport
3363
+ ? this._tableViewport.scrollWidth > this._tableViewport.clientWidth + 1
3364
+ : false;
3365
+ let tableWidth = sourceTableWidth;
3366
+
3367
+ if (viewportMetrics) {
3368
+ tableWidth = (!hasHorizontalOverflow || viewportMetrics.hasVerticalOverflow)
3369
+ ? sourceTableWidth
3370
+ : Math.max(sourceTableWidth, viewportMetrics.effectiveWidth);
3371
+ }
3372
+
3373
+ const tableWidthCss = `${Math.max(0, tableWidth)}px`;
3374
+ if (state.table.style.width !== tableWidthCss) {
3375
+ state.table.style.width = tableWidthCss;
3376
+ }
3377
+ state.headerWrap.style.width = '100%';
3378
+ let totalCloneWidth = 0;
3379
+ sourceCells.forEach((cell, index) => {
3380
+ const cloneCell = cloneCells[index];
3381
+ if (!cloneCell) {
3382
+ return;
3383
+ }
3384
+ const width = cell.getBoundingClientRect().width;
3385
+ const safeWidth = Math.max(0, width);
3386
+ const widthCss = `${safeWidth}px`;
3387
+ if (cloneCell.style.width !== widthCss) {
3388
+ cloneCell.style.width = widthCss;
3389
+ }
3390
+ if (cloneCell.style.minWidth !== widthCss) {
3391
+ cloneCell.style.minWidth = widthCss;
3392
+ }
3393
+ if (cloneCell.style.maxWidth !== widthCss) {
3394
+ cloneCell.style.maxWidth = widthCss;
3395
+ }
3396
+ totalCloneWidth += safeWidth;
3397
+ });
3398
+
3399
+ if (hasHorizontalOverflow && cloneCells.length) {
3400
+ const gap = Math.max(0, tableWidth - totalCloneWidth);
3401
+ if (gap > 1) {
3402
+ const lastCell = cloneCells[cloneCells.length - 1];
3403
+ const current = lastCell.getBoundingClientRect().width;
3404
+ const next = Math.max(0, current + gap);
3405
+ lastCell.style.width = `${next}px`;
3406
+ lastCell.style.minWidth = `${next}px`;
3407
+ lastCell.style.maxWidth = `${next}px`;
3408
+ }
3409
+ }
3410
+
3411
+ state.headerWrap.hidden = false;
3412
+ const cloneHeight = state.headerWrap.getBoundingClientRect().height;
3413
+ this._parent.style.setProperty('--vgdt-sticky-clone-height', `${Math.max(0, cloneHeight)}px`);
3414
+ this._syncCloneStickyScroll();
3415
+ if (cloneHeadChanged && typeof this._rebuildFixedColumnsCellsCache === 'function') {
3416
+ this._rebuildFixedColumnsCellsCache();
3417
+ if (typeof this._syncFixedColumnsScroll === 'function') {
3418
+ this._syncFixedColumnsScroll({ immediate: true });
3419
+ }
3420
+ }
3421
+ }
3422
+
3423
+ _syncCloneStickySelectAllState(sourceHead, cloneHead) {
3424
+ if (!sourceHead || !cloneHead || typeof sourceHead.querySelector !== 'function' || typeof cloneHead.querySelector !== 'function') {
3425
+ return;
3426
+ }
3427
+ const sourceSelectAll = sourceHead.querySelector('[data-select-all]');
3428
+ const cloneSelectAll = cloneHead.querySelector('[data-select-all]');
3429
+ if (!sourceSelectAll || !cloneSelectAll) {
3430
+ return;
3431
+ }
3432
+ cloneSelectAll.checked = Boolean(sourceSelectAll.checked);
3433
+ cloneSelectAll.indeterminate = Boolean(sourceSelectAll.indeterminate);
3434
+ cloneSelectAll.disabled = Boolean(sourceSelectAll.disabled);
3435
+ }
3436
+
3437
+ _sanitizeCloneStickyHeadIds(head) {
3438
+ if (!head || typeof head.querySelectorAll !== 'function') {
3439
+ return;
3440
+ }
3441
+
3442
+ const nodesWithId = Array.from(head.querySelectorAll('[id]'));
3443
+ if (!nodesWithId.length) {
3444
+ return;
3445
+ }
3446
+
3447
+ const idMap = new Map();
3448
+ nodesWithId.forEach((node) => {
3449
+ const originalId = String(node.getAttribute('id') || '').trim();
3450
+ if (!originalId) {
3451
+ return;
3452
+ }
3453
+ const cloneId = `${originalId}--vgdt-clone`;
3454
+ node.setAttribute('id', cloneId);
3455
+ idMap.set(originalId, cloneId);
3456
+ });
3457
+
3458
+ if (!idMap.size) {
3459
+ return;
3460
+ }
3461
+
3462
+ const idRefAttrs = ['for', 'headers', 'aria-labelledby', 'aria-describedby', 'aria-controls', 'aria-owns', 'aria-details'];
3463
+ const allNodes = Array.from(head.querySelectorAll('*'));
3464
+ allNodes.forEach((node) => {
3465
+ idRefAttrs.forEach((attr) => {
3466
+ const value = String(node.getAttribute(attr) || '').trim();
3467
+ if (!value) {
3468
+ return;
3469
+ }
3470
+ const nextValue = value
3471
+ .split(/\s+/)
3472
+ .map((part) => idMap.get(part) || part)
3473
+ .join(' ');
3474
+ if (nextValue !== value) {
3475
+ node.setAttribute(attr, nextValue);
3476
+ }
3477
+ });
3478
+ });
3479
+ }
3480
+
3481
+ _syncCloneStickyScroll() {
3482
+ const state = this._cloneStickyState;
3483
+ if (!state || !state.viewport) {
3484
+ return;
3485
+ }
3486
+ const hasHorizontalOverflow = state.viewport.scrollWidth > state.viewport.clientWidth + 1;
3487
+ if (!hasHorizontalOverflow) {
3488
+ if (state.viewport.scrollLeft !== 0) {
3489
+ state.viewport.scrollLeft = 0;
3490
+ }
3491
+ state.table.style.transform = 'translateX(0px)';
3492
+ return;
3493
+ }
3494
+ const left = state.viewport.scrollLeft || 0;
3495
+ state.table.style.transform = `translateX(${-left}px)`;
3496
+ }
3497
+
3498
+ _getViewportWidthMetrics() {
3499
+ const viewport = this._tableViewport;
3500
+ if (!viewport) {
3501
+ return null;
3502
+ }
3503
+
3504
+ const clientWidth = viewport.clientWidth || 0;
3505
+ const clientHeight = viewport.clientHeight || 0;
3506
+ const hasVerticalOverflow = viewport.scrollHeight > clientHeight + 1;
3507
+ const effectiveWidth = clientWidth || viewport.offsetWidth || 0;
3508
+
3509
+ return {
3510
+ clientWidth,
3511
+ effectiveWidth,
3512
+ hasVerticalOverflow,
3513
+ };
3514
+ }
3515
+
3516
+ _forwardCloneStickyInteraction(event, type) {
3517
+ const state = this._cloneStickyState;
3518
+ if (!state || !this._element || !this._element.tHead || !event) {
3519
+ return;
3520
+ }
3521
+ const target = event.target instanceof Element ? event.target : null;
3522
+ if (!target) {
3523
+ return;
3524
+ }
3525
+ const cloneTh = target.closest('th');
3526
+ if (!cloneTh) {
3527
+ return;
3528
+ }
3529
+
3530
+ const cloneHeaders = Array.from(state.head.querySelectorAll('th'));
3531
+ const sourceHeaders = Array.from(this._element.tHead.querySelectorAll('th'));
3532
+ const index = cloneHeaders.indexOf(cloneTh);
3533
+ const sourceTh = index >= 0 ? sourceHeaders[index] : null;
3534
+ if (!sourceTh) {
3535
+ return;
3536
+ }
3537
+
3538
+ if (type === 'dragstart' || type === 'dragover' || type === 'drop' || type === 'dragend') {
3539
+ if (!this._columnReorderState.enabled) {
3540
+ return;
3541
+ }
3542
+
3543
+ if (type === 'dragstart') {
3544
+ const fromIndex = this._findHeaderIndex(sourceTh);
3545
+ if (fromIndex < 0 || this._isHeaderFixedForColumnReorder(sourceTh)) {
3546
+ return;
3547
+ }
3548
+ this._columnReorderState.fromIndex = fromIndex;
3549
+ this._columnReorderState.toIndex = fromIndex;
3550
+ this._columnReorderState.isDragging = true;
3551
+ this._element.classList.add('is-col-dragging');
3552
+ sourceTh.setAttribute('data-col-dragging', '1');
3553
+ cloneTh.setAttribute('data-col-dragging', '1');
3554
+
3555
+ if (event.dataTransfer) {
3556
+ event.dataTransfer.effectAllowed = 'move';
3557
+ try {
3558
+ event.dataTransfer.setData('text/plain', String(fromIndex));
3559
+ } catch (error) {
3560
+ // Ignore setData errors.
3561
+ }
3562
+ }
3563
+ return;
3564
+ }
3565
+
3566
+ if (type === 'dragover') {
3567
+ if (!this._columnReorderState.isDragging || this._isHeaderFixedForColumnReorder(sourceTh)) {
3568
+ return;
3569
+ }
3570
+ event.preventDefault();
3571
+ const toIndex = this._findHeaderIndex(sourceTh);
3572
+ if (toIndex < 0) {
3573
+ return;
3574
+ }
3575
+ this._columnReorderState.toIndex = toIndex;
3576
+ this._syncColumnDragOverMarker(toIndex);
3577
+ return;
3578
+ }
3579
+
3580
+ if (type === 'drop') {
3581
+ if (!this._columnReorderState.isDragging) {
3582
+ return;
3583
+ }
3584
+ event.preventDefault();
3585
+ if (this._isHeaderFixedForColumnReorder(sourceTh)) {
3586
+ this._resetColumnDragMarkers();
3587
+ return;
3588
+ }
3589
+ const toIndex = this._findHeaderIndex(sourceTh);
3590
+ if (toIndex < 0) {
3591
+ this._resetColumnDragMarkers();
3592
+ return;
3593
+ }
3594
+ const fromIndex = this._columnReorderState.fromIndex;
3595
+ this._resetColumnDragMarkers();
3596
+ this._finalizeColumnReorder(fromIndex, toIndex);
3597
+ return;
3598
+ }
3599
+
3600
+ this._resetColumnDragMarkers();
3601
+ return;
3602
+ }
3603
+
3604
+ if (type === 'change') {
3605
+ const clonedSelectAll = target.closest('[data-select-all]');
3606
+ if (!clonedSelectAll) {
3607
+ return;
3608
+ }
3609
+ const sourceSelectAll = sourceTh.querySelector('[data-select-all]');
3610
+ if (!sourceSelectAll) {
3611
+ return;
3612
+ }
3613
+ sourceSelectAll.checked = Boolean(clonedSelectAll.checked);
3614
+ sourceSelectAll.dispatchEvent(new Event('change', { bubbles: true }));
3615
+ this._syncCloneStickyHeader();
3616
+ return;
3617
+ }
3618
+
3619
+ if (type === 'click') {
3620
+ const clonedSelectAll = target.closest('[data-select-all]');
3621
+ if (clonedSelectAll) {
3622
+ return;
3623
+ }
3624
+ }
3625
+
3626
+ const clonedArrow = target.closest('[data-sort-arrow]');
3627
+ let clickTarget = sourceTh;
3628
+ if (clonedArrow) {
3629
+ const dir = clonedArrow.getAttribute('data-sort-arrow');
3630
+ const sourceArrow = sourceTh.querySelector(`[data-sort-arrow="${dir}"]`);
3631
+ if (sourceArrow) {
3632
+ clickTarget = sourceArrow;
3633
+ }
3634
+ } else {
3635
+ const clonedSelectAll = target.closest('[data-select-all]');
3636
+ if (clonedSelectAll) {
3637
+ const sourceSelectAll = sourceTh.querySelector('[data-select-all]');
3638
+ if (sourceSelectAll) {
3639
+ clickTarget = sourceSelectAll;
3640
+ }
3641
+ }
3642
+ }
3643
+
3644
+ clickTarget.dispatchEvent(new MouseEvent('click', {
3645
+ bubbles: true,
3646
+ cancelable: true,
3647
+ view: window,
3648
+ shiftKey: Boolean(event.shiftKey),
3649
+ ctrlKey: Boolean(event.ctrlKey),
3650
+ altKey: Boolean(event.altKey),
3651
+ metaKey: Boolean(event.metaKey),
3652
+ }));
3653
+ this._syncCloneStickyHeader();
3654
+ }
3655
+
3656
+ _renderRemoteRows(rows) {
3657
+ const body = this._getBody();
3658
+
3659
+ if (!rows.length) {
3660
+ const message = this._getTableMessage('stateEmpty', 'Ничего нет');
3661
+ this._renderStateRow(message, 'empty');
3662
+ return;
3663
+ }
3664
+
3665
+ this._setFixedColumnsSuppressed(false);
3666
+ this._clearStateMode();
3667
+ body.innerHTML = rows.map((row) => {
3668
+ const cells = this._fields.map((field) => {
3669
+ const rawValue = row && Object.prototype.hasOwnProperty.call(row, field) ? row[field] : '';
3670
+ const prepared = this._formatFieldValue(field, rawValue);
3671
+ return `<td>${this._escapeHtml(prepared)}</td>`;
3672
+ });
3673
+ return `<tr>${cells.join('')}</tr>`;
3674
+ }).join('');
3675
+ this._updatePanHintVisibility();
3676
+ }
3677
+
3678
+ _renderStateRow(message, state) {
3679
+ const body = this._getBody();
3680
+ const columns = this._getRenderedColumnsCount();
3681
+ const retryButton = state === 'error'
3682
+ ? `<div class="vgdt-state-actions"><button type="button" class="vgdt-state-reset" data-state-retry>${this._escapeHtml(this._getTableMessage('retry', 'Повторить'))}</button></div>`
3683
+ : '';
3684
+ const illustration = this._buildStateIllustrationMarkup(state);
3685
+ body.innerHTML = `<tr><td colspan="${columns}" data-table-state="${state}"><div class="vgdt-state">${illustration}<div class="vgdt-state-text">${this._escapeHtml(message)}</div>${retryButton}</div></td></tr>`;
3686
+ const stateCell = body.querySelector('[data-table-state]');
3687
+ this._setFixedColumnsSuppressed(true);
3688
+ this._setStateMode(state);
3689
+ this._ensureStateRowVisible(stateCell);
3690
+ this._announce(message);
3691
+ this._clearFooter();
3692
+ this._updatePanHintVisibility();
3693
+ this._refreshStickyAndFixedLayout();
3694
+ }
3695
+
3696
+ _scrollToPaginationTop() {
3697
+ if (!this._isPaginationScrollToTopEnabled()) {
3698
+ return;
3699
+ }
3700
+ if (this._tableViewport) {
3701
+ this._tableViewport.scrollTop = 0;
3702
+ this._tableViewport.scrollLeft = 0;
3703
+ }
3704
+ if (typeof window === 'undefined' || !this._parent) {
3705
+ return;
3706
+ }
3707
+
3708
+ const targetTop = this._getPaginationScrollTopTarget();
3709
+ const currentTop = window.pageYOffset || window.scrollY || 0;
3710
+ if (!Number.isFinite(targetTop)) {
3711
+ return;
3712
+ }
3713
+ if (Math.abs(targetTop - currentTop) < 2) {
3714
+ return;
3715
+ }
3716
+
3717
+ const prefersReducedMotion = typeof window.matchMedia === 'function'
3718
+ && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
3719
+
3720
+ window.scrollTo({
3721
+ top: Math.max(0, Math.round(targetTop)),
3722
+ behavior: prefersReducedMotion ? 'auto' : 'smooth',
3723
+ });
3724
+ }
3725
+
3726
+ _getPaginationScrollTopTarget() {
3727
+ if (this._isPaginationScrollToWindowTopEnabled()) {
3728
+ return 0;
3729
+ }
3730
+ const rect = this._parent.getBoundingClientRect();
3731
+ if (!Number.isFinite(rect.top)) {
3732
+ return NaN;
3733
+ }
3734
+ return rect.top + (window.pageYOffset || window.scrollY || 0);
3735
+ }
3736
+
3737
+ _isPaginationScrollToTopEnabled() {
3738
+ const pagination = this._params.pagination || {};
3739
+ const option = pagination.scrollToTop;
3740
+ if (option === undefined || option === null) {
3741
+ return false;
3742
+ }
3743
+ return Boolean(option);
3744
+ }
3745
+
3746
+ _isPaginationScrollToWindowTopEnabled() {
3747
+ const pagination = this._params.pagination || {};
3748
+ const option = pagination.scrollToWindowTop;
3749
+ if (option === undefined || option === null) {
3750
+ return false;
3751
+ }
3752
+ return Boolean(option);
3753
+ }
3754
+
3755
+ _getI18nSection(section) {
3756
+ const i18n = this._params.i18n && typeof this._params.i18n === 'object'
3757
+ ? this._params.i18n
3758
+ : {};
3759
+ const defaultI18n = DEFAULT_OPTIONS.i18n && typeof DEFAULT_OPTIONS.i18n === 'object'
3760
+ ? DEFAULT_OPTIONS.i18n
3761
+ : {};
3762
+ const localeRaw = String(this._params.locale || 'ru').toLowerCase();
3763
+ const locale = localeRaw.split('-')[0];
3764
+ const baseLocale = defaultI18n[localeRaw] || defaultI18n[locale] || defaultI18n.ru || {};
3765
+ const baseSection = baseLocale[section] && typeof baseLocale[section] === 'object' ? baseLocale[section] : {};
3766
+
3767
+ const localized = i18n[localeRaw] || i18n[locale];
3768
+ const localizedSection = localized && typeof localized === 'object' && localized[section] && typeof localized[section] === 'object'
3769
+ ? localized[section]
3770
+ : {};
3771
+
3772
+ return Object.assign({}, baseSection, localizedSection);
3773
+ }
3774
+
3775
+ _getTableMessage(key, fallback) {
3776
+ const tableI18n = this._getI18nSection('table');
3777
+ const value = tableI18n[key];
3778
+ return typeof value === 'string' && value.trim() ? value : fallback;
3779
+ }
3780
+
3781
+ async _waitMinLoadingDelay(startedAt) {
3782
+ const loadingOptions = this._params.loading || {};
3783
+ const minDelayMs = Math.max(0, Number.parseInt(loadingOptions.minDelay, 10) || 0);
3784
+ if (minDelayMs <= 0) {
3785
+ return;
3786
+ }
3787
+
3788
+ const elapsed = Date.now() - startedAt;
3789
+ const remaining = minDelayMs - elapsed;
3790
+ if (remaining <= 0) {
3791
+ return;
3792
+ }
3793
+
3794
+ await new Promise((resolve) => setTimeout(resolve, remaining));
3795
+ }
3796
+
3797
+ _ensureSummaryNode() {
3798
+ if (!this._isSummaryEnabled() || this._summaryNode || !this._parent) {
3799
+ return;
3800
+ }
3801
+ const node = document.createElement('div');
3802
+ node.className = 'vgdt-summary';
3803
+ node.hidden = true;
3804
+ const anchor = this._tableViewport && this._tableViewport.parentElement === this._parent
3805
+ ? this._tableViewport
3806
+ : this._element;
3807
+ this._parent.insertBefore(node, anchor);
3808
+ this._summaryNode = node;
3809
+ }
3810
+
3811
+ }
3812
+
3813
+ Object.assign(
3814
+ VGDynamicTable.prototype,
3815
+ fixedColumnsMethods,
3816
+ viewportMethods,
3817
+ summaryFooterMethods,
3818
+ skeletonMethods,
3819
+ tableRemoteMethods,
3820
+ tableUrlStateMethods,
3821
+ tableStateMethods
3822
+ );
3823
+
3824
+ EventHandler.on(document, 'DOMContentLoaded', () => {
3825
+ VGDynamicTable.initAll();
3826
+ });
3827
+
3828
+ export default VGDynamicTable;
3829
+