vgapp 0.7.7 → 0.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +20 -4
  2. package/LICENSE +22 -0
  3. package/app/modules/base-module.js +62 -17
  4. package/app/modules/module-fn.js +10 -100
  5. package/app/modules/vgalert/js/vgalert.js +356 -214
  6. package/app/modules/vgalert/readme.md +242 -0
  7. package/app/modules/vgcollapse/js/vgcollapse.js +216 -62
  8. package/app/modules/vgcollapse/readme.md +56 -0
  9. package/app/modules/vgcollapse/scss/_variables.scss +5 -0
  10. package/app/modules/vgcollapse/scss/vgcollapse.scss +41 -0
  11. package/app/modules/vgdropdown/js/vgdropdown.js +104 -118
  12. package/app/modules/vgdropdown/scss/vgdropdown.scss +1 -2
  13. package/app/modules/vgformsender/js/hideshowpass.js +7 -4
  14. package/app/modules/vgformsender/js/vgformsender.js +343 -160
  15. package/app/modules/vgformsender/readme.md +250 -0
  16. package/app/modules/vgformsender/scss/vgformsender.scss +11 -3
  17. package/app/modules/vgnav/js/vgnav.js +98 -26
  18. package/app/modules/vgnav/scss/_placement.scss +8 -93
  19. package/app/modules/vgnav/scss/vgnav.scss +0 -1
  20. package/app/utils/js/components/ajax.js +237 -0
  21. package/app/utils/js/components/lang.js +108 -0
  22. package/app/utils/js/components/params.js +5 -0
  23. package/app/utils/js/components/placement.js +111 -108
  24. package/app/utils/js/components/templater.js +365 -33
  25. package/app/utils/js/functions.js +275 -143
  26. package/app/utils/scss/default.scss +1 -0
  27. package/app/utils/scss/placement.scss +72 -0
  28. package/app/utils/scss/variables.scss +10 -5
  29. package/build/vgapp.css +1 -1
  30. package/build/vgapp.css.map +1 -1
  31. package/index.scss +3 -0
  32. package/package.json +1 -1
  33. package/app/utils/js/components/alert.js +0 -8
@@ -1,25 +1,37 @@
1
1
  /**
2
2
  * Набор скриптов для широкого применения
3
+ * Включает утилиты для работы с DOM, типами данных, анимациями и массивами.
4
+ * Все функции экспортируются для использования в других модулях.
3
5
  */
4
6
 
5
7
  /**
6
- * Экранирование селекторов для корректной работы идентификаторов
7
- * @param {string} selector
8
- * @returns {string}
8
+ * Экранирование селекторов для корректной работы идентификаторов,
9
+ * содержащих специальные символы (например, `#my-id:with-colon`).
10
+ * Использует `CSS.escape()` при наличии поддержки в браузере.
11
+ *
12
+ * @param {string} selector - CSS-селектор, который необходимо экранировать
13
+ * @returns {string} Экранированный селектор, пригодный для `document.querySelector`
14
+ * @example
15
+ * parseSelector('#user:input') → '#user\\:input'
9
16
  */
10
- const parseSelector = selector => {
17
+ const parseSelector = (selector) => {
11
18
  if (selector && window.CSS && window.CSS.escape) {
12
19
  // document.querySelector needs escaping to handle IDs (html5+) containing for instance /
13
- selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`)
20
+ selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`);
14
21
  }
15
22
 
16
- return selector
17
- }
23
+ return selector;
24
+ };
18
25
 
19
26
  /**
20
- * Если что-нибудь в объекте
21
- * @param obj
22
- * @returns {boolean}
27
+ * Проверяет, является ли объект пустым.
28
+ * Объект считается пустым, если не содержит собственных перечисляемых свойств.
29
+ *
30
+ * @param {Object} obj - Объект для проверки
31
+ * @returns {boolean} `true`, если объект пуст или не является объектом, иначе `false`
32
+ * @example
33
+ * isEmptyObj({}) → true
34
+ * isEmptyObj({ a: 1 }) → false
23
35
  */
24
36
  function isEmptyObj(obj) {
25
37
  for (let prop in obj) {
@@ -28,160 +40,199 @@ function isEmptyObj(obj) {
28
40
  }
29
41
  }
30
42
 
31
- return true
43
+ return true;
32
44
  }
33
45
 
34
- const getElement = object => {
46
+ /**
47
+ * Получает DOM-элемент по селектору или возвращает сам элемент, если передан.
48
+ * Поддерживает jQuery-объекты (через `.jquery` и `[0]`).
49
+ *
50
+ * @param {string|Element|jQuery} object - Селектор строки, DOM-элемент или jQuery-объект
51
+ * @returns {Element|null} Найденный DOM-элемент или `null`, если не найден
52
+ * @example
53
+ * getElement('#myId') → <div id="myId">...</div>
54
+ * getElement(element) → element
55
+ */
56
+ const getElement = (object) => {
35
57
  // it's a jQuery object or a node element
36
58
  if (isElement(object)) {
37
- return object.jquery ? object[0] : object
59
+ return object.jquery ? object[0] : object;
38
60
  }
39
61
 
40
62
  if (typeof object === 'string' && object.length > 0) {
41
- return document.querySelector(parseSelector(object))
63
+ return document.querySelector(parseSelector(object));
42
64
  }
43
65
 
44
- return null
45
- }
66
+ return null;
67
+ };
46
68
 
47
69
  /**
48
- * isElement
49
- * @param object
50
- * @returns {boolean}
70
+ * Проверяет, является ли переданный объект DOM-элементом.
71
+ *
72
+ * @param {*} object - Объект для проверки
73
+ * @returns {boolean} `true`, если объект является DOM-элементом, иначе `false`
74
+ * @example
75
+ * isElement(document.body) → true
76
+ * isElement({}) → false
51
77
  */
52
- const isElement = object => {
78
+ const isElement = (object) => {
53
79
  if (!isObject(object)) {
54
- return false
80
+ return false;
55
81
  }
56
82
 
57
- return typeof object.nodeType !== 'undefined'
58
- }
83
+ return typeof object.nodeType !== 'undefined';
84
+ };
59
85
 
60
86
  /**
61
- * isDisabled
62
- * @param element
63
- * @returns {boolean}
87
+ * Проверяет, отключён ли DOM-элемент.
88
+ * Учитывает класс `.disabled`, атрибут `disabled`, а также свойство `disabled`.
89
+ *
90
+ * @param {Element} element - DOM-элемент для проверки
91
+ * @returns {boolean} `true`, если элемент отключён, иначе `false`
92
+ * @example
93
+ * isDisabled(document.querySelector('[disabled]')) → true
64
94
  */
65
- const isDisabled = element => {
95
+ const isDisabled = (element) => {
66
96
  if (!element || element.nodeType !== Node.ELEMENT_NODE) {
67
- return true
97
+ return true;
68
98
  }
69
99
 
70
100
  if (element.classList.contains('disabled')) {
71
- return true
101
+ return true;
72
102
  }
73
103
 
74
104
  if (typeof element.disabled !== 'undefined') {
75
- return element.disabled
105
+ return element.disabled;
76
106
  }
77
107
 
78
- return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'
79
- }
108
+ return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';
109
+ };
80
110
 
81
111
  /**
82
- * isVisible
83
- * @param element
84
- * @returns {boolean}
112
+ * Проверяет, видим ли DOM-элемент.
113
+ * Учитывает размеры (getClientRects), `visibility: hidden`, `details:not([open])` и `summary`.
114
+ *
115
+ * @param {Element} element - DOM-элемент для проверки видимости
116
+ * @returns {boolean} `true`, если элемент видим, иначе `false`
117
+ * @example
118
+ * isVisible(button) → true
85
119
  */
86
- function isVisible (element) {
120
+ function isVisible(element) {
87
121
  if (!isElement(element) || element.getClientRects().length === 0) {
88
- return false
122
+ return false;
89
123
  }
90
124
 
91
- const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'
92
- const closedDetails = element.closest('details:not([open])')
125
+ const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible';
126
+ const closedDetails = element.closest('details:not([open])');
93
127
 
94
128
  if (!closedDetails) {
95
- return elementIsVisible
129
+ return elementIsVisible;
96
130
  }
97
131
 
98
132
  if (closedDetails !== element) {
99
- const summary = element.closest('summary')
133
+ const summary = element.closest('summary');
100
134
  if (summary && summary.parentNode !== closedDetails) {
101
- return false
135
+ return false;
102
136
  }
103
137
 
104
138
  if (summary === null) {
105
- return false
139
+ return false;
106
140
  }
107
141
  }
108
142
 
109
- return elementIsVisible
143
+ return elementIsVisible;
110
144
  }
111
145
 
112
146
  /**
113
- * isObject
114
- * @param obj
115
- * @returns {boolean}
147
+ * Проверяет, является ли значение объектом (включая массивы и null).
148
+ *
149
+ * @param {*} obj - Значение для проверки
150
+ * @returns {boolean} `true`, если значение — объект, иначе `false`
151
+ * @example
152
+ * isObject({}) → true
153
+ * isObject([]) → true
154
+ * isObject(null) → false
116
155
  */
117
156
  function isObject(obj) {
118
- return obj && typeof obj === 'object'
157
+ return obj && typeof obj === 'object';
119
158
  }
120
159
 
121
160
  /**
122
- * Приводим в порядок типы данных
123
- * @param value
124
- * @returns {any}
161
+ * Нормализует строку в соответствующий тип данных.
162
+ * Преобразует строки в `true`, `false`, `null`, числа, JSON и т.д.
163
+ *
164
+ * @param {string|*} value - Значение для нормализации
165
+ * @returns {*} Нормализованное значение: boolean, number, null, object, string и т.д.
166
+ * @example
167
+ * normalizeData('true') → true
168
+ * normalizeData('{"a":1}') → { a: 1 }
125
169
  */
126
- function normalizeData(value) {
170
+ function normalizeData(value) {
127
171
  if (value === 'true') {
128
- return true
172
+ return true;
129
173
  }
130
174
 
131
175
  if (value === 'false') {
132
- return false
176
+ return false;
133
177
  }
134
178
 
135
179
  if (value === Number(value).toString()) {
136
- return Number(value)
180
+ return Number(value);
137
181
  }
138
182
 
139
183
  if (value === '' || value === 'null') {
140
- return null
184
+ return null;
141
185
  }
142
186
 
143
187
  if (typeof value !== 'string') {
144
- return value
188
+ return value;
145
189
  }
146
190
 
147
191
  try {
148
- return JSON.parse(decodeURIComponent(value))
192
+ return JSON.parse(decodeURIComponent(value));
149
193
  } catch {
150
- return value
194
+ return value;
151
195
  }
152
196
  }
153
197
 
154
198
  /**
155
- * Удаляем элементы с массива
156
- * @param arr
157
- * @param el
199
+ * Удаляет указанные элементы из массива.
200
+ *
201
+ * @param {Array} arr - Исходный массив
202
+ * @param {Array} el - Массив элементов, которые нужно удалить
203
+ * @returns {Array} Новый массив без указанных элементов
204
+ * @example
205
+ * removeElementArray([1, 2, 3], [2]) → [1, 3]
158
206
  */
159
207
  function removeElementArray(arr, el) {
160
208
  return arr.filter((item) => !el.includes(item));
161
209
  }
162
210
 
163
211
  /**
164
- * Глубокое объединение объектов
165
- * @param objects
166
- * @returns {*}
212
+ * Глубокое объединение нескольких объектов.
213
+ * Поддерживает вложенные объекты и массивы (конкатенация).
214
+ *
215
+ * @param {...Object} objects - Объекты для объединения
216
+ * @returns {Object} Новый объект с объединёнными данными
217
+ * @example
218
+ * mergeDeepObject({ a: [1] }, { a: [2] }) → { a: [1, 2] }
219
+ * mergeDeepObject({ a: { b: 1 } }, { a: { c: 2 } }) → { a: { b: 1, c: 2 } }
167
220
  */
168
221
  function mergeDeepObject(...objects) {
169
- const isObject = obj => obj && typeof obj === 'object';
222
+ const isObject = (obj) => obj && typeof obj === 'object';
170
223
 
171
224
  if (!isObject) return;
172
225
 
173
226
  return objects.reduce((prev, obj) => {
174
- Object.keys(obj).forEach(key => {
227
+ Object.keys(obj).forEach((key) => {
175
228
  const pVal = prev[key];
176
229
  const oVal = obj[key];
177
230
 
178
231
  if (Array.isArray(pVal) && Array.isArray(oVal)) {
179
232
  prev[key] = pVal.concat(...oVal);
180
- }
181
- else if (isObject(pVal) && isObject(oVal)) {
233
+ } else if (isObject(pVal) && isObject(oVal)) {
182
234
  prev[key] = mergeDeepObject(pVal, oVal);
183
- }
184
- else {
235
+ } else {
185
236
  prev[key] = oVal;
186
237
  }
187
238
  });
@@ -191,100 +242,144 @@ function mergeDeepObject(...objects) {
191
242
  }
192
243
 
193
244
  /**
194
- * Callback
195
- * @param possibleCallback
196
- * @param args
197
- * @param defaultValue
198
- * @returns {*}
245
+ * Выполняет функцию, если передан колбэк; иначе возвращает значение по умолчанию.
246
+ *
247
+ * @param {Function|*} possibleCallback - Функция или любое значение
248
+ * @param {Array} [args=[]] - Аргументы для вызова функции
249
+ * @param {*} [defaultValue=possibleCallback] - Значение по умолчанию, если колбэк не функция
250
+ * @returns {*} Результат выполнения колбэка или `defaultValue`
251
+ * @example
252
+ * execute(() => 'hi') → 'hi'
253
+ * execute('notFunc', [], 'fallback') → 'fallback'
199
254
  */
200
255
  function execute(possibleCallback, args = [], defaultValue = possibleCallback) {
201
- return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue
256
+ return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue;
202
257
  }
203
258
 
204
259
  /**
205
- * Transition
206
- * @param callback
207
- * @param transitionElement
208
- * @param waitForTransition
260
+ * Событие окончания CSS-перехода.
261
+ * @type {string}
209
262
  */
210
263
  const TRANSITION_END = 'transitionend';
264
+
265
+ /**
266
+ * Множитель для перевода секунд в миллисекунды.
267
+ * @type {number}
268
+ */
211
269
  const MILLISECONDS_MULTIPLIER = 1000;
212
270
 
213
- function executeAfterTransition (callback, transitionElement, waitForTransition = true, timeOutMs) {
271
+ /**
272
+ * Выполняет колбэк после завершения CSS-перехода.
273
+ * Использует `transitionend`, с fallback на `setTimeout`.
274
+ *
275
+ * @param {Function} callback - Функция, вызываемая после перехода
276
+ * @param {Element} transitionElement - Элемент, за переходом которого нужно следить
277
+ * @param {boolean} [waitForTransition=true] - Ожидать ли переход
278
+ * @param {number} [timeOutMs] - Фиксированная задержка (вместо авто-определения)
279
+ * @example
280
+ * executeAfterTransition(() => console.log('done'), button)
281
+ */
282
+ function executeAfterTransition(callback, transitionElement, waitForTransition = true, timeOutMs) {
214
283
  if (!waitForTransition) {
215
- execute(callback)
216
- return
284
+ execute(callback);
285
+ return;
217
286
  }
218
287
 
219
- const durationPadding = 5
288
+ const durationPadding = 5;
220
289
  const emulatedDuration = timeOutMs ? timeOutMs : getTransitionDurationFromElement(transitionElement) + durationPadding;
221
290
 
222
- let called = false
291
+ let called = false;
223
292
 
224
293
  const handler = ({ target }) => {
225
294
  if (target !== transitionElement) {
226
- return
295
+ return;
227
296
  }
228
297
 
229
- called = true
230
- transitionElement.removeEventListener(TRANSITION_END, handler)
231
- execute(callback)
232
- }
298
+ called = true;
299
+ transitionElement.removeEventListener(TRANSITION_END, handler);
300
+ execute(callback);
301
+ };
233
302
 
234
- transitionElement.addEventListener(TRANSITION_END, handler)
303
+ transitionElement.addEventListener(TRANSITION_END, handler);
235
304
  setTimeout(() => {
236
305
  if (!called) {
237
- triggerTransitionEnd(transitionElement)
306
+ triggerTransitionEnd(transitionElement);
238
307
  }
239
- }, emulatedDuration)
308
+ }, emulatedDuration);
240
309
  }
241
310
 
242
- const getTransitionDurationFromElement = element => {
311
+ /**
312
+ * Получает длительность CSS-перехода элемента в миллисекундах.
313
+ * Учитывает `transition-duration` и `transition-delay`.
314
+ *
315
+ * @param {Element} element - DOM-элемент
316
+ * @returns {number} Длительность перехода в миллисекундах
317
+ * @example
318
+ * getTransitionDurationFromElement(div) → 300
319
+ */
320
+ const getTransitionDurationFromElement = (element) => {
243
321
  if (!element) {
244
- return 0
322
+ return 0;
245
323
  }
246
324
 
247
325
  // Get transition-duration of the element
248
- let { transitionDuration, transitionDelay } = window.getComputedStyle(element)
326
+ let { transitionDuration, transitionDelay } = window.getComputedStyle(element);
249
327
 
250
- const floatTransitionDuration = Number.parseFloat(transitionDuration)
251
- const floatTransitionDelay = Number.parseFloat(transitionDelay)
328
+ const floatTransitionDuration = Number.parseFloat(transitionDuration);
329
+ const floatTransitionDelay = Number.parseFloat(transitionDelay);
252
330
 
253
331
  // Return 0 if element or transition duration is not found
254
332
  if (!floatTransitionDuration && !floatTransitionDelay) {
255
- return 0
333
+ return 0;
256
334
  }
257
335
 
258
336
  // If multiple durations are defined, take the first
259
- transitionDuration = transitionDuration.split(',')[0]
260
- transitionDelay = transitionDelay.split(',')[0]
261
-
262
- return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER
263
- }
337
+ transitionDuration = transitionDuration.split(',')[0];
338
+ transitionDelay = transitionDelay.split(',')[0];
264
339
 
265
- const triggerTransitionEnd = element => {
266
- element.dispatchEvent(new Event(TRANSITION_END))
267
- }
340
+ return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;
341
+ };
268
342
 
269
343
  /**
270
- * Трюк для перезапуска анимации элемента
344
+ * Принудительно генерирует событие окончания перехода на элементе.
271
345
  *
272
- * @param {HTMLElement} element
273
- * @return void
346
+ * @param {Element} element - Элемент, на котором нужно вызвать событие
347
+ * @example
348
+ * triggerTransitionEnd(button)
349
+ */
350
+ const triggerTransitionEnd = (element) => {
351
+ element.dispatchEvent(new Event(TRANSITION_END));
352
+ };
353
+
354
+ /**
355
+ * Принудительный рефлоу (пересчёт макета) для перезапуска CSS-анимаций.
274
356
  *
275
- * @смотри https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation
357
+ * @param {HTMLElement} element - DOM-элемент
358
+ * @returns {void}
359
+ * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation
360
+ * @example
361
+ * reflow(myElement);
276
362
  */
277
- const reflow = element => {
278
- element.offsetHeight // eslint-disable-line no-unused-expressions
279
- }
363
+ const reflow = (element) => {
364
+ element.offsetHeight; // eslint-disable-line no-unused-expressions
365
+ };
280
366
 
281
367
  /**
282
- * Noop
368
+ * Пустая функция, используемая как заглушка.
369
+ *
370
+ * @returns {void}
371
+ * @example
372
+ * someFunc || noop
283
373
  */
284
374
  const noop = () => {};
285
375
 
286
376
  /**
287
- * Генерация случайной строки
377
+ * Генерирует случайную строку заданной длины.
378
+ *
379
+ * @param {number} [length=7] - Длина строки
380
+ * @returns {string} Случайная строка из латинских букв и цифр
381
+ * @example
382
+ * makeRandomString(5) → 'aB3xY'
288
383
  */
289
384
  function makeRandomString(length = 7) {
290
385
  let result = '';
@@ -299,56 +394,68 @@ function makeRandomString(length = 7) {
299
394
  }
300
395
 
301
396
  /**
302
- * Транслитерация символов с латиницы на кириллицу и обратно
303
- * @param text
304
- * @param enToRu
305
- * @returns {*}
397
+ * Транслитерация символов между кириллицей и латиницей.
398
+ *
399
+ * @param {string} text - Текст для транслитерации
400
+ * @param {boolean} [enToRu=true] - Направление: `true` — en→ru, `false` — ru→en
401
+ * @returns {string} Транслитерированный текст
402
+ * @example
403
+ * transliterate('privet', true) → 'привет'
404
+ * transliterate('привет', false) → 'privet'
306
405
  */
307
406
  function transliterate(text, enToRu) {
308
- let ru = "й ц у к е н г ш щ з х ъ ф ы в а п р о л д ж э я ч с м и т ь б ю".split(/ +/g);
309
- let en = "q w e r t y u i o p [ ] a s d f g h j k l ; ' z x c v b n m , .".split(/ +/g);
407
+ let ru = 'й ц у к е н г ш щ з х ъ ф ы в а п р о л д ж э я ч с м и т ь б ю'.split(/ +/g);
408
+ let en = 'q w e r t y u i o p [ ] a s d f g h j k l ; \' z x c v b n m , .'.split(/ +/g);
310
409
  let x;
311
410
 
312
411
  for (x = 0; x < ru.length; x++) {
313
412
  text = text.split(enToRu ? en[x] : ru[x]).join(enToRu ? ru[x] : en[x]);
314
- text = text.split(enToRu ? en[x].toUpperCase() : ru[x].toUpperCase()).join(enToRu ? ru[x].toUpperCase() : en[x].toUpperCase());
413
+ text = text
414
+ .split(enToRu ? en[x].toUpperCase() : ru[x].toUpperCase())
415
+ .join(enToRu ? ru[x].toUpperCase() : en[x].toUpperCase());
315
416
  }
316
417
 
317
418
  return text;
318
419
  }
319
420
 
320
421
  /**
321
- * Возвращает предыдущий/следующий элемент списка.
422
+ * Возвращает следующий или предыдущий элемент в списке.
423
+ * Поддерживает циклическое переключение.
322
424
  *
323
- * @param {array} list The list of elements
324
- * @param activeElement The active element
325
- * @param shouldGetNext Choose to get next or previous element
326
- * @param isCycleAllowed
327
- * @return {Element|elem} The proper element
425
+ * @param {Array<Element>} list - Массив DOM-элементов
426
+ * @param {Element} activeElement - Текущий активный элемент
427
+ * @param {boolean} shouldGetNext - `true` следующий, `false` — предыдущий
428
+ * @param {boolean} isCycleAllowed - Разрешить цикл (с конца к началу и наоборот)
429
+ * @returns {Element} Следующий/предыдущий элемент
430
+ * @example
431
+ * getNextActiveElement(items, current, true, true) → следующий элемент (с циклом)
328
432
  */
329
433
  const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {
330
- const listLength = list.length
331
- let index = list.indexOf(activeElement)
434
+ const listLength = list.length;
435
+ let index = list.indexOf(activeElement);
332
436
 
333
437
  // if the element does not exist in the list return an element
334
438
  // depending on the direction and if cycle is allowed
335
439
  if (index === -1) {
336
- return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]
440
+ return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];
337
441
  }
338
442
 
339
- index += shouldGetNext ? 1 : -1
443
+ index += shouldGetNext ? 1 : -1;
340
444
 
341
445
  if (isCycleAllowed) {
342
- index = (index + listLength) % listLength
446
+ index = (index + listLength) % listLength;
343
447
  }
344
448
 
345
- return list[Math.max(0, Math.min(index, listLength - 1))]
346
- }
449
+ return list[Math.max(0, Math.min(index, listLength - 1))];
450
+ };
347
451
 
348
452
  /**
349
- * Рекурсивный поиск самого глубокого последнего потомка
350
- * @param element
351
- * @returns {*}
453
+ * Рекурсивно возвращает самый глубокий последний дочерний элемент.
454
+ *
455
+ * @param {Element} element - Родительский элемент
456
+ * @returns {Element} Самый вложенный `lastElementChild`
457
+ * @example
458
+ * getDeepestLastChild(div) → <span>...</span>
352
459
  */
353
460
  function getDeepestLastChild(element) {
354
461
  let current = element;
@@ -361,8 +468,33 @@ function getDeepestLastChild(element) {
361
468
  }
362
469
 
363
470
  /**
471
+ * Проверяет, используется ли направление текста справа налево (RTL).
364
472
  *
473
+ * @returns {boolean} `true`, если `dir="rtl"`, иначе `false`
474
+ * @example
475
+ * isRTL() → true (если <html dir="rtl">)
365
476
  */
366
- const isRTL = () => document.documentElement.dir === 'rtl'
367
-
368
- export {getDeepestLastChild, isElement, isVisible, isDisabled, isObject, isEmptyObj, mergeDeepObject, removeElementArray, normalizeData, execute, executeAfterTransition, reflow, noop, makeRandomString, isRTL, transliterate, getElement, getNextActiveElement}
477
+ const isRTL = () => document.documentElement.dir === 'rtl';
478
+
479
+ export {
480
+ getDeepestLastChild,
481
+ isElement,
482
+ isVisible,
483
+ isDisabled,
484
+ isObject,
485
+ isEmptyObj,
486
+ mergeDeepObject,
487
+ removeElementArray,
488
+ normalizeData,
489
+ execute,
490
+ executeAfterTransition,
491
+ reflow,
492
+ noop,
493
+ makeRandomString,
494
+ isRTL,
495
+ transliterate,
496
+ getElement,
497
+ getNextActiveElement,
498
+ getTransitionDurationFromElement,
499
+ triggerTransitionEnd,
500
+ };
@@ -2,6 +2,7 @@
2
2
  @import "../../utils/scss/variables";
3
3
  @import "../../utils/scss/mixin";
4
4
  @import "../../utils/scss/animation";
5
+ @import "../../utils/scss/placement";
5
6
 
6
7
  .vg-backdrop {
7
8
  @include mix-vars('backdrop', $backdrop-map);