vgapp 0.8.0 → 0.8.2

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/CHANGELOG.md CHANGED
@@ -1,4 +1,19 @@
1
- # VEGAS-APP 0.8.0 (Декабрь, 31, 2025)
1
+ # VEGAS-APP 0.8.2 (Январь, 4, 2025)
2
+ * Оптимизирован модуль VGRollup, см. файл readme.md
3
+ * Оптимизирован модуль VGSidebar, см. файл readme.md
4
+ * Оптимизирован модуль VGTabs, см. файл readme.md
5
+ * Оптимизирован модуль VGSpy, см. файл readme.md
6
+ * Оптимизирован модуль VGToast, см. файл readme.md
7
+ * Оптимизирован и дополнен модуль VGSelect, см. файл readme.md
8
+
9
+ ---
10
+
11
+ # VEGAS-APP 0.8.1 (Январь, 2, 2025)
12
+ * Оптимизирован и дополнен модуль VGLoadMore, см. файл readme.md
13
+
14
+ ---
15
+
16
+ # VEGAS-APP 0.8.0 (Декабрь, 31, 2025)
2
17
  * Полностью переписан модуль VGFiles, см. файл readme.md
3
18
  * Рефакторинг модуля VGLawCookie, см. файл readme.md
4
19
 
@@ -6,5 +6,19 @@
6
6
  "files": {
7
7
  "agree": "Yeah, I agree",
8
8
  "cancel": "Cancel"
9
+ },
10
+ "loadmore": {
11
+ "send": "Uploading...",
12
+ "show": "I'm showing you...",
13
+ "text-ajax": "Upload more",
14
+ "text-more": "Show more"
15
+ },
16
+ "rollup": {
17
+ "less": "Roll up",
18
+ "show": "Show",
19
+ "more": " more "
20
+ },
21
+ "select": {
22
+ "load-more": "Upload more"
9
23
  }
10
24
  }
@@ -28,5 +28,8 @@
28
28
  "title": "Default header",
29
29
  "description": "Description of the current action",
30
30
  "reason": "Alert already open"
31
+ },
32
+ "select": {
33
+ "loading": "Loading"
31
34
  }
32
35
  }
@@ -2,5 +2,8 @@
2
2
  "errors": {
3
3
  "title": "Error",
4
4
  "titles": "Errors"
5
+ },
6
+ "select": {
7
+ "search": "Search..."
5
8
  }
6
9
  }
@@ -6,5 +6,19 @@
6
6
  "files": {
7
7
  "agree": "Да",
8
8
  "cancel": "Нет"
9
+ },
10
+ "loadmore": {
11
+ "send": "Загружаю...",
12
+ "show": "Показываю...",
13
+ "text-ajax": "Загрузить еще",
14
+ "text-more": "Показать еще"
15
+ },
16
+ "rollup": {
17
+ "less": "Свернуть",
18
+ "show": "Показать",
19
+ "more": " еще "
20
+ },
21
+ "select": {
22
+ "load-more": "Загрузить еще"
9
23
  }
10
24
  }
@@ -28,5 +28,8 @@
28
28
  "title": "Заголовок по умолчанию",
29
29
  "description": "Описание текущего действия",
30
30
  "reason": "Алерт уже открыт"
31
+ },
32
+ "select": {
33
+ "loading": "Загрузка"
31
34
  }
32
35
  }
@@ -2,5 +2,8 @@
2
2
  "errors": {
3
3
  "title": "Ошибка",
4
4
  "titles": "Ошибки"
5
+ },
6
+ "select": {
7
+ "search": "Поиск..."
5
8
  }
6
9
  }
@@ -1,37 +1,117 @@
1
1
  import BaseModule from "../../base-module";
2
2
  import EventHandler from "../../../utils/js/dom/event";
3
- import {execute, isObject, mergeDeepObject, normalizeData} from "../../../utils/js/functions";
3
+ import { execute, isObject, mergeDeepObject, normalizeData } from "../../../utils/js/functions";
4
4
  import Selectors from "../../../utils/js/dom/selectors";
5
- import {Manipulator} from "../../../utils/js/dom/manipulator";
5
+ import { Manipulator } from "../../../utils/js/dom/manipulator";
6
+ import { lang_buttons } from "../../../utils/js/components/lang";
6
7
 
7
- const NAME = 'loadmore';
8
- const NAME_KEY = 'vg.loadmore';
8
+ /**
9
+ * @typedef {Object} VGLoadMoreParams
10
+ * @property {string} [lang='ru'] - Язык интерфейса (используется для локализации)
11
+ * @property {number} limit - Количество элементов, загружаемых за один раз
12
+ * @property {number} offset - Начальный сдвиг при загрузке
13
+ * @property {boolean} output - Вставлять ли ответ в DOM
14
+ * @property {boolean} autohide - Автоматически скрывать/удалять кнопку при достижении конца
15
+ * @property {boolean} animate - Анимировать появление новых элементов
16
+ * @property {string} append - Позиция вставки: 'after' (в конец) или 'before' (в начало)
17
+ * @property {string} mode - Режим загрузки: 'button' или 'scroll'
18
+ * @property {number} threshold - Отступ в пикселях для срабатывания при прокрутке
19
+ * @property {boolean} debug - Включить отладочные сообщения в консоли
20
+ * @property {boolean} detach - Удалять кнопку из DOM после использования
21
+ * @property {Object} button - Настройки кнопки
22
+ * @property {string} button.text - Текст кнопки по умолчанию
23
+ * @property {string} button.send - Текст во время AJAX-загрузки
24
+ * @property {string} button.show - Текст во время статической загрузки
25
+ * @property {boolean} button.loader - Показывать лоадер
26
+ * @property {string[]} button.classes - CSS-классы кнопки
27
+ * @property {Object} ajax - Настройки AJAX-запроса
28
+ * @property {string} ajax.route - URL для загрузки данных
29
+ * @property {string} ajax.target - Селектор контейнера для вставки
30
+ * @property {string} ajax.method - HTTP-метод ('get', 'post')
31
+ * @property {boolean} ajax.loader - Использовать лоадер
32
+ * @property {boolean} ajax.once - Загружать только один раз
33
+ * @property {boolean} ajax.output - Выводить ли ответ
34
+ * @property {Object} ajax.data - Дополнительные данные для запроса
35
+ */
9
36
 
37
+ /**
38
+ * @class VGLoadMore
39
+ * @extends BaseModule
40
+ * @description
41
+ * Модуль подгрузки контента по кнопке или при прокрутке.
42
+ * Поддерживает как статическую подгрузку скрытых элементов из DOM,
43
+ * так и AJAX-загрузку с сервера. Имеет гибкую настройку через data-атрибуты,
44
+ * поддержку языков, анимаций и отладку.
45
+ *
46
+ * @example
47
+ * <!-- Простая подгрузка по кнопке -->
48
+ * <div data-vgloadmore data-limit="3" data-elements="item">
49
+ * <div class="item vg-collapse">Элемент 1</div>
50
+ * <div class="item vg-collapse">Элемент 2</div>
51
+ * ...
52
+ * </div>
53
+ *
54
+ * @example
55
+ * <!-- AJAX-подгрузка при прокрутке -->
56
+ * <div data-vgloadmore
57
+ * data-ajax-route="/api/load"
58
+ * data-mode="scroll"
59
+ * data-threshold="200">
60
+ * </div>
61
+ */
62
+
63
+ const NAME = 'loadmore';
64
+ const NAME_KEY = 'vg.' + NAME;
10
65
  const SELECTOR_DATA_TOGGLE = '[data-vg-toggle="loadmore"]';
66
+ const SELECTOR_DATA_MODULE = '[data-vgloadmore]';
11
67
 
12
- const EVENT_KEY_HIDE = `${NAME_KEY}.hide`;
13
- const EVENT_KEY_HIDDEN = `${NAME_KEY}.hidden`;
14
- const EVENT_KEY_SHOW = `${NAME_KEY}.show`;
15
- const EVENT_KEY_SHOWN = `${NAME_KEY}.shown`;
16
68
  const EVENT_KEY_LOADED = `${NAME_KEY}.loaded`;
17
-
69
+ const EVENT_KEY_BEFORE_LOAD = `${NAME_KEY}.before.load`;
18
70
  const CLASS_NAME_HIDE = 'vg-collapse';
19
71
  const CLASS_NAME_SHOW = 'show';
20
-
21
- const EVENT_KEY_CLICK_DATA_API = `click.${NAME_KEY}.data.api`;
22
-
23
- class VGLoadMore extends BaseModule{
72
+ const EVENT_KEY_CLICK_DATA_API = `click.${NAME_KEY}.data.api`;
73
+
74
+ class VGLoadMore extends BaseModule {
75
+ /**
76
+ * Имя модуля
77
+ * @type {string}
78
+ */
79
+ static get NAME() { return NAME; }
80
+
81
+ /**
82
+ * Ключ события модуля
83
+ * @type {string}
84
+ */
85
+ static get NAME_KEY() { return NAME_KEY; }
86
+
87
+ /**
88
+ * @private
89
+ */
24
90
  constructor(element, params) {
25
91
  super(element, params);
26
92
 
93
+ /**
94
+ * Параметры модуля
95
+ * @type {VGLoadMoreParams}
96
+ * @private
97
+ */
27
98
  this._params = this._getParams(element, mergeDeepObject({
99
+ lang: document.documentElement.lang || 'ru',
28
100
  limit: 0,
29
101
  offset: 0,
30
102
  output: true,
31
103
  autohide: true,
104
+ animate: false,
105
+ append: 'after',
106
+ mode: 'button',
107
+ threshold: 100,
108
+ debug: false,
109
+ detach: false,
32
110
  button: {
33
111
  text: '',
34
- send: 'Загружаем...',
112
+ send: 'Загружаю...',
113
+ show: 'Показываю...',
114
+ loader: false,
35
115
  classes: []
36
116
  },
37
117
  ajax: {
@@ -41,168 +121,339 @@ class VGLoadMore extends BaseModule{
41
121
  loader: false,
42
122
  once: false,
43
123
  output: false,
44
- },
124
+ data: {}
125
+ }
45
126
  }, params));
46
127
 
47
- this.fOffset = this._params.offset;
128
+ /**
129
+ * Обсервер пересечения (для режима scroll)
130
+ * @type {IntersectionObserver|null}
131
+ * @private
132
+ */
133
+ this._observer = null;
134
+
135
+ /**
136
+ * Режим прокрутки включён
137
+ * @type {boolean}
138
+ * @private
139
+ */
140
+ this._isScrollMode = this._params.mode === 'scroll';
141
+
142
+ /**
143
+ * Элемент является триггером (имеет data-vg-toggle)
144
+ * @type {boolean}
145
+ * @private
146
+ */
147
+ this._isToggleElement = element.hasAttribute('data-vg-toggle');
148
+
149
+ // Локализация текстов кнопок
150
+ this._params.button.send = lang_buttons(this._params.lang, NAME)['send'];
151
+ this._params.button.show = lang_buttons(this._params.lang, NAME)['show'];
48
152
 
49
153
  if (!this._params.button.text) {
50
- this._params.button.text = this._element.innerHTML;
154
+ this._params.button.text = this._isToggleElement
155
+ ? this._element.innerHTML.trim() || lang_buttons(this._params.lang, NAME)['text-ajax']
156
+ : this._params.ajax.route ? lang_buttons(this._params.lang, NAME)['text-ajax'] : lang_buttons(this._params.lang, NAME)['text-more'];
51
157
  }
52
- }
53
158
 
54
- static get NAME() {
55
- return NAME;
159
+ if (this._isToggleElement) {
160
+ this._initializeAsButton();
161
+ } else {
162
+ this._initializeContainer();
163
+ }
56
164
  }
57
165
 
58
- static get NAME_KEY() {
59
- return NAME_KEY;
166
+ /**
167
+ * Инициализация, если элемент — кнопка-триггер
168
+ * @private
169
+ */
170
+ _initializeAsButton() {
171
+ if (this._isScrollMode) {
172
+ this._initScrollMode();
173
+ }
60
174
  }
61
175
 
62
- static init(el, callback) {
63
- let id = el.id,
64
- items = normalizeData(el.dataset.elements),
65
- limit = normalizeData(el.dataset.limit),
66
- offset = normalizeData(el.dataset.offset),
67
- output = el.dataset.output || 'true',
68
- autohide = el.dataset.autohide || 'true',
69
- params = el.dataset.params,
70
- buttonParams = normalizeData(el.dataset.button);
71
-
72
- if (!isObject(buttonParams)) {
73
- console.error('Дата атрибут data-button должен быть в формате json и передавать объект');
74
- return;
75
- }
176
+ /**
177
+ * Инициализация контейнера с элементами
178
+ * @private
179
+ */
180
+ _initializeContainer() {
181
+ const { elements: itemClass, limit } = this._params;
182
+ const container = this._element;
183
+ const items = Selectors.findAll(`.${itemClass}`, container);
76
184
 
77
- if (limit < offset) {
78
- console.error('Параметр offset должен быть меньше или равен параметру limit');
185
+ if (!this._params.ajax.route && (items.length <= limit || this._params.offset >= items.length)) {
79
186
  return;
80
187
  }
81
188
 
82
- if (!id && !items && !limit && !offset) return;
189
+ items.forEach((item, i) => {
190
+ item.classList.toggle(CLASS_NAME_SHOW, i < limit);
191
+ item.classList.toggle(CLASS_NAME_HIDE, i >= limit);
192
+ });
83
193
 
84
- let itemsElements = [... Selectors.findAll('.' + items, el)];
194
+ if (this._params.mode === 'button') {
195
+ this._createAndInsertButton(container);
196
+ } else if (this._isScrollMode) {
197
+ this._initScrollMode();
198
+ }
199
+
200
+ if (this._params.offset === 0) {
201
+ this._params.offset = limit;
202
+ }
203
+ }
85
204
 
86
- if (itemsElements.length <= limit) return;
205
+ /**
206
+ * Создаёт и вставляет кнопку управления
207
+ * @param {HTMLElement} container - Контейнер, рядом с которым вставляется кнопка
208
+ * @private
209
+ */
210
+ _createAndInsertButton(container) {
211
+ const button = document.createElement('button');
212
+ const buttonText = normalizeData(container.dataset.buttonText) || this._params.button.text;
87
213
 
88
- itemsElements.forEach((item, i) => {
89
- item.classList.add(CLASS_NAME_HIDE)
90
- if ((i + 1) <= limit) item.classList.add(CLASS_NAME_SHOW)
214
+ const buttonData = {
215
+ limit: this._params.limit,
216
+ offset: this._params.offset,
217
+ output: this._params.output,
218
+ autohide: this._params.autohide,
219
+ animate: this._params.animate,
220
+ append: this._params.append,
221
+ mode: this._params.mode,
222
+ threshold: this._params.threshold,
223
+ debug: this._params.debug,
224
+ detach: this._params.detach,
225
+ elements: this._params.elements,
226
+ 'vg-toggle': 'loadmore',
227
+ target: `#${container.id}`
228
+ };
229
+
230
+ Object.assign(buttonData, normalizeData(container.dataset.params));
231
+
232
+ Object.keys(buttonData).forEach(key => {
233
+ Manipulator.set(button, `data-${key}`, buttonData[key]);
91
234
  });
92
235
 
93
- let button = document.createElement('button');
236
+ button.textContent = buttonText;
237
+ button.classList.add(...this._params.button.classes);
94
238
 
95
- buttonParams.text = normalizeData(el.dataset.buttonText) || 'Показать еще';
239
+ container.parentNode.insertBefore(button, container.nextSibling);
240
+ }
96
241
 
97
- Manipulator.set(button, 'data-limit', limit);
98
- Manipulator.set(button, 'data-offset', offset);
99
- Manipulator.set(button, 'data-output', output);
100
- Manipulator.set(button, 'data-autohide', autohide);
101
- Manipulator.set(button, 'data-elements', items);
102
- Manipulator.set(button, 'data-vg-toggle', 'loadmore');
103
- Manipulator.set(button, 'data-target', '#' + id);
242
+ /**
243
+ * Инициализирует режим прокрутки
244
+ * @private
245
+ */
246
+ _initScrollMode() {
247
+ this._setupIntersectionObserver();
248
+ this._observeLastVisibleItem();
249
+ }
104
250
 
105
- if (params) Manipulator.set(button, 'data-autohide', params);
251
+ /**
252
+ * Настраивает IntersectionObserver для отслеживания видимости последнего элемента
253
+ * @private
254
+ */
255
+ _setupIntersectionObserver() {
256
+ if (this._observer) return;
257
+
258
+ this._observer = new IntersectionObserver((entries) => {
259
+ entries.forEach(entry => {
260
+ if (entry.isIntersecting) {
261
+ this._params.debug && console.log('[VGLoadMore] Пересечение — вызов toggle');
262
+ this.toggle();
263
+ }
264
+ });
265
+ }, {
266
+ root: null,
267
+ rootMargin: `0px 0px ${this._params.threshold}px 0px`,
268
+ threshold: 0.1
269
+ });
270
+ }
106
271
 
107
- button.innerHTML = buttonParams.text
272
+ /**
273
+ * Начинает отслеживание последнего видимого элемента
274
+ * @private
275
+ */
276
+ _observeLastVisibleItem() {
277
+ if (!this._observer) return;
108
278
 
109
- if ('classes' in buttonParams && buttonParams.classes.length) {
110
- buttonParams.classes.forEach(cl => button.classList.add(cl));
111
- }
279
+ this._observer.disconnect();
112
280
 
113
- el.parentNode.insertBefore(button, el.nextSibling);
281
+ const container = Selectors.find(this._params.target) || this._element.parentNode;
282
+ const items = Selectors.findAll(`.${this._params.elements}`, container);
283
+ const visibleItems = items.filter(item => item.classList.contains(CLASS_NAME_SHOW));
284
+ const lastVisible = visibleItems[visibleItems.length - 1];
114
285
 
115
- execute(callback, [el, button]);
286
+ if (lastVisible instanceof Element) {
287
+ this._observer.observe(lastVisible);
288
+ }
116
289
  }
117
290
 
291
+ /**
292
+ * Основной метод подгрузки — вызывается по клику или при прокрутке
293
+ * @param {Function} [callback] - Колбэк, вызываемый после загрузки
294
+ * @fires VGLoadMore#vg.loadmore.before.load
295
+ * @fires VGLoadMore#vg.loadmore.loaded
296
+ * @public
297
+ */
118
298
  toggle(callback) {
119
- if (this._params.ajax.route) {
120
- this.ajax(callback);
121
- } else {
122
- this.static(callback);
299
+ this._params.debug && console.log('[VGLoadMore] toggle()');
300
+
301
+ if (EventHandler.trigger(this._element, EVENT_KEY_BEFORE_LOAD).defaultPrevented) return;
302
+
303
+ const isButton = this._isToggleElement || (['BUTTON', 'A'].includes(this._element.tagName) && !this._isScrollMode);
304
+ if (isButton) {
305
+ this._element.disabled = true;
306
+ this._element.innerHTML = this._params.ajax.route ? this._params.button.send : this._params.button.show;
123
307
  }
308
+
309
+ this._params.ajax.route ? this.ajax(callback) : this.staticLoad(callback);
124
310
  }
125
311
 
312
+ /**
313
+ * Выполняет AJAX-запрос для подгрузки данных
314
+ * @param {Function} [callback] - Колбэк после завершения
315
+ * @private
316
+ */
126
317
  ajax(callback) {
127
- this._params.ajax.data = {
128
- limit: this._params.limit,
129
- offset: this._params.offset
318
+ const targetSelector = this._params.ajax.target?.trim();
319
+ let targetEl = null;
320
+
321
+ if (targetSelector) {
322
+ targetEl = Selectors.find(targetSelector);
323
+ } else {
324
+ targetEl = this._element;
130
325
  }
131
326
 
132
- if (this._params.button.send) {
133
- this._element.innerHTML = this._params.button.send;
327
+ if (!targetEl) {
328
+ console.error('[VGLoadMore] target элемент не найден:', this._params.ajax.target);
329
+ return;
134
330
  }
135
331
 
136
- this._route((status, data, target) => {
137
- if ('loader' in this._params.ajax && this._params.ajax.loader) {
138
- let loader = Selectors.find('.vg-loader', target);
139
- if (loader) loader.remove();
140
- }
332
+ const originalText = this._params.button.text;
333
+
334
+ this._params.ajax.data = { limit: this._params.limit, offset: this._params.offset };
141
335
 
142
- if ('output' in this._params && this._params.output) {
143
- target.insertAdjacentHTML('beforeend', data.response);
336
+ this._route((status, data, responseTarget) => {
337
+ if (status === 'error' || typeof data?.response !== 'string') return;
338
+
339
+ if (this._params.output) {
340
+ targetEl.insertAdjacentHTML(
341
+ this._params.append === 'after' ? 'beforeend' : 'afterbegin',
342
+ data.response
343
+ );
144
344
  }
145
345
 
146
- this._params.offset = this.counter();
147
- this._element.innerHTML = this._params.button.text;
346
+ this._params.offset += this._params.limit;
347
+ this._restoreElementState(originalText);
348
+ this._observeLastVisibleItem();
148
349
 
149
- if ('autohide' in this._params && this._params.autohide) {
150
- if (!data.response) this._element.remove();
350
+ const noMoreData = !data.response.trim();
351
+ if (this._params.autohide && this._params.detach && noMoreData) {
352
+ this._autohideTrigger();
151
353
  }
152
354
 
153
- EventHandler.trigger(this._element, EVENT_KEY_LOADED, {stats: status, data: data});
154
- execute(callback, [this, data, target, status]);
355
+ EventHandler.trigger(this._element, EVENT_KEY_LOADED, { stats: status, data });
356
+ execute(callback, [this, data, responseTarget, status]);
357
+ }, (error) => {
358
+ this._restoreElementState(originalText);
359
+ console.error('[VGLoadMore] AJAX ошибка', error);
155
360
  });
156
361
  }
157
362
 
158
- static(callback) {
159
- if (!'elements' in this._params && !'target' in this._params) return;
160
-
161
- let container = Selectors.find(this._params.target),
162
- items = Selectors.findAll('.' + this._params.elements, container);
163
-
164
- if (items) {
165
- items.slice(this._params.offset, this._params.offset + this._params.limit).forEach(item => item.classList.add(CLASS_NAME_SHOW));
166
- this._params.offset = this.counter();
363
+ /**
364
+ * Подгружает элементы из DOM (статический режим)
365
+ * @param {Function} [callback] - Колбэк после завершения
366
+ * @private
367
+ */
368
+ staticLoad(callback) {
369
+ const container = Selectors.find(this._params.target) || this._element.parentNode;
370
+ const items = Selectors.findAll(`.${this._params.elements}`, container);
371
+ const start = this._params.offset;
372
+ const end = start + this._params.limit;
373
+ const newItems = items.slice(start, end);
374
+
375
+ if (newItems.length === 0) return;
376
+
377
+ if (this._isToggleElement || (['BUTTON', 'A'].includes(this._element.tagName) && !this._isScrollMode)) {
378
+ this._element.disabled = true;
379
+ this._element.innerHTML = this._params.button.show;
167
380
  }
168
381
 
169
- let itemsHidden = Selectors.findAll('.' + this._params.elements + ':not(.show)', container);
170
-
171
- if (this.remainder(itemsHidden.length)) {
172
- if ('autohide' in this._params && this._params.autohide) {
173
- this._element.remove();
382
+ newItems.forEach(item => {
383
+ item.classList.replace(CLASS_NAME_HIDE, CLASS_NAME_SHOW);
384
+ if (this._params.animate) {
385
+ item.style.opacity = 0;
386
+ requestAnimationFrame(() => {
387
+ item.style.transition = 'opacity 0.3s ease';
388
+ item.style.opacity = 1;
389
+ });
174
390
  }
391
+ });
392
+
393
+ this._params.offset = end;
394
+ this._observeLastVisibleItem();
395
+
396
+ const remaining = items.slice(end);
397
+ if (this._params.autohide && this._params.detach && remaining.length === 0) {
398
+ this._autohideTrigger();
175
399
  }
176
400
 
401
+ this._restoreElementState(this._params.button.text);
177
402
  execute(callback, [this, this._element]);
178
403
  }
179
404
 
180
- counter() {
181
- return this.fOffset + this._params.offset;
405
+ /**
406
+ * Восстанавливает состояние кнопки (включает, устанавливает текст)
407
+ * @param {string} text - Текст кнопки
408
+ * @private
409
+ */
410
+ _restoreElementState(text) {
411
+ const isButton = this._isToggleElement || this._element.tagName === 'BUTTON';
412
+ if (isButton && !this._isScrollMode) {
413
+ this._element.disabled = false;
414
+ this._element.innerHTML = text;
415
+ }
182
416
  }
183
417
 
184
- remainder(count) {
185
- return count === 0
418
+ /**
419
+ * Автоматически скрывает или удаляет элемент при завершении
420
+ * @private
421
+ */
422
+ _autohideTrigger() {
423
+ if (this._isScrollMode) {
424
+ this._observer?.disconnect();
425
+ this._observer = null;
426
+ } else if (this._params.detach && this._element.parentNode) {
427
+ this._element.remove();
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Полная деинициализация модуля
433
+ * @override
434
+ */
435
+ dispose() {
436
+ this._observer?.disconnect();
437
+ super.dispose();
186
438
  }
187
439
  }
188
440
 
189
- EventHandler.on(document, 'DOMContentLoaded', function () {
190
- [... document.querySelectorAll('[data-vgloadmore]')].forEach(el => {
191
- VGLoadMore.init(el);
192
- })
441
+ /**
442
+ * Автоматическая инициализация на элементах с data-vgloadmore
443
+ */
444
+ EventHandler.on(document, 'DOMContentLoaded', () => {
445
+ Selectors.findAll(SELECTOR_DATA_MODULE).forEach(el => {
446
+ !el.dataset.initialized && VGLoadMore.getOrCreateInstance(el);
447
+ el.dataset.initialized = 'true';
448
+ });
193
449
  });
194
450
 
195
451
  /**
196
- * Data API implementation
452
+ * Обработка кликов по элементам с data-vg-toggle="loadmore"
197
453
  */
198
454
  EventHandler.on(document, EVENT_KEY_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
199
- const target = this;
200
-
201
- if (['A', 'AREA'].includes(this.tagName)) event.preventDefault();
202
-
203
- const instance = VGLoadMore.getOrCreateInstance(target);
204
- instance.toggle();
455
+ ['A', 'AREA'].includes(this.tagName) && event.preventDefault();
456
+ VGLoadMore.getOrCreateInstance(this).toggle();
205
457
  });
206
458
 
207
-
208
459
  export default VGLoadMore;