vgapp 1.5.5 → 1.5.7
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.
- package/app/modules/vgalert/js/vgalert.js +7 -4
- package/app/modules/vgfiles/js/base.js +25 -30
- package/app/modules/vgmodal/js/vgmodal.js +9 -3
- package/app/modules/vgnav/js/vgnav.js +62 -58
- package/app/modules/vgrangeslider/js/skins.js +8 -3
- package/app/modules/vgrangeslider/js/vgrangeslider.js +29 -17
- package/app/modules/vgrollup/js/vgrollup.js +15 -7
- package/app/modules/vgselect/js/handlers.js +6 -53
- package/app/modules/vgselect/js/vgselect.js +107 -63
- package/app/modules/vgselect/scss/vgselect.scss +1 -1
- package/app/modules/vgsidebar/js/vgsidebar.js +21 -10
- package/app/modules/vgspy/js/vgspy.js +43 -22
- package/app/modules/vgtable/js/_options.js +16 -5
- package/app/modules/vgtable/js/_pagination.js +118 -24
- package/app/modules/vgtable/js/_responsive.js +79 -0
- package/app/modules/vgtable/js/vgtable.js +38 -11
- package/app/modules/vgtabs/js/vgtabs.js +91 -66
- package/app/modules/vgtoast/js/vgtoast.js +11 -5
- package/app/modules/vgtooltip/js/vgtooltip.js +19 -9
- package/app/utils/js/components/file-icon.js +7 -2
- package/app/utils/js/components/responsive.js +165 -0
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/build/vgapp.js +1 -1
- package/build/vgapp.js.map +1 -1
- package/index.js +7 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Описание: локальная и remote-пагинация строк базовой таблицы VGTable.
|
|
3
|
-
* Возможности: выбор страницы и размера, серверная meta,
|
|
3
|
+
* Возможности: выбор страницы и размера, серверная meta, лимит кнопок, responsive-представление без запросов, многоточия, быстрый переход и сохранение.
|
|
4
4
|
*/
|
|
5
5
|
import EventHandler from "../../../utils/js/dom/event";
|
|
6
6
|
import VGDropdown from "../../vgdropdown";
|
|
@@ -16,7 +16,10 @@ class _pagination {
|
|
|
16
16
|
this._options = options;
|
|
17
17
|
this._host = table.closest('.vg-table-wrapper') || table.parentElement;
|
|
18
18
|
this._containers = [];
|
|
19
|
-
this._dropdowns = [];
|
|
19
|
+
this._dropdowns = [];
|
|
20
|
+
this._externalVisibility = new Map();
|
|
21
|
+
this._manageExternalVisibility = options.responsive === true;
|
|
22
|
+
this._updatingPresentation = false;
|
|
20
23
|
this._page = this._positiveInt(options.page, 1);
|
|
21
24
|
this._perPage = this._clampPerPage(options.per);
|
|
22
25
|
this._remote = options.remote === true;
|
|
@@ -29,20 +32,24 @@ class _pagination {
|
|
|
29
32
|
this._boundFocusOut = this._handleFocusOut.bind(this);
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
init() {
|
|
33
|
-
this._restoreState();
|
|
34
|
-
this._containers = this._resolveContainers();
|
|
35
|
-
this.
|
|
35
|
+
init() {
|
|
36
|
+
this._restoreState();
|
|
37
|
+
this._containers = this._resolveContainers();
|
|
38
|
+
this._bindContainers();
|
|
39
|
+
this.refresh();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_bindContainers() {
|
|
43
|
+
this._containers.forEach((container) => {
|
|
36
44
|
container.addEventListener('click', this._boundClick);
|
|
37
45
|
container.addEventListener('change', this._boundChange);
|
|
38
46
|
container.addEventListener('keydown', this._boundKeydown);
|
|
39
47
|
container.addEventListener('focusin', this._boundFocusIn);
|
|
40
48
|
container.addEventListener('focusout', this._boundFocusOut);
|
|
41
49
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
dispose() {
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_releaseContainers() {
|
|
46
53
|
this._disposeDropdowns();
|
|
47
54
|
this._containers.forEach((container) => {
|
|
48
55
|
container.removeEventListener('click', this._boundClick);
|
|
@@ -50,15 +57,69 @@ class _pagination {
|
|
|
50
57
|
container.removeEventListener('keydown', this._boundKeydown);
|
|
51
58
|
container.removeEventListener('focusin', this._boundFocusIn);
|
|
52
59
|
container.removeEventListener('focusout', this._boundFocusOut);
|
|
53
|
-
if (container.hasAttribute(GENERATED_ATTRIBUTE)) container.remove();
|
|
54
|
-
});
|
|
60
|
+
if (container.hasAttribute(GENERATED_ATTRIBUTE)) container.remove();
|
|
61
|
+
});
|
|
62
|
+
this._containers = [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dispose() {
|
|
66
|
+
this._releaseContainers();
|
|
67
|
+
this._externalVisibility.forEach((hidden, container) => { container.hidden = hidden; });
|
|
68
|
+
this._externalVisibility.clear();
|
|
55
69
|
this._rows().forEach((row) => {
|
|
56
70
|
row.hidden = row.getAttribute('data-vg-table-expand-hidden') === 'true'
|
|
57
71
|
|| row.hasAttribute('data-vg-table-filter-hidden');
|
|
58
72
|
row.removeAttribute('data-vg-table-page-row');
|
|
59
73
|
});
|
|
60
|
-
this._containers = [];
|
|
61
|
-
}
|
|
74
|
+
this._containers = [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Обновляет только панели: не трогает строки, page/per, storage, scroll или onChange. */
|
|
78
|
+
updatePresentation(options) {
|
|
79
|
+
const focus = this._captureFocus();
|
|
80
|
+
const position = this._position();
|
|
81
|
+
this._updatingPresentation = true;
|
|
82
|
+
try {
|
|
83
|
+
this._options = {...this._options, ...options};
|
|
84
|
+
if (position !== this._position()) {
|
|
85
|
+
this._manageExternalVisibility = true;
|
|
86
|
+
this._releaseContainers();
|
|
87
|
+
this._containers = this._resolveContainers();
|
|
88
|
+
this._bindContainers();
|
|
89
|
+
}
|
|
90
|
+
this._renderControls();
|
|
91
|
+
} finally {
|
|
92
|
+
this._updatingPresentation = false;
|
|
93
|
+
}
|
|
94
|
+
this._restoreFocus(focus);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_captureFocus() {
|
|
98
|
+
const active = this._table.ownerDocument.activeElement;
|
|
99
|
+
const container = this._containers.find((item) => item.contains(active));
|
|
100
|
+
if (!container) return null;
|
|
101
|
+
const attribute = ['data-pagination-page', 'data-pagination-per-page', 'data-pagination-quick-input', 'data-pagination-quick-button']
|
|
102
|
+
.find((name) => active.hasAttribute(name));
|
|
103
|
+
return {
|
|
104
|
+
position: this._positionFor(container), attribute, value: attribute ? active.getAttribute(attribute) : null,
|
|
105
|
+
inputValue: active.tagName === 'INPUT' ? active.value : null,
|
|
106
|
+
start: active.selectionStart, end: active.selectionEnd,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_restoreFocus(focus) {
|
|
111
|
+
if (!focus) return;
|
|
112
|
+
const container = this._containers.find((item) => this._positionFor(item) === focus.position) || this._containers[0];
|
|
113
|
+
if (!container) return;
|
|
114
|
+
const matching = focus.attribute ? Array.from(container.querySelectorAll(`[${focus.attribute}]`))
|
|
115
|
+
.find((item) => !item.disabled && item.getAttribute(focus.attribute) === focus.value) : null;
|
|
116
|
+
const target = matching || container.querySelector('[aria-current="page"]');
|
|
117
|
+
target?.focus({preventScroll: true});
|
|
118
|
+
if (matching && focus.inputValue !== null) {
|
|
119
|
+
matching.value = focus.inputValue;
|
|
120
|
+
if (focus.start !== null && focus.start !== undefined) matching.setSelectionRange(focus.start, focus.end);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
62
123
|
|
|
63
124
|
setPage(page, emit = false, source = 'api') {
|
|
64
125
|
const nextPage = this._clampPage(page);
|
|
@@ -131,7 +192,11 @@ class _pagination {
|
|
|
131
192
|
});
|
|
132
193
|
}
|
|
133
194
|
|
|
134
|
-
|
|
195
|
+
this._renderControls();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
_renderControls() {
|
|
199
|
+
const markup = this._buildMarkup();
|
|
135
200
|
this._disposeDropdowns();
|
|
136
201
|
this._containers.forEach((container) => {
|
|
137
202
|
container.className = `vg-table-pagination vg-table-pagination--${this._positionFor(container)}`;
|
|
@@ -221,8 +286,10 @@ class _pagination {
|
|
|
221
286
|
return `<svg class="vg-table-page__ellipsis-chevron vg-table-page__ellipsis-chevron--${direction}" viewBox="0 0 20 20" focusable="false" aria-hidden="true">${paths}</svg>`;
|
|
222
287
|
}
|
|
223
288
|
|
|
224
|
-
_buildPages() {
|
|
225
|
-
const total = this._totalPages();
|
|
289
|
+
_buildPages() {
|
|
290
|
+
const total = this._totalPages();
|
|
291
|
+
const maxButtons = this._options.maxButtons;
|
|
292
|
+
if (Number.isInteger(maxButtons) && maxButtons >= 3) return this._buildLimitedPages(total, maxButtons);
|
|
226
293
|
const visible = Math.max(1, this._positiveInt(this._options.visible, 5));
|
|
227
294
|
const threshold = Math.max(1, this._positiveInt(this._options.threshold, 5));
|
|
228
295
|
if (this._options.ellipsis === false || total <= threshold || total <= visible + 2) {
|
|
@@ -237,8 +304,30 @@ class _pagination {
|
|
|
237
304
|
for (let page = start; page <= end; page += 1) pages.push(page);
|
|
238
305
|
if (end < total - 1) pages.push({direction: 'next'});
|
|
239
306
|
pages.push(total);
|
|
240
|
-
return pages;
|
|
241
|
-
}
|
|
307
|
+
return pages;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
_buildLimitedPages(total, maximum) {
|
|
311
|
+
if (total <= maximum) return this._range(1, total);
|
|
312
|
+
if (maximum < 5 || this._options.ellipsis === false) {
|
|
313
|
+
const start = Math.max(1, Math.min(this._page - Math.floor(maximum / 2), total - maximum + 1));
|
|
314
|
+
return this._range(start, start + maximum - 1);
|
|
315
|
+
}
|
|
316
|
+
// Резервируем края и подбираем наибольшее окно, учитывая оба многоточия в лимите.
|
|
317
|
+
for (let count = maximum - 2; count >= 1; count -= 1) {
|
|
318
|
+
const start = Math.max(2, Math.min(this._page - Math.floor(count / 2), total - count));
|
|
319
|
+
const end = start + count - 1;
|
|
320
|
+
const pages = [1];
|
|
321
|
+
if (start === 3) pages.push(2);
|
|
322
|
+
else if (start > 3) pages.push({direction: 'prev'});
|
|
323
|
+
pages.push(...this._range(start, end));
|
|
324
|
+
if (end === total - 2) pages.push(total - 1);
|
|
325
|
+
else if (end < total - 2) pages.push({direction: 'next'});
|
|
326
|
+
pages.push(total);
|
|
327
|
+
if (pages.length <= maximum) return pages;
|
|
328
|
+
}
|
|
329
|
+
return [this._page];
|
|
330
|
+
}
|
|
242
331
|
|
|
243
332
|
_handleClick(event) {
|
|
244
333
|
const sizeOption = event.target.closest('[data-pagination-per-page-option]');
|
|
@@ -297,7 +386,8 @@ class _pagination {
|
|
|
297
386
|
if (input) input.value = String(this._perPage);
|
|
298
387
|
}
|
|
299
388
|
|
|
300
|
-
_handleFocusOut(event) {
|
|
389
|
+
_handleFocusOut(event) {
|
|
390
|
+
if (this._updatingPresentation) return;
|
|
301
391
|
const input = event.target.closest('[data-pagination-per-page]');
|
|
302
392
|
if (!input) return;
|
|
303
393
|
const related = event.relatedTarget instanceof Element ? event.relatedTarget : null;
|
|
@@ -323,10 +413,14 @@ class _pagination {
|
|
|
323
413
|
_resolveContainers() {
|
|
324
414
|
if (!this._host) return [];
|
|
325
415
|
const position = this._position();
|
|
326
|
-
const existing = Array.from(this._host.querySelectorAll('[data-vg-table-pagination]'));
|
|
327
|
-
if (existing.length) {
|
|
328
|
-
|
|
329
|
-
|
|
416
|
+
const existing = Array.from(this._host.querySelectorAll('[data-vg-table-pagination]'));
|
|
417
|
+
if (existing.length) {
|
|
418
|
+
const selected = position === 'both' ? existing : [existing.find((item) => item.getAttribute('data-position') === position) || existing[0]];
|
|
419
|
+
if (this._manageExternalVisibility) existing.forEach((container) => {
|
|
420
|
+
if (!this._externalVisibility.has(container)) this._externalVisibility.set(container, container.hidden);
|
|
421
|
+
container.hidden = !selected.includes(container);
|
|
422
|
+
});
|
|
423
|
+
return selected;
|
|
330
424
|
}
|
|
331
425
|
|
|
332
426
|
const positions = position === 'both' ? ['top', 'bottom'] : [position];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Описание: адаптер общего Responsive для представления VGTable.
|
|
3
|
+
* Возможности: отбор безопасных параметров пагинации и события таблицы; границы, наследование и подписки делегируются Responsive.
|
|
4
|
+
*/
|
|
5
|
+
import EventHandler from "../../../utils/js/dom/event";
|
|
6
|
+
import {Responsive} from "../../../utils/js/components/responsive";
|
|
7
|
+
|
|
8
|
+
const object = (value) => value && typeof value === 'object' && !Array.isArray(value);
|
|
9
|
+
|
|
10
|
+
// Только представление: page/per, callbacks, storage и параметры запросов не принимаются.
|
|
11
|
+
const paginationProfile = (value) => {
|
|
12
|
+
const result = {};
|
|
13
|
+
if (!object(value)) return result;
|
|
14
|
+
if (value.maxButtons === null || (Number.isInteger(value.maxButtons) && value.maxButtons >= 3)) result.maxButtons = value.maxButtons;
|
|
15
|
+
if (['left', 'center', 'right', 'between'].includes(value.align)) result.align = value.align;
|
|
16
|
+
if (['top', 'bottom', 'both'].includes(value.position)) result.position = value.position;
|
|
17
|
+
if (object(value.size)) {
|
|
18
|
+
result.size = {};
|
|
19
|
+
if (typeof value.size.enabled === 'boolean') result.size.enabled = value.size.enabled;
|
|
20
|
+
if (value.size.label === false || typeof value.size.label === 'string') result.size.label = value.size.label;
|
|
21
|
+
}
|
|
22
|
+
if (object(value.quick) && [true, false, 'auto'].includes(value.quick.enabled)) result.quick = {enabled: value.quick.enabled};
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
class _responsive {
|
|
27
|
+
constructor(table, options, onChange) {
|
|
28
|
+
this._table = table;
|
|
29
|
+
this._onChange = onChange;
|
|
30
|
+
this._responsive = new Responsive({breakpoints: options.breakpoints, window: table.ownerDocument.defaultView});
|
|
31
|
+
this._profiles = Object.fromEntries(Object.keys(this._responsive.breakpoints).map((name) => [name, paginationProfile(options[name]?.pagination)]));
|
|
32
|
+
this._breakpoint = null;
|
|
33
|
+
this._pagination = {};
|
|
34
|
+
this._width = 0;
|
|
35
|
+
this._valid = this._responsive.isValid();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
init() {
|
|
39
|
+
if (!this._valid) {
|
|
40
|
+
console.warn('VGTable: responsive.breakpoints must start at xs: 0 and increase through xxl.');
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
this.refresh();
|
|
44
|
+
this._responsive.subscribe(() => this.refresh());
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
refresh(force = false) {
|
|
49
|
+
if (!this._valid || !this._responsive) return this.getState();
|
|
50
|
+
this._width = this._responsive.viewport().width;
|
|
51
|
+
const breakpoint = this._responsive.getBreakpointKey(this._width);
|
|
52
|
+
if (!force && breakpoint === this._breakpoint) return this.getState();
|
|
53
|
+
const previous = this._breakpoint;
|
|
54
|
+
this._breakpoint = breakpoint;
|
|
55
|
+
this._pagination = this._responsive.resolve(this._profiles, {}, this._width);
|
|
56
|
+
this._onChange();
|
|
57
|
+
if (previous !== null && previous !== breakpoint) {
|
|
58
|
+
EventHandler.trigger(this._table, 'responsivechange.vg.table', {...this.getState(), previous});
|
|
59
|
+
}
|
|
60
|
+
return this.getState();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getState() {
|
|
64
|
+
return {
|
|
65
|
+
breakpoint: this._breakpoint,
|
|
66
|
+
width: this._responsive?.viewport().width ?? this._width,
|
|
67
|
+
reason: this._valid ? null : 'invalid-breakpoints',
|
|
68
|
+
pagination: {...this._pagination, size: {...this._pagination.size}, quick: {...this._pagination.quick}},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
dispose() {
|
|
73
|
+
this._width = this._responsive?.viewport().width ?? this._width;
|
|
74
|
+
this._responsive?.dispose();
|
|
75
|
+
this._responsive = null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default _responsive;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Описание: основной модуль базовых таблиц VGTable.
|
|
3
|
-
* Возможности: i18n, wrapper/container, состояния и URL state, local/remote, sticky,
|
|
3
|
+
* Возможности: i18n, wrapper/container, состояния и URL state, local/remote, sticky, колонки и строки, сортировка, дерево, responsive-пагинация, выбор и panning.
|
|
4
4
|
*/
|
|
5
5
|
import BaseModule from "../../base-module";
|
|
6
6
|
import {mergeDeepObject} from "../../../utils/js/functions";
|
|
@@ -9,7 +9,8 @@ import Selectors from "../../../utils/js/dom/selectors";
|
|
|
9
9
|
import _sorting from "./_sorting.js";
|
|
10
10
|
import _panning from "./_panning.js";
|
|
11
11
|
import _selection from "./_selection.js";
|
|
12
|
-
import _pagination from "./_pagination.js";
|
|
12
|
+
import _pagination from "./_pagination.js";
|
|
13
|
+
import _responsive from "./_responsive.js";
|
|
13
14
|
import _expandable from "./_expandable.js";
|
|
14
15
|
import _stickyHeader from "./_sticky-header.js";
|
|
15
16
|
import _fixedColumns from "./_fixed-columns.js";
|
|
@@ -63,7 +64,8 @@ class VGTable extends BaseModule {
|
|
|
63
64
|
this._panning = null;
|
|
64
65
|
this._selection = null;
|
|
65
66
|
this._expandable = null;
|
|
66
|
-
this._pagination = null;
|
|
67
|
+
this._pagination = null;
|
|
68
|
+
this._responsive = null;
|
|
67
69
|
this._filters = null;
|
|
68
70
|
this._search = null;
|
|
69
71
|
this._skeleton = null;
|
|
@@ -187,9 +189,17 @@ class VGTable extends BaseModule {
|
|
|
187
189
|
this._expandable.init();
|
|
188
190
|
}
|
|
189
191
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
if (!this._responsive && this._params.responsive?.enabled === true) {
|
|
193
|
+
this._responsive = new _responsive(this._element, this._params.responsive, () => {
|
|
194
|
+
this._pagination?.updatePresentation(this._paginationOptions());
|
|
195
|
+
});
|
|
196
|
+
this._responsive.init();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Включаем локальную или серверную пагинацию
|
|
200
|
+
if (!this._pagination && this._params.pagination.enabled === true) {
|
|
201
|
+
this._pagination = new _pagination(this._element, Object.assign({}, this._paginationOptions(), {
|
|
202
|
+
responsive: this._responsive?.getState().reason === null,
|
|
193
203
|
remote: this._isRemote,
|
|
194
204
|
onChange: (state) => this._handlePaginationChange(state),
|
|
195
205
|
}));
|
|
@@ -300,15 +310,27 @@ class VGTable extends BaseModule {
|
|
|
300
310
|
this._normalizeColumnsDataOptions();
|
|
301
311
|
this._normalizeRowReorderDataOptions();
|
|
302
312
|
if (this._remote) this._remote._options.labels = this._dictionary.remote || {};
|
|
303
|
-
this._pagination?.
|
|
313
|
+
this._pagination?.updatePresentation(this._paginationOptions());
|
|
304
314
|
this._expandable?.refresh?.();
|
|
305
315
|
this._columns?.refresh?.();
|
|
306
316
|
this._rowReorder?.refresh?.();
|
|
307
317
|
const state = this._states?.getState?.();
|
|
308
318
|
if (state) this._states.render(state.type);
|
|
309
319
|
EventHandler.trigger(this._element, 'localechange.vg.table', {locale: normalized});
|
|
310
|
-
return normalized;
|
|
311
|
-
}
|
|
320
|
+
return normalized;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Текущий брейкпоинт и накопленные переопределения; null при выключенном responsive. */
|
|
324
|
+
getResponsiveState() { return this._responsive?.getState() || null; }
|
|
325
|
+
|
|
326
|
+
/** Принудительно обновляет только адаптивное представление, без загрузки данных. */
|
|
327
|
+
refreshResponsive() { return this._responsive?.refresh(true) || null; }
|
|
328
|
+
|
|
329
|
+
_paginationOptions() {
|
|
330
|
+
const base = this._params.pagination;
|
|
331
|
+
const profile = this._responsive?.getState().pagination || {};
|
|
332
|
+
return {...base, ...profile, size: {...base.size, ...profile.size}, quick: {...base.quick, ...profile.quick}};
|
|
333
|
+
}
|
|
312
334
|
|
|
313
335
|
_mergeLocale(target, source) {
|
|
314
336
|
if (!target || !source || typeof source !== 'object') return target;
|
|
@@ -524,7 +546,11 @@ class VGTable extends BaseModule {
|
|
|
524
546
|
assign('ellipsis', (value) => { pagination.ellipsis = boolean(value); });
|
|
525
547
|
assign('ellipsis-hover', (value) => { pagination.ellipsisHover = boolean(value); });
|
|
526
548
|
assign('ellipsis-after', (value) => { pagination.threshold = number(value, pagination.threshold); });
|
|
527
|
-
assign('max-visible-pages', (value) => { pagination.visible = number(value, pagination.visible); });
|
|
549
|
+
assign('max-visible-pages', (value) => { pagination.visible = number(value, pagination.visible); });
|
|
550
|
+
assign('max-buttons', (value) => {
|
|
551
|
+
const parsed = Number(value);
|
|
552
|
+
pagination.maxButtons = Number.isInteger(parsed) && parsed >= 3 ? parsed : null;
|
|
553
|
+
});
|
|
528
554
|
assign('show-per-page', (value) => { pagination.size.enabled = boolean(value); });
|
|
529
555
|
assign('show-per-page-label', (value) => { pagination.size.label = boolean(value) ? 'Строк на странице' : false; });
|
|
530
556
|
assign('per-page-label', (value) => { pagination.size.label = value; });
|
|
@@ -1436,7 +1462,8 @@ class VGTable extends BaseModule {
|
|
|
1436
1462
|
/**
|
|
1437
1463
|
* Очистка ресурсов
|
|
1438
1464
|
*/
|
|
1439
|
-
dispose() {
|
|
1465
|
+
dispose() {
|
|
1466
|
+
this._responsive?.dispose();
|
|
1440
1467
|
if (this._remote) this._remote.dispose();
|
|
1441
1468
|
if (this._skeleton) this._skeleton.dispose();
|
|
1442
1469
|
if (this._urlState) this._urlState.dispose();
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Описание: вкладки VGTabs с декларативной и ручной инициализацией.
|
|
3
|
+
* Возможности: клавиатура, начальный hash, AJAX, отменяемые события и адаптивный индикатор.
|
|
4
|
+
*/
|
|
5
|
+
import BaseModule from "../../base-module";
|
|
2
6
|
import Selectors from "../../../utils/js/dom/selectors";
|
|
3
7
|
import EventHandler from "../../../utils/js/dom/event";
|
|
4
8
|
import {getNextActiveElement, isDisabled, mergeDeepObject} from "../../../utils/js/functions";
|
|
@@ -132,7 +136,7 @@ class VGTabs extends BaseModule {
|
|
|
132
136
|
once: true,
|
|
133
137
|
output: true,
|
|
134
138
|
},
|
|
135
|
-
},
|
|
139
|
+
}, params || {});
|
|
136
140
|
|
|
137
141
|
this._parent = this._element.closest(SELECTOR.TAB_PANEL);
|
|
138
142
|
this._main_parent = this._parent?.closest(SELECTOR.TAB_CLASS) || null;
|
|
@@ -173,19 +177,19 @@ class VGTabs extends BaseModule {
|
|
|
173
177
|
show() {
|
|
174
178
|
const innerElem = this._element;
|
|
175
179
|
|
|
176
|
-
if (this._elemIsActive(innerElem)) return;
|
|
180
|
+
if (!innerElem || isDisabled(innerElem) || this._elemIsActive(innerElem)) return;
|
|
177
181
|
|
|
178
182
|
const activeElem = this._getActiveElem();
|
|
179
|
-
const relatedTarget = innerElem;
|
|
180
183
|
|
|
181
184
|
// События hide и show
|
|
182
|
-
const hideEvent = activeElem ? EventHandler.trigger(activeElem, EVENT_HIDE, {relatedTarget}) : null;
|
|
183
|
-
const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, {relatedTarget});
|
|
185
|
+
const hideEvent = activeElem ? EventHandler.trigger(activeElem, EVENT_HIDE, {relatedTarget: innerElem}) : null;
|
|
186
|
+
const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, {relatedTarget: activeElem});
|
|
184
187
|
|
|
185
188
|
if (showEvent.defaultPrevented || (hideEvent && hideEvent.defaultPrevented)) return;
|
|
186
189
|
|
|
187
190
|
this._deactivate(activeElem, innerElem);
|
|
188
|
-
this._activate(innerElem,
|
|
191
|
+
this._activate(innerElem, activeElem);
|
|
192
|
+
this._updateSlider(innerElem);
|
|
189
193
|
}
|
|
190
194
|
|
|
191
195
|
/**
|
|
@@ -218,21 +222,28 @@ class VGTabs extends BaseModule {
|
|
|
218
222
|
const target = Selectors.getElementFromSelector(element);
|
|
219
223
|
if (target) this._activate(target, relatedTarget);
|
|
220
224
|
|
|
221
|
-
const complete = () => {
|
|
225
|
+
const complete = () => {
|
|
226
|
+
if (!this._element || !element.classList.contains(CLASS_NAME.ACTIVE)) return;
|
|
222
227
|
if (element.getAttribute('role') !== 'tab') {
|
|
223
228
|
element.classList.add(CLASS_NAME.SHOW);
|
|
224
229
|
return;
|
|
225
230
|
}
|
|
226
231
|
|
|
227
|
-
this.
|
|
228
|
-
|
|
229
|
-
|
|
232
|
+
if (this._params.ajax.route && !this._isLoaded && !this._isLoading) {
|
|
233
|
+
this._isLoading = true;
|
|
234
|
+
const loaded = (status, data) => {
|
|
235
|
+
if (!this._element) return;
|
|
236
|
+
this._isLoading = false;
|
|
237
|
+
EventHandler.trigger(this._element, EVENT_LOADED, { stats: status, data });
|
|
238
|
+
};
|
|
239
|
+
this._route(loaded, error => loaded('error', error));
|
|
240
|
+
}
|
|
230
241
|
|
|
231
242
|
element.removeAttribute('tabindex');
|
|
232
243
|
element.setAttribute('aria-selected', 'true');
|
|
233
244
|
this._toggleDropDown(element, true);
|
|
234
245
|
|
|
235
|
-
EventHandler.trigger(element, EVENT_SHOWN, { relatedTarget });
|
|
246
|
+
EventHandler.trigger(element, EVENT_SHOWN, { relatedTarget });
|
|
236
247
|
};
|
|
237
248
|
|
|
238
249
|
this._queueCallback(complete, element, element.classList.contains(CLASS_NAME.FADE));
|
|
@@ -252,7 +263,8 @@ class VGTabs extends BaseModule {
|
|
|
252
263
|
const target = Selectors.getElementFromSelector(element);
|
|
253
264
|
if (target) this._deactivate(target, relatedTarget);
|
|
254
265
|
|
|
255
|
-
const complete = () => {
|
|
266
|
+
const complete = () => {
|
|
267
|
+
if (!this._element || element.classList.contains(CLASS_NAME.ACTIVE)) return;
|
|
256
268
|
if (element.getAttribute('role') !== 'tab') {
|
|
257
269
|
element.classList.remove(CLASS_NAME.SHOW);
|
|
258
270
|
return;
|
|
@@ -300,13 +312,12 @@ class VGTabs extends BaseModule {
|
|
|
300
312
|
_setTabHash() {
|
|
301
313
|
if (!this._params.hash) return;
|
|
302
314
|
|
|
303
|
-
|
|
304
|
-
if (!
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
null;
|
|
315
|
+
let hash = document.location.hash;
|
|
316
|
+
if (!hash) return;
|
|
317
|
+
try { hash = decodeURIComponent(hash); } catch { return; }
|
|
318
|
+
const element = this._getChildren().find(child =>
|
|
319
|
+
!isDisabled(child) && (child.getAttribute('href') === hash || child.getAttribute('data-vg-target') === hash)
|
|
320
|
+
);
|
|
310
321
|
|
|
311
322
|
if (element) {
|
|
312
323
|
VGTabs.getOrCreateInstance(element).show();
|
|
@@ -316,8 +327,8 @@ class VGTabs extends BaseModule {
|
|
|
316
327
|
/**
|
|
317
328
|
* Инициализация слайдера-индикатора под вкладками
|
|
318
329
|
*/
|
|
319
|
-
_setInitialSlider() {
|
|
320
|
-
if (!this._params.slide) return;
|
|
330
|
+
_setInitialSlider() {
|
|
331
|
+
if (!this._params.slide || !this._main_parent) return;
|
|
321
332
|
|
|
322
333
|
let slider = Selectors.find(`.${CLASS_NAME.SLIDER}`, this._main_parent);
|
|
323
334
|
if (!slider) {
|
|
@@ -328,45 +339,58 @@ class VGTabs extends BaseModule {
|
|
|
328
339
|
|
|
329
340
|
this._main_parent.classList.add(CLASS_NAME.WITH_SLIDER);
|
|
330
341
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
342
|
+
this._updateSlider(this._getActiveElem());
|
|
343
|
+
this._sliderOver = event => {
|
|
344
|
+
const target = event.delegateTarget;
|
|
345
|
+
if (target.closest(SELECTOR.TAB_PANEL) === this._parent && !isDisabled(target)) this._updateSlider(target);
|
|
346
|
+
};
|
|
347
|
+
this._sliderOut = () => this._updateSlider(this._getActiveElem());
|
|
348
|
+
// Только один владелец общих обработчиков на группу вкладок.
|
|
349
|
+
const owner = this._getChildren().some(child => VGTabs.getInstance(child)?._sliderOver && child !== this._element);
|
|
350
|
+
if (owner) {
|
|
351
|
+
this._sliderOver = null;
|
|
352
|
+
this._sliderOut = null;
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
EventHandler.on(this._main_parent, EVENT_MOUSEOVER_DATA_API, SELECTOR.DATA_TOGGLE, this._sliderOver);
|
|
356
|
+
EventHandler.on(this._main_parent, EVENT_MOUSEOUT_DATA_API, SELECTOR.DATA_TOGGLE, this._sliderOut);
|
|
357
|
+
this._sliderResize = () => this._updateSlider(this._getActiveElem());
|
|
358
|
+
window.addEventListener('resize', this._sliderResize);
|
|
359
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
360
|
+
this._sliderObserver = new ResizeObserver(this._sliderResize);
|
|
361
|
+
this._sliderObserver.observe(this._parent);
|
|
362
|
+
this._getChildren().forEach(child => this._sliderObserver.observe(child));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
_updateSlider(target) {
|
|
367
|
+
if (!target || !this._main_parent) return;
|
|
368
|
+
const slider = this._main_parent.querySelector(`.${CLASS_NAME.SLIDER}`);
|
|
369
|
+
if (!slider) return;
|
|
370
|
+
this._getChildren().forEach(child => child.classList.toggle(CLASS_NAME.HOVER, child === target));
|
|
371
|
+
const {width, height} = window.getComputedStyle(target);
|
|
372
|
+
Object.assign(slider.style, {width, height, left: `${target.offsetLeft}px`, top: `${target.offsetTop}px`});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
dispose() {
|
|
376
|
+
if (!this._element) return;
|
|
377
|
+
const parent = this._main_parent;
|
|
378
|
+
const successor = this._sliderOver && this._getChildren()
|
|
379
|
+
.map(child => VGTabs.getInstance(child))
|
|
380
|
+
.find(instance => instance && instance !== this && instance._params.slide);
|
|
381
|
+
if (this._sliderOver) {
|
|
382
|
+
EventHandler.off(parent, EVENT_MOUSEOVER_DATA_API, SELECTOR.DATA_TOGGLE, this._sliderOver);
|
|
383
|
+
EventHandler.off(parent, EVENT_MOUSEOUT_DATA_API, SELECTOR.DATA_TOGGLE, this._sliderOut);
|
|
384
|
+
window.removeEventListener('resize', this._sliderResize);
|
|
385
|
+
this._sliderObserver?.disconnect();
|
|
386
|
+
}
|
|
387
|
+
super.dispose();
|
|
388
|
+
if (successor) successor._setInitialSlider();
|
|
389
|
+
else if (parent && !Selectors.findAll(SELECTOR.DATA_TOGGLE, parent).some(child => VGTabs.getInstance(child)?._params.slide)) {
|
|
390
|
+
parent.querySelector(`.${CLASS_NAME.SLIDER}`)?.remove();
|
|
391
|
+
parent.classList.remove(CLASS_NAME.WITH_SLIDER);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
370
394
|
|
|
371
395
|
/**
|
|
372
396
|
* Устанавливает базовые ARIA-атрибуты родителю
|
|
@@ -391,9 +415,9 @@ class VGTabs extends BaseModule {
|
|
|
391
415
|
if (outerElem !== child) {
|
|
392
416
|
this._setAttributeIfNotExists(outerElem, 'role', 'presentation');
|
|
393
417
|
}
|
|
394
|
-
if (!isActive) {
|
|
395
|
-
child.setAttribute('tabindex', '-1');
|
|
396
|
-
}
|
|
418
|
+
if (!isActive) {
|
|
419
|
+
child.setAttribute('tabindex', '-1');
|
|
420
|
+
} else child.removeAttribute('tabindex');
|
|
397
421
|
this._setAttributeIfNotExists(child, 'role', 'tab');
|
|
398
422
|
this._setInitialAttributesOnTargetPanel(child);
|
|
399
423
|
}
|
|
@@ -429,7 +453,8 @@ class VGTabs extends BaseModule {
|
|
|
429
453
|
* @returns {HTMLElement[]}
|
|
430
454
|
*/
|
|
431
455
|
_getChildren() {
|
|
432
|
-
return Selectors.findAll(SELECTOR.INNER_ELEM, this._parent)
|
|
456
|
+
return Selectors.findAll(SELECTOR.INNER_ELEM, this._parent)
|
|
457
|
+
.filter(child => child.closest(SELECTOR.TAB_PANEL) === this._parent);
|
|
433
458
|
}
|
|
434
459
|
|
|
435
460
|
/**
|
|
@@ -490,4 +515,4 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
|
|
|
490
515
|
});
|
|
491
516
|
});
|
|
492
517
|
|
|
493
|
-
export default VGTabs;
|
|
518
|
+
export default VGTabs;
|