vgapp 1.5.6 → 1.5.8

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 (109) hide show
  1. package/app/langs/en/buttons.json +17 -17
  2. package/app/langs/en/messages.json +36 -36
  3. package/app/langs/ru/buttons.json +17 -17
  4. package/app/langs/ru/messages.json +1 -1
  5. package/app/modules/vgalert/js/vgalert.js +6 -3
  6. package/app/modules/vgalert/scss/_variables.scss +16 -16
  7. package/app/modules/vgcollapse/scss/_variables.scss +7 -7
  8. package/app/modules/vgcollapse/scss/vgcollapse.scss +5 -5
  9. package/app/modules/vgdropdown/scss/_variables.scss +14 -14
  10. package/app/modules/vgdropdown/scss/vgdropdown.scss +5 -5
  11. package/app/modules/vgfilepreview/js/i18n.js +56 -56
  12. package/app/modules/vgfilepreview/js/renderers/image-modal.js +145 -145
  13. package/app/modules/vgfilepreview/js/renderers/image.js +92 -92
  14. package/app/modules/vgfilepreview/js/renderers/index.js +19 -19
  15. package/app/modules/vgfilepreview/js/renderers/office-modal.js +168 -168
  16. package/app/modules/vgfilepreview/js/renderers/office.js +79 -79
  17. package/app/modules/vgfilepreview/js/renderers/pdf-modal.js +260 -260
  18. package/app/modules/vgfilepreview/js/renderers/pdf.js +76 -76
  19. package/app/modules/vgfilepreview/js/renderers/playlist.js +71 -71
  20. package/app/modules/vgfilepreview/js/renderers/text-modal.js +343 -343
  21. package/app/modules/vgfilepreview/js/renderers/text.js +83 -83
  22. package/app/modules/vgfilepreview/js/renderers/zip-modal.js +522 -522
  23. package/app/modules/vgfilepreview/js/renderers/zip.js +89 -89
  24. package/app/modules/vgfilepreview/readme.md +68 -68
  25. package/app/modules/vgfiles/js/base.js +189 -194
  26. package/app/modules/vgfiles/js/droppable.js +17 -17
  27. package/app/modules/vgfiles/js/render.js +153 -153
  28. package/app/modules/vgfiles/js/sortable.js +52 -52
  29. package/app/modules/vgfiles/js/vgfiles.js +43 -43
  30. package/app/modules/vgfiles/readme.md +123 -123
  31. package/app/modules/vgfiles/scss/_mixins.scss +13 -13
  32. package/app/modules/vgfiles/scss/_variables.scss +10 -10
  33. package/app/modules/vgfiles/scss/vgfiles.scss +28 -28
  34. package/app/modules/vgformsender/js/hideshowpass.js +22 -22
  35. package/app/modules/vgformsender/readme.md +1 -1
  36. package/app/modules/vgformsender/scss/_variables.scss +5 -5
  37. package/app/modules/vgformsender/scss/vgformsender.scss +42 -42
  38. package/app/modules/vglawcookie/js/vglawcookie.js +19 -19
  39. package/app/modules/vglawcookie/scss/_variables.scss +13 -13
  40. package/app/modules/vglawcookie/scss/vglawcookie.scss +5 -5
  41. package/app/modules/vgmodal/js/vgmodal.interaction.js +216 -216
  42. package/app/modules/vgmodal/js/vgmodal.js +6 -0
  43. package/app/modules/vgmodal/js/vgmodal.minimized.js +227 -227
  44. package/app/modules/vgmodal/scss/_variables.scss +17 -17
  45. package/app/modules/vgmodal/scss/vgmodal.scss +77 -77
  46. package/app/modules/vgnav/js/vgnav.js +62 -62
  47. package/app/modules/vgnav/scss/_breakpoints.scss +36 -36
  48. package/app/modules/vgnav/scss/_hamburger.scss +5 -5
  49. package/app/modules/vgnav/scss/_placement.scss +5 -5
  50. package/app/modules/vgnav/scss/_toggle.scss +5 -5
  51. package/app/modules/vgnav/scss/_variables.scss +26 -26
  52. package/app/modules/vgnav/scss/vgnav.scss +41 -41
  53. package/app/modules/vgnestable/README.md +39 -39
  54. package/app/modules/vgrangeslider/js/skins.js +8 -8
  55. package/app/modules/vgrangeslider/js/vgrangeslider.js +29 -29
  56. package/app/modules/vgrangeslider/scss/_variables.scss +55 -55
  57. package/app/modules/vgrangeslider/scss/vgrangeslider.scss +239 -239
  58. package/app/modules/vgrollup/js/vgrollup.js +15 -15
  59. package/app/modules/vgrollup/scss/_variables.scss +15 -15
  60. package/app/modules/vgrollup/scss/vgrollup.scss +6 -6
  61. package/app/modules/vgselect/js/handlers.js +5 -52
  62. package/app/modules/vgselect/js/vgselect.js +128 -40
  63. package/app/modules/vgselect/readme.md +11 -11
  64. package/app/modules/vgselect/scss/_variables.scss +51 -51
  65. package/app/modules/vgselect/scss/vgselect.scss +1 -1
  66. package/app/modules/vgsidebar/js/vgsidebar.js +16 -5
  67. package/app/modules/vgsidebar/scss/_variables.scss +10 -10
  68. package/app/modules/vgsidebar/scss/vgsidebar.scss +5 -5
  69. package/app/modules/vgspy/js/vgspy.js +28 -7
  70. package/app/modules/vgtable/js/_filters.js +10 -6
  71. package/app/modules/vgtable/js/_options.js +16 -16
  72. package/app/modules/vgtable/js/_pagination.js +118 -118
  73. package/app/modules/vgtable/js/_responsive.js +79 -79
  74. package/app/modules/vgtable/js/_sticky-header.js +349 -280
  75. package/app/modules/vgtable/js/vgtable.js +98 -98
  76. package/app/modules/vgtable/scss/_sorting.scss +6 -6
  77. package/app/modules/vgtable/scss/_sticky-header.scss +31 -31
  78. package/app/modules/vgtable/scss/_variables.scss +6 -4
  79. package/app/modules/vgtable/scss/vgtable.scss +8 -4
  80. package/app/modules/vgtabs/js/vgtabs.js +82 -57
  81. package/app/modules/vgtabs/scss/_variables.scss +23 -23
  82. package/app/modules/vgtabs/scss/vgtabs.scss +5 -5
  83. package/app/modules/vgtoast/js/vgtoast.js +11 -5
  84. package/app/modules/vgtoast/scss/_variables.scss +13 -13
  85. package/app/modules/vgtoast/scss/vgtoast.scss +5 -5
  86. package/app/modules/vgtooltip/js/vgtooltip.js +121 -111
  87. package/app/modules/vgtooltip/scss/_variables.scss +10 -10
  88. package/app/modules/vgtooltip/scss/vgtooltip.scss +5 -5
  89. package/app/utils/js/components/audio-metadata.js +240 -240
  90. package/app/utils/js/components/backdrop.js +116 -116
  91. package/app/utils/js/components/file-icon.js +5 -0
  92. package/app/utils/js/components/file-preview.js +304 -304
  93. package/app/utils/js/components/responsive.js +165 -165
  94. package/app/utils/js/components/sanitize.js +150 -150
  95. package/app/utils/js/components/video-metadata.js +140 -140
  96. package/app/utils/scss/_backdrop.scss +5 -5
  97. package/app/utils/scss/_theme-default.scss +12 -12
  98. package/app/utils/scss/_theme.scss +7 -7
  99. package/app/utils/scss/default.scss +9 -9
  100. package/app/utils/scss/functions.scss +21 -21
  101. package/app/utils/scss/index.scss +4 -4
  102. package/app/utils/scss/mixin.scss +16 -16
  103. package/app/utils/scss/variables.scss +147 -147
  104. package/build/vgapp.css +1 -1
  105. package/build/vgapp.css.map +1 -1
  106. package/build/vgapp.js +1 -1
  107. package/build/vgapp.js.map +1 -1
  108. package/index.js +7 -7
  109. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
- @use "../../../utils/scss" as vg;
2
-
3
- $sidebar-map: (
1
+ @use "../../../utils/scss" as vg;
2
+
3
+ $sidebar-map: (
4
4
  horizontal: (
5
5
  width: 400px,
6
6
  height: 100vh,
@@ -9,13 +9,13 @@ $sidebar-map: (
9
9
  width: 100vw,
10
10
  height: 30vh,
11
11
  ),
12
- bg-color: var(--vg-surface-bg, #{vg.$surface-bg}),
13
- color: var(--vg-text-color, #{vg.$color}),
14
- box-shadow: var(--vg-box-shadow, #{vg.$box-shadow}),
12
+ bg-color: var(--vg-surface-bg, #{vg.$surface-bg}),
13
+ color: var(--vg-text-color, #{vg.$color}),
14
+ box-shadow: var(--vg-box-shadow, #{vg.$box-shadow}),
15
15
  header-height: 60px,
16
16
  footer-height: 70px,
17
17
  padding: 1rem,
18
- border: vg.$border-width vg.$border-style var(--vg-border-color, #{vg.$border-color}),
19
- z-index: vg.$sidebar-index,
20
- transition: vg.$transition-base,
21
- ) !default;
18
+ border: vg.$border-width vg.$border-style var(--vg-border-color, #{vg.$border-color}),
19
+ z-index: vg.$sidebar-index,
20
+ transition: vg.$transition-base,
21
+ ) !default;
@@ -6,12 +6,12 @@
6
6
  *--------------------------------------------------------------------------
7
7
  **/
8
8
 
9
- @forward "variables";
10
- @use "../../../utils/scss" as vg;
11
- @use "variables" as vars;
9
+ @forward "variables";
10
+ @use "../../../utils/scss" as vg;
11
+ @use "variables" as vars;
12
12
 
13
13
  .vg-sidebar {
14
- @include vg.mix-vars('sidebar', vars.$sidebar-map);
14
+ @include vg.mix-vars('sidebar', vars.$sidebar-map);
15
15
  position: fixed;
16
16
  width: auto;
17
17
  height: auto;
@@ -90,4 +90,4 @@
90
90
  padding: var(--vg-sidebar-padding);
91
91
  border-top: var(--vg-sidebar-border);
92
92
  }
93
- }
93
+ }
@@ -1,5 +1,9 @@
1
+ /**
2
+ * Описание: отслеживание активных секций и навигация по якорям.
3
+ * Возможности: Data API, нативная и виртуальная прокрутка, вложенные меню и обновление секций.
4
+ */
1
5
  import BaseModule from "../../base-module";
2
- import { mergeDeepObject, getElement, isDisabled, isVisible } from "../../../utils/js/functions";
6
+ import { getElement, isDisabled, isVisible, normalizeData } from "../../../utils/js/functions";
3
7
  import EventHandler from "../../../utils/js/dom/event";
4
8
  import Selectors from "../../../utils/js/dom/selectors";
5
9
 
@@ -52,7 +56,7 @@ class VGSpy extends BaseModule {
52
56
  * @property {number[]|string} threshold - пороги видимости (0.1, 0.5, 1)
53
57
  */
54
58
  this._params = this._configAfterMerge(
55
- mergeDeepObject(
59
+ Object.assign(
56
60
  {
57
61
  offset: null, // Устаревшее, для обратной совместимости
58
62
  rootMargin: '0px 0px -25%',
@@ -60,7 +64,8 @@ class VGSpy extends BaseModule {
60
64
  target: this._element,
61
65
  threshold: [0.1, 0.5, 1],
62
66
  },
63
- params
67
+ params,
68
+ this._getDataOptions()
64
69
  )
65
70
  );
66
71
 
@@ -134,10 +139,22 @@ class VGSpy extends BaseModule {
134
139
  return NAME_KEY;
135
140
  }
136
141
 
142
+ _getDataOptions() {
143
+ const options = {};
144
+ for (const name of ['target', 'offset', 'rootMargin', 'smoothScroll', 'threshold']) {
145
+ const attribute = `data-${name.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)}`;
146
+ if (this._element.hasAttribute(attribute)) {
147
+ options[name] = normalizeData(this._element.getAttribute(attribute));
148
+ }
149
+ }
150
+ return options;
151
+ }
152
+
137
153
  /**
138
154
  * Инициализирует или перезапускает модуль: находит ссылки и секции, создаёт observer
139
155
  */
140
156
  refresh() {
157
+ this._process(null);
141
158
  this._initializeTargetsAndObservables();
142
159
  this._updateRootElement();
143
160
  this._maybeEnableSmoothScroll();
@@ -164,7 +181,7 @@ class VGSpy extends BaseModule {
164
181
  // Smooth Scrollbar can be initialized after the spy; retry once on the next tick.
165
182
  if (this._rootElement && !this._isScrollableSelf(this._rootElement) && window.Scrollbar) {
166
183
  setTimeout(() => {
167
- if (this._scrollbar) return;
184
+ if (!this._element || this._scrollbar) return;
168
185
  this._updateSmoothScrollbar();
169
186
  if (this._scrollbar) this._setupScrollbarTracking();
170
187
  }, 0);
@@ -357,6 +374,8 @@ class VGSpy extends BaseModule {
357
374
  }
358
375
 
359
376
  dispose() {
377
+ if (!this._element) return;
378
+ this._process(null);
360
379
  if (this._observer) {
361
380
  this._observer.disconnect();
362
381
  }
@@ -416,6 +435,7 @@ class VGSpy extends BaseModule {
416
435
  // don't hijack the click; allow UI to reveal it, then retry.
417
436
  if (!isVisible(section)) {
418
437
  setTimeout(() => {
438
+ if (!this._element) return;
419
439
  const revealed = Selectors.findID(id);
420
440
  if (!revealed || !isVisible(revealed)) return;
421
441
 
@@ -507,7 +527,7 @@ class VGSpy extends BaseModule {
507
527
 
508
528
  for (const entry of entries) {
509
529
  if (!entry.isIntersecting) {
510
- this._clearActiveClass(getTargetLink(entry));
530
+ if (getTargetLink(entry) === this._activeTarget) this._process(null);
511
531
  continue;
512
532
  }
513
533
 
@@ -516,7 +536,7 @@ class VGSpy extends BaseModule {
516
536
  const shouldActivate =
517
537
  (userScrollsDown && isEntryBelow) || (!userScrollsDown && !isEntryBelow);
518
538
 
519
- if (shouldActivate) {
539
+ if (shouldActivate || !this._activeTarget) {
520
540
  this._previousScrollData.visibleEntryTop = entryTop;
521
541
  this._process(getTargetLink(entry));
522
542
  }
@@ -595,7 +615,7 @@ class VGSpy extends BaseModule {
595
615
  parent.classList.remove(CLASS_NAME_ACTIVE);
596
616
 
597
617
  const activeLinks = Selectors.findAll(
598
- `[href].${CLASS_NAME_ACTIVE}, [data-vg-target].${CLASS_NAME_ACTIVE}`,
618
+ `[href].${CLASS_NAME_ACTIVE}, [data-vg-target].${CLASS_NAME_ACTIVE}, ${SELECTOR_DROPDOWN_TOGGLE}.${CLASS_NAME_ACTIVE}`,
599
619
  parent
600
620
  );
601
621
  for (const link of activeLinks) {
@@ -605,6 +625,7 @@ class VGSpy extends BaseModule {
605
625
 
606
626
  _getTargetIdFromTrigger(trigger) {
607
627
  if (!trigger || typeof trigger.getAttribute !== 'function') return null;
628
+ if (isDisabled(trigger) || trigger.matches(SELECTOR_DROPDOWN_TOGGLE)) return null;
608
629
 
609
630
  const dataTarget = (trigger.getAttribute('data-vg-target') || '').trim();
610
631
  const href = (trigger.getAttribute('href') || '').trim();
@@ -2,7 +2,8 @@
2
2
  * Описание: форма локальных и remote-фильтров VGTable.
3
3
  * Возможности: auto/manual применение, debounce, операторы, reset и публичное состояние.
4
4
  */
5
- import EventHandler from "../../../utils/js/dom/event";
5
+ import EventHandler from "../../../utils/js/dom/event";
6
+ import VGSelect from "../../vgselect/js/vgselect";
6
7
 
7
8
  const EVENT_CHANGE = 'filterschange.vg.table';
8
9
 
@@ -112,11 +113,14 @@ class _filters {
112
113
  return this.getState();
113
114
  }
114
115
 
115
- reset(options = {}) {
116
- this._controls.forEach((control) => this._resetControl(control));
117
- if (options.emit !== false) this._emit(String(options.source || 'reset'));
118
- return this.getState();
119
- }
116
+ reset(options = {}) {
117
+ this._controls.forEach((control) => {
118
+ this._resetControl(control);
119
+ if (control.tagName === 'SELECT') VGSelect.updateUI(control);
120
+ });
121
+ if (options.emit !== false) this._emit(String(options.source || 'reset'));
122
+ return this.getState();
123
+ }
120
124
 
121
125
  _handleInput(event) {
122
126
  if (!this._isControl(event.target) || this._isManual()) return;
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Описание: константы и параметры по умолчанию базовой таблицы VGTable.
3
- * Возможности: единая настройка Data API, i18n, wrapper, remote, фильтров, sticky, колонок, строк, выбора и адаптивной пагинации.
4
- */
3
+ * Возможности: единая настройка Data API, i18n, wrapper, remote, фильтров, sticky, колонок, строк, выбора и адаптивной пагинации.
4
+ */
5
5
 
6
6
  /**
7
7
  * Служебные константы модуля.
8
8
  */
9
9
  // Имя компонента в публичном API. Варианты: фиксированное значение 'table'.
10
- const NAME = 'table';
10
+ const NAME = 'table';
11
11
  // Ключ экземпляра в Data API. Варианты: фиксированное значение 'vg.table'.
12
12
  const NAME_KEY = `vg.${NAME}`;
13
13
  // Селектор автоматической инициализации. Варианты: элементы с атрибутом data-vg-table.
@@ -21,16 +21,16 @@ const TABLE_CONTAINER_SELECTOR = '.vg-table-container';
21
21
  // Маркер container, созданного компонентом. Варианты: пустой data-атрибут.
22
22
  const GENERATED_TABLE_CONTAINER_ATTRIBUTE = 'data-vg-table-generated-container';
23
23
 
24
- const DEFAULT_OPTIONS = {
25
- /** Профили представления: базовые параметры + все достигнутые границы xs–xxl. */
26
- responsive: {
27
- // Включается явно; Data API: data-responsive-enabled.
28
- enabled: false,
29
- // Локальные переопределения границ общего Responsive; пустой объект сохраняет глобальные значения.
30
- breakpoints: {},
31
- // Профили принимают pagination: maxButtons, align, position, size.enabled/label, quick.enabled.
32
- xs: {}, sm: {}, md: {}, lg: {}, xl: {}, xxl: {},
33
- },
24
+ const DEFAULT_OPTIONS = {
25
+ /** Профили представления: базовые параметры + все достигнутые границы xs–xxl. */
26
+ responsive: {
27
+ // Включается явно; Data API: data-responsive-enabled.
28
+ enabled: false,
29
+ // Локальные переопределения границ общего Responsive; пустой объект сохраняет глобальные значения.
30
+ breakpoints: {},
31
+ // Профили принимают pagination: maxButtons, align, position, size.enabled/label, quick.enabled.
32
+ xs: {}, sm: {}, md: {}, lg: {}, xl: {}, xxl: {},
33
+ },
34
34
  // Активная локаль встроенного интерфейса. Data API: data-locale. Варианты: 'ru' | 'en' | региональный BCP 47 код.
35
35
  locale: 'ru',
36
36
  // Пользовательские словари, объединяемые со встроенными ru/en. Data API отсутствует; варианты: объект {locale: {group: {key: value}}}.
@@ -368,9 +368,9 @@ const DEFAULT_OPTIONS = {
368
368
  // Задаёт число страниц, после которого разрешены многоточие и quick='auto'. Варианты: целое число >= 1.
369
369
  threshold: 5,
370
370
  // Задаёт максимальное число соседних страниц между первой и последней. Варианты: целое число >= 1.
371
- visible: 5,
372
- // Лимит номеров и многоточий без prev/next; null сохраняет visible/threshold. Data API: data-pagination-max-buttons.
373
- maxButtons: null,
371
+ visible: 5,
372
+ // Лимит номеров и многоточий без prev/next; null сохраняет visible/threshold. Data API: data-pagination-max-buttons.
373
+ maxButtons: null,
374
374
 
375
375
  /** Поле и VGDropdown для выбора количества строк. */
376
376
  size: {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Описание: локальная и remote-пагинация строк базовой таблицы VGTable.
3
- * Возможности: выбор страницы и размера, серверная meta, лимит кнопок, responsive-представление без запросов, многоточия, быстрый переход и сохранение.
3
+ * Возможности: выбор страницы и размера, серверная meta, лимит кнопок, responsive-представление без запросов, многоточия, быстрый переход и сохранение.
4
4
  */
5
5
  import EventHandler from "../../../utils/js/dom/event";
6
6
  import VGDropdown from "../../vgdropdown";
@@ -16,10 +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 = [];
20
- this._externalVisibility = new Map();
21
- this._manageExternalVisibility = options.responsive === true;
22
- this._updatingPresentation = false;
19
+ this._dropdowns = [];
20
+ this._externalVisibility = new Map();
21
+ this._manageExternalVisibility = options.responsive === true;
22
+ this._updatingPresentation = false;
23
23
  this._page = this._positiveInt(options.page, 1);
24
24
  this._perPage = this._clampPerPage(options.per);
25
25
  this._remote = options.remote === true;
@@ -32,24 +32,24 @@ class _pagination {
32
32
  this._boundFocusOut = this._handleFocusOut.bind(this);
33
33
  }
34
34
 
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) => {
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) => {
44
44
  container.addEventListener('click', this._boundClick);
45
45
  container.addEventListener('change', this._boundChange);
46
46
  container.addEventListener('keydown', this._boundKeydown);
47
47
  container.addEventListener('focusin', this._boundFocusIn);
48
48
  container.addEventListener('focusout', this._boundFocusOut);
49
49
  });
50
- }
51
-
52
- _releaseContainers() {
50
+ }
51
+
52
+ _releaseContainers() {
53
53
  this._disposeDropdowns();
54
54
  this._containers.forEach((container) => {
55
55
  container.removeEventListener('click', this._boundClick);
@@ -57,69 +57,69 @@ class _pagination {
57
57
  container.removeEventListener('keydown', this._boundKeydown);
58
58
  container.removeEventListener('focusin', this._boundFocusIn);
59
59
  container.removeEventListener('focusout', this._boundFocusOut);
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();
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();
69
69
  this._rows().forEach((row) => {
70
70
  row.hidden = row.getAttribute('data-vg-table-expand-hidden') === 'true'
71
71
  || row.hasAttribute('data-vg-table-filter-hidden');
72
72
  row.removeAttribute('data-vg-table-page-row');
73
73
  });
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
- }
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
+ }
123
123
 
124
124
  setPage(page, emit = false, source = 'api') {
125
125
  const nextPage = this._clampPage(page);
@@ -192,11 +192,11 @@ class _pagination {
192
192
  });
193
193
  }
194
194
 
195
- this._renderControls();
196
- }
197
-
198
- _renderControls() {
199
- const markup = this._buildMarkup();
195
+ this._renderControls();
196
+ }
197
+
198
+ _renderControls() {
199
+ const markup = this._buildMarkup();
200
200
  this._disposeDropdowns();
201
201
  this._containers.forEach((container) => {
202
202
  container.className = `vg-table-pagination vg-table-pagination--${this._positionFor(container)}`;
@@ -286,10 +286,10 @@ class _pagination {
286
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>`;
287
287
  }
288
288
 
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);
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);
293
293
  const visible = Math.max(1, this._positiveInt(this._options.visible, 5));
294
294
  const threshold = Math.max(1, this._positiveInt(this._options.threshold, 5));
295
295
  if (this._options.ellipsis === false || total <= threshold || total <= visible + 2) {
@@ -304,30 +304,30 @@ class _pagination {
304
304
  for (let page = start; page <= end; page += 1) pages.push(page);
305
305
  if (end < total - 1) pages.push({direction: 'next'});
306
306
  pages.push(total);
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
- }
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
+ }
331
331
 
332
332
  _handleClick(event) {
333
333
  const sizeOption = event.target.closest('[data-pagination-per-page-option]');
@@ -386,8 +386,8 @@ class _pagination {
386
386
  if (input) input.value = String(this._perPage);
387
387
  }
388
388
 
389
- _handleFocusOut(event) {
390
- if (this._updatingPresentation) return;
389
+ _handleFocusOut(event) {
390
+ if (this._updatingPresentation) return;
391
391
  const input = event.target.closest('[data-pagination-per-page]');
392
392
  if (!input) return;
393
393
  const related = event.relatedTarget instanceof Element ? event.relatedTarget : null;
@@ -413,14 +413,14 @@ class _pagination {
413
413
  _resolveContainers() {
414
414
  if (!this._host) return [];
415
415
  const position = this._position();
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;
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;
424
424
  }
425
425
 
426
426
  const positions = position === 'both' ? ['top', 'bottom'] : [position];