vgapp 0.7.9 → 0.8.1
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 +9 -0
- package/app/langs/en/buttons.json +16 -0
- package/app/langs/en/messages.json +32 -0
- package/app/langs/en/titles.json +6 -0
- package/app/langs/ru/buttons.json +16 -0
- package/app/langs/ru/messages.json +32 -0
- package/app/langs/ru/titles.json +6 -0
- package/app/modules/base-module.js +12 -1
- package/app/modules/module-fn.js +20 -9
- package/app/modules/vgalert/js/vgalert.js +12 -6
- package/app/modules/vgalert/readme.md +1 -1
- package/app/modules/vgcollapse/readme.md +1 -1
- package/app/modules/vgdropdown/js/vgdropdown.js +140 -38
- package/app/modules/vgdropdown/readme.md +225 -0
- package/app/modules/vgfiles/js/base.js +499 -0
- package/app/modules/vgfiles/js/droppable.js +159 -0
- package/app/modules/vgfiles/js/loader.js +389 -0
- package/app/modules/vgfiles/js/render.js +83 -0
- package/app/modules/vgfiles/js/sortable.js +155 -0
- package/app/modules/vgfiles/js/vgfiles.js +796 -280
- package/app/modules/vgfiles/readme.md +193 -0
- package/app/modules/vgfiles/scss/_animations.scss +18 -0
- package/app/modules/vgfiles/scss/_mixins.scss +73 -0
- package/app/modules/vgfiles/scss/_variables.scss +103 -26
- package/app/modules/vgfiles/scss/vgfiles.scss +573 -60
- package/app/modules/vgformsender/js/vgformsender.js +5 -1
- package/app/modules/vgformsender/readme.md +1 -1
- package/app/modules/vglawcookie/js/vglawcookie.js +96 -62
- package/app/modules/vglawcookie/readme.md +102 -0
- package/app/modules/vgloadmore/js/vgloadmore.js +212 -112
- package/app/modules/vgloadmore/readme.md +145 -0
- package/app/modules/vgsidebar/js/vgsidebar.js +6 -4
- package/app/utils/js/components/ajax.js +172 -122
- package/app/utils/js/components/animation.js +124 -39
- package/app/utils/js/components/backdrop.js +54 -31
- package/app/utils/js/components/lang.js +69 -88
- package/app/utils/js/components/params.js +34 -31
- package/app/utils/js/components/scrollbar.js +118 -67
- package/app/utils/js/components/templater.js +14 -4
- package/app/utils/js/dom/cookie.js +107 -64
- package/app/utils/js/dom/data.js +68 -20
- package/app/utils/js/dom/event.js +272 -239
- package/app/utils/js/dom/manipulator.js +135 -62
- package/app/utils/js/dom/selectors.js +134 -59
- package/app/utils/js/functions.js +183 -349
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/package.json +1 -1
- package/app/utils/js/components/overflow.js +0 -28
|
@@ -1,481 +1,315 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Все функции экспортируются для использования в других модулях.
|
|
2
|
+
* Утилиты: DOM, типы, массивы, анимации, объекты
|
|
3
|
+
* Экспортируются все функции.
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* @param {string} selector - CSS-селектор, который необходимо экранировать
|
|
13
|
-
* @returns {string} Экранированный селектор, пригодный для `document.querySelector`
|
|
14
|
-
* @example
|
|
15
|
-
* parseSelector('#user:input') → '#user\\:input'
|
|
7
|
+
* Проверяет, является ли объект DOM-элементом.
|
|
8
|
+
* @param {*} obj
|
|
9
|
+
* @returns {boolean}
|
|
16
10
|
*/
|
|
17
|
-
const
|
|
18
|
-
if (selector && window.CSS && window.CSS.escape) {
|
|
19
|
-
// document.querySelector needs escaping to handle IDs (html5+) containing for instance /
|
|
20
|
-
selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return selector;
|
|
24
|
-
};
|
|
11
|
+
const isElement = (obj) => obj?.nodeType === Node.ELEMENT_NODE;
|
|
25
12
|
|
|
26
13
|
/**
|
|
27
|
-
* Проверяет, является ли
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* @param {Object} obj - Объект для проверки
|
|
31
|
-
* @returns {boolean} `true`, если объект пуст или не является объектом, иначе `false`
|
|
32
|
-
* @example
|
|
33
|
-
* isEmptyObj({}) → true
|
|
34
|
-
* isEmptyObj({ a: 1 }) → false
|
|
14
|
+
* Проверяет, является ли значение объектом (не null, включая массивы).
|
|
15
|
+
* @param {*} obj
|
|
16
|
+
* @returns {boolean}
|
|
35
17
|
*/
|
|
36
|
-
|
|
37
|
-
for (let prop in obj) {
|
|
38
|
-
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
18
|
+
const isObject = (obj) => obj && typeof obj === 'object';
|
|
45
19
|
|
|
46
20
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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
|
|
21
|
+
* Проверяет, пуст ли объект (нет собственных перечисляемых ключей).
|
|
22
|
+
* @param {Object} obj
|
|
23
|
+
* @returns {boolean}
|
|
55
24
|
*/
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (typeof object === 'string' && object.length > 0) {
|
|
63
|
-
return document.querySelector(parseSelector(object));
|
|
25
|
+
const isEmptyObj = (obj) => {
|
|
26
|
+
if (!isObject(obj)) return true;
|
|
27
|
+
for (const key in obj) {
|
|
28
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) return false;
|
|
64
29
|
}
|
|
65
|
-
|
|
66
|
-
return null;
|
|
30
|
+
return true;
|
|
67
31
|
};
|
|
68
32
|
|
|
69
33
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* @
|
|
73
|
-
* @returns {boolean} `true`, если объект является DOM-элементом, иначе `false`
|
|
74
|
-
* @example
|
|
75
|
-
* isElement(document.body) → true
|
|
76
|
-
* isElement({}) → false
|
|
34
|
+
* Возвращает DOM-элемент по селектору, элементу или jQuery-объекту.
|
|
35
|
+
* @param {string|Element|jQuery} selector
|
|
36
|
+
* @returns {Element|null}
|
|
77
37
|
*/
|
|
78
|
-
const
|
|
79
|
-
if (
|
|
80
|
-
|
|
38
|
+
const getElement = (selector) => {
|
|
39
|
+
if (isElement(selector)) return selector;
|
|
40
|
+
if (selector?.jquery) return selector[0] || null;
|
|
41
|
+
if (typeof selector === 'string' && selector.trim() !== '') {
|
|
42
|
+
try {
|
|
43
|
+
return document.querySelector(selector);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.warn('Invalid selector:', selector);
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
81
48
|
}
|
|
82
|
-
|
|
83
|
-
return typeof object.nodeType !== 'undefined';
|
|
49
|
+
return null;
|
|
84
50
|
};
|
|
85
51
|
|
|
86
52
|
/**
|
|
87
|
-
* Проверяет, отключён ли
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* @param {Element} element - DOM-элемент для проверки
|
|
91
|
-
* @returns {boolean} `true`, если элемент отключён, иначе `false`
|
|
92
|
-
* @example
|
|
93
|
-
* isDisabled(document.querySelector('[disabled]')) → true
|
|
53
|
+
* Проверяет, отключён ли элемент (по классу .disabled, атрибуту или свойству).
|
|
54
|
+
* @param {Element} el
|
|
55
|
+
* @returns {boolean}
|
|
94
56
|
*/
|
|
95
|
-
const isDisabled = (
|
|
96
|
-
if (!
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (element.classList.contains('disabled')) {
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (typeof element.disabled !== 'undefined') {
|
|
105
|
-
return element.disabled;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';
|
|
57
|
+
const isDisabled = (el) => {
|
|
58
|
+
if (!isElement(el)) return true;
|
|
59
|
+
if (el.classList.contains('disabled')) return true;
|
|
60
|
+
if (typeof el.disabled !== 'undefined') return !!el.disabled;
|
|
61
|
+
return el.hasAttribute('disabled') && el.getAttribute('disabled') !== 'false';
|
|
109
62
|
};
|
|
110
63
|
|
|
111
64
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* @param {Element} element - DOM-элемент для проверки видимости
|
|
116
|
-
* @returns {boolean} `true`, если элемент видим, иначе `false`
|
|
117
|
-
* @example
|
|
118
|
-
* isVisible(button) → true
|
|
65
|
+
* Проверяет видимость элемента (размер, visibility, details).
|
|
66
|
+
* @param {Element} el
|
|
67
|
+
* @returns {boolean}
|
|
119
68
|
*/
|
|
120
|
-
|
|
121
|
-
if (!isElement(
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible';
|
|
126
|
-
const closedDetails = element.closest('details:not([open])');
|
|
127
|
-
|
|
128
|
-
if (!closedDetails) {
|
|
129
|
-
return elementIsVisible;
|
|
130
|
-
}
|
|
69
|
+
const isVisible = (el) => {
|
|
70
|
+
if (!isElement(el) || el.getClientRects().length === 0) return false;
|
|
131
71
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (summary && summary.parentNode !== closedDetails) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
72
|
+
const style = getComputedStyle(el);
|
|
73
|
+
if (style.visibility === 'hidden') return false;
|
|
137
74
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
}
|
|
75
|
+
const parentDetails = el.closest('details:not([open])');
|
|
76
|
+
if (!parentDetails) return true;
|
|
142
77
|
|
|
143
|
-
|
|
144
|
-
|
|
78
|
+
const summary = el.closest('summary');
|
|
79
|
+
if (summary && summary.parentNode === parentDetails) return true;
|
|
145
80
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
*
|
|
149
|
-
* @param {*} obj - Значение для проверки
|
|
150
|
-
* @returns {boolean} `true`, если значение — объект, иначе `false`
|
|
151
|
-
* @example
|
|
152
|
-
* isObject({}) → true
|
|
153
|
-
* isObject([]) → true
|
|
154
|
-
* isObject(null) → false
|
|
155
|
-
*/
|
|
156
|
-
function isObject(obj) {
|
|
157
|
-
return obj && typeof obj === 'object';
|
|
158
|
-
}
|
|
81
|
+
return false;
|
|
82
|
+
};
|
|
159
83
|
|
|
160
84
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* @param {string|*} value - Значение для нормализации
|
|
165
|
-
* @returns {*} Нормализованное значение: boolean, number, null, object, string и т.д.
|
|
166
|
-
* @example
|
|
167
|
-
* normalizeData('true') → true
|
|
168
|
-
* normalizeData('{"a":1}') → { a: 1 }
|
|
85
|
+
* Преобразует строку в соответствующий тип: boolean, number, null, объект.
|
|
86
|
+
* @param {*} value
|
|
87
|
+
* @returns {*}
|
|
169
88
|
*/
|
|
170
|
-
|
|
171
|
-
if (value === 'true')
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (value === '
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (value === Number(value).toString()) {
|
|
180
|
-
return Number(value);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (value === '' || value === 'null') {
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (typeof value !== 'string') {
|
|
188
|
-
return value;
|
|
189
|
-
}
|
|
89
|
+
const normalizeData = (value) => {
|
|
90
|
+
if (value === 'true') return true;
|
|
91
|
+
if (value === 'false') return false;
|
|
92
|
+
if (value === 'null') return null;
|
|
93
|
+
if (value === '') return null;
|
|
94
|
+
if (typeof value === 'number' || value === Number(value)) return Number(value);
|
|
95
|
+
if (typeof value !== 'string') return value;
|
|
190
96
|
|
|
191
97
|
try {
|
|
192
98
|
return JSON.parse(decodeURIComponent(value));
|
|
193
99
|
} catch {
|
|
194
100
|
return value;
|
|
195
101
|
}
|
|
196
|
-
}
|
|
102
|
+
};
|
|
197
103
|
|
|
198
104
|
/**
|
|
199
|
-
* Удаляет
|
|
200
|
-
*
|
|
201
|
-
* @param {Array}
|
|
202
|
-
* @
|
|
203
|
-
* @returns {Array} Новый массив без указанных элементов
|
|
204
|
-
* @example
|
|
205
|
-
* removeElementArray([1, 2, 3], [2]) → [1, 3]
|
|
105
|
+
* Удаляет элементы из массива.
|
|
106
|
+
* @param {Array} arr
|
|
107
|
+
* @param {Array} toRemove
|
|
108
|
+
* @returns {Array}
|
|
206
109
|
*/
|
|
207
|
-
|
|
208
|
-
return arr.filter((item) => !el.includes(item));
|
|
209
|
-
}
|
|
110
|
+
const removeElementArray = (arr, toRemove) => arr.filter((item) => !toRemove.includes(item));
|
|
210
111
|
|
|
211
112
|
/**
|
|
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 } }
|
|
113
|
+
* Глубокое слияние объектов с конкатенацией массивов.
|
|
114
|
+
* @param {...Object} sources
|
|
115
|
+
* @returns {Object}
|
|
220
116
|
*/
|
|
221
|
-
|
|
222
|
-
const
|
|
117
|
+
const mergeDeepObject = (...sources) => {
|
|
118
|
+
const isObj = (o) => o && typeof o === 'object';
|
|
223
119
|
|
|
224
|
-
|
|
120
|
+
return sources.reduce((acc, obj) => {
|
|
121
|
+
if (!isObj(obj)) return acc;
|
|
225
122
|
|
|
226
|
-
return objects.reduce((prev, obj) => {
|
|
227
123
|
Object.keys(obj).forEach((key) => {
|
|
228
|
-
const
|
|
229
|
-
const
|
|
124
|
+
const src = acc[key];
|
|
125
|
+
const val = obj[key];
|
|
230
126
|
|
|
231
|
-
if (Array.isArray(
|
|
232
|
-
|
|
233
|
-
} else if (
|
|
234
|
-
|
|
127
|
+
if (Array.isArray(src) && Array.isArray(val)) {
|
|
128
|
+
acc[key] = src.concat(val);
|
|
129
|
+
} else if (isObj(src) && isObj(val)) {
|
|
130
|
+
acc[key] = mergeDeepObject(src, val);
|
|
235
131
|
} else {
|
|
236
|
-
|
|
132
|
+
acc[key] = val;
|
|
237
133
|
}
|
|
238
134
|
});
|
|
239
135
|
|
|
240
|
-
return
|
|
136
|
+
return acc;
|
|
241
137
|
}, {});
|
|
242
|
-
}
|
|
138
|
+
};
|
|
243
139
|
|
|
244
140
|
/**
|
|
245
|
-
* Выполняет
|
|
246
|
-
*
|
|
247
|
-
* @param {
|
|
248
|
-
* @param {
|
|
249
|
-
* @
|
|
250
|
-
* @returns {*} Результат выполнения колбэка или `defaultValue`
|
|
251
|
-
* @example
|
|
252
|
-
* execute(() => 'hi') → 'hi'
|
|
253
|
-
* execute('notFunc', [], 'fallback') → 'fallback'
|
|
141
|
+
* Выполняет функцию с аргументами или возвращает значение по умолчанию.
|
|
142
|
+
* @param {Function|*} fn
|
|
143
|
+
* @param {Array} args
|
|
144
|
+
* @param {*} defaultValue
|
|
145
|
+
* @returns {*}
|
|
254
146
|
*/
|
|
255
|
-
|
|
256
|
-
return typeof
|
|
257
|
-
}
|
|
147
|
+
const execute = (fn, args = [], defaultValue = fn) => {
|
|
148
|
+
return typeof fn === 'function' ? fn(...args) : defaultValue;
|
|
149
|
+
};
|
|
258
150
|
|
|
259
151
|
/**
|
|
260
|
-
* Событие
|
|
152
|
+
* Событие завершения CSS-перехода.
|
|
261
153
|
* @type {string}
|
|
262
154
|
*/
|
|
263
155
|
const TRANSITION_END = 'transitionend';
|
|
264
156
|
|
|
265
157
|
/**
|
|
266
|
-
* Множитель для
|
|
158
|
+
* Множитель для мс.
|
|
267
159
|
* @type {number}
|
|
268
160
|
*/
|
|
269
161
|
const MILLISECONDS_MULTIPLIER = 1000;
|
|
270
162
|
|
|
271
163
|
/**
|
|
272
|
-
* Выполняет колбэк после
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
* @param {
|
|
276
|
-
* @param {
|
|
277
|
-
* @param {boolean} [waitForTransition=true] - Ожидать ли переход
|
|
278
|
-
* @param {number} [timeOutMs] - Фиксированная задержка (вместо авто-определения)
|
|
279
|
-
* @example
|
|
280
|
-
* executeAfterTransition(() => console.log('done'), button)
|
|
164
|
+
* Выполняет колбэк после CSS-перехода с fallback на setTimeout.
|
|
165
|
+
* @param {Function} callback
|
|
166
|
+
* @param {Element} element
|
|
167
|
+
* @param {boolean} waitForTransition
|
|
168
|
+
* @param {number} [timeoutMs]
|
|
281
169
|
*/
|
|
282
|
-
|
|
170
|
+
const executeAfterTransition = (callback, element, waitForTransition = true, timeoutMs) => {
|
|
283
171
|
if (!waitForTransition) {
|
|
284
172
|
execute(callback);
|
|
285
173
|
return;
|
|
286
174
|
}
|
|
287
175
|
|
|
288
|
-
const
|
|
289
|
-
const emulatedDuration = timeOutMs ? timeOutMs : getTransitionDurationFromElement(transitionElement) + durationPadding;
|
|
290
|
-
|
|
176
|
+
const duration = timeoutMs ?? getTransitionDurationFromElement(element) + 5;
|
|
291
177
|
let called = false;
|
|
292
178
|
|
|
293
|
-
const handler = (
|
|
294
|
-
if (target
|
|
295
|
-
|
|
179
|
+
const handler = (e) => {
|
|
180
|
+
if (e.target === element) {
|
|
181
|
+
called = true;
|
|
182
|
+
element.removeEventListener(TRANSITION_END, handler);
|
|
183
|
+
execute(callback);
|
|
296
184
|
}
|
|
297
|
-
|
|
298
|
-
called = true;
|
|
299
|
-
transitionElement.removeEventListener(TRANSITION_END, handler);
|
|
300
|
-
execute(callback);
|
|
301
185
|
};
|
|
302
186
|
|
|
303
|
-
|
|
187
|
+
element.addEventListener(TRANSITION_END, handler);
|
|
304
188
|
setTimeout(() => {
|
|
305
|
-
if (!called)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}, emulatedDuration);
|
|
309
|
-
}
|
|
189
|
+
if (!called) triggerTransitionEnd(element);
|
|
190
|
+
}, duration);
|
|
191
|
+
};
|
|
310
192
|
|
|
311
193
|
/**
|
|
312
|
-
* Получает длительность CSS-перехода элемента в
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* @param {Element} element - DOM-элемент
|
|
316
|
-
* @returns {number} Длительность перехода в миллисекундах
|
|
317
|
-
* @example
|
|
318
|
-
* getTransitionDurationFromElement(div) → 300
|
|
194
|
+
* Получает длительность CSS-перехода элемента в мс.
|
|
195
|
+
* @param {Element} element
|
|
196
|
+
* @returns {number}
|
|
319
197
|
*/
|
|
320
198
|
const getTransitionDurationFromElement = (element) => {
|
|
321
|
-
if (!element)
|
|
322
|
-
|
|
323
|
-
}
|
|
199
|
+
if (!element) return 0;
|
|
200
|
+
const { transitionDuration, transitionDelay } = getComputedStyle(element);
|
|
324
201
|
|
|
325
|
-
|
|
326
|
-
|
|
202
|
+
const durations = transitionDuration.split(', ');
|
|
203
|
+
const delays = transitionDelay.split(', ');
|
|
327
204
|
|
|
328
|
-
const
|
|
329
|
-
const
|
|
205
|
+
const duration = Number.parseFloat(durations[0]) || 0;
|
|
206
|
+
const delay = Number.parseFloat(delays[0]) || 0;
|
|
330
207
|
|
|
331
|
-
|
|
332
|
-
if (!floatTransitionDuration && !floatTransitionDelay) {
|
|
333
|
-
return 0;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// If multiple durations are defined, take the first
|
|
337
|
-
transitionDuration = transitionDuration.split(',')[0];
|
|
338
|
-
transitionDelay = transitionDelay.split(',')[0];
|
|
339
|
-
|
|
340
|
-
return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;
|
|
208
|
+
return (duration + delay) * MILLISECONDS_MULTIPLIER;
|
|
341
209
|
};
|
|
342
210
|
|
|
343
211
|
/**
|
|
344
|
-
* Принудительно
|
|
345
|
-
*
|
|
346
|
-
* @param {Element} element - Элемент, на котором нужно вызвать событие
|
|
347
|
-
* @example
|
|
348
|
-
* triggerTransitionEnd(button)
|
|
212
|
+
* Принудительно вызывает событие перехода.
|
|
213
|
+
* @param {Element} element
|
|
349
214
|
*/
|
|
350
215
|
const triggerTransitionEnd = (element) => {
|
|
351
216
|
element.dispatchEvent(new Event(TRANSITION_END));
|
|
352
217
|
};
|
|
353
218
|
|
|
354
219
|
/**
|
|
355
|
-
* Принудительный рефлоу (
|
|
356
|
-
*
|
|
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);
|
|
220
|
+
* Принудительный рефлоу (для перезапуска анимаций).
|
|
221
|
+
* @param {HTMLElement} element
|
|
362
222
|
*/
|
|
363
223
|
const reflow = (element) => {
|
|
364
|
-
|
|
224
|
+
/* eslint-disable no-unused-expressions */
|
|
225
|
+
element.offsetHeight;
|
|
226
|
+
/* eslint-enable no-unused-expressions */
|
|
365
227
|
};
|
|
366
228
|
|
|
367
229
|
/**
|
|
368
|
-
* Пустая
|
|
369
|
-
*
|
|
370
|
-
* @returns {void}
|
|
371
|
-
* @example
|
|
372
|
-
* someFunc || noop
|
|
230
|
+
* Пустая функция-заглушка.
|
|
373
231
|
*/
|
|
374
232
|
const noop = () => {};
|
|
375
233
|
|
|
376
234
|
/**
|
|
377
|
-
* Генерирует случайную
|
|
378
|
-
*
|
|
379
|
-
* @
|
|
380
|
-
* @returns {string} Случайная строка из латинских букв и цифр
|
|
381
|
-
* @example
|
|
382
|
-
* makeRandomString(5) → 'aB3xY'
|
|
235
|
+
* Генерирует случайную строку.
|
|
236
|
+
* @param {number} length
|
|
237
|
+
* @returns {string}
|
|
383
238
|
*/
|
|
384
|
-
|
|
239
|
+
const makeRandomString = (length = 7) => {
|
|
240
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
385
241
|
let result = '';
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
let counter = 0;
|
|
389
|
-
while (counter < length) {
|
|
390
|
-
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
391
|
-
counter += 1;
|
|
242
|
+
for (let i = 0; i < length; i++) {
|
|
243
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
392
244
|
}
|
|
393
245
|
return result;
|
|
394
|
-
}
|
|
246
|
+
};
|
|
395
247
|
|
|
396
248
|
/**
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
* @param {
|
|
400
|
-
* @
|
|
401
|
-
* @returns {string} Транслитерированный текст
|
|
402
|
-
* @example
|
|
403
|
-
* transliterate('privet', true) → 'привет'
|
|
404
|
-
* transliterate('привет', false) → 'privet'
|
|
249
|
+
* Транслитерация: en ⇄ ru.
|
|
250
|
+
* @param {string} text
|
|
251
|
+
* @param {boolean} [enToRu=true] - true: en→ru, false: ru→en
|
|
252
|
+
* @returns {string}
|
|
405
253
|
*/
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
let x;
|
|
410
|
-
|
|
411
|
-
for (x = 0; x < ru.length; x++) {
|
|
412
|
-
text = text.split(enToRu ? en[x] : ru[x]).join(enToRu ? ru[x] : en[x]);
|
|
413
|
-
text = text
|
|
414
|
-
.split(enToRu ? en[x].toUpperCase() : ru[x].toUpperCase())
|
|
415
|
-
.join(enToRu ? ru[x].toUpperCase() : en[x].toUpperCase());
|
|
416
|
-
}
|
|
254
|
+
const transliterate = (text, enToRu = true) => {
|
|
255
|
+
const from = '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(' ');
|
|
256
|
+
const to = 'й ц у к е н г ш щ з х ъ ф ы в а п р о л д ж э я ч с м и т ь б ю'.split(' ');
|
|
417
257
|
|
|
418
|
-
|
|
419
|
-
|
|
258
|
+
const src = enToRu ? from : to;
|
|
259
|
+
const dst = enToRu ? to : from;
|
|
420
260
|
|
|
421
|
-
|
|
422
|
-
* Возвращает следующий или предыдущий элемент в списке.
|
|
423
|
-
* Поддерживает циклическое переключение.
|
|
424
|
-
*
|
|
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) → следующий элемент (с циклом)
|
|
432
|
-
*/
|
|
433
|
-
const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {
|
|
434
|
-
const listLength = list.length;
|
|
435
|
-
let index = list.indexOf(activeElement);
|
|
436
|
-
|
|
437
|
-
// if the element does not exist in the list return an element
|
|
438
|
-
// depending on the direction and if cycle is allowed
|
|
439
|
-
if (index === -1) {
|
|
440
|
-
return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];
|
|
441
|
-
}
|
|
261
|
+
let result = text;
|
|
442
262
|
|
|
443
|
-
|
|
263
|
+
for (let i = 0; i < src.length; i++) {
|
|
264
|
+
const srcLower = src[i];
|
|
265
|
+
const dstLower = dst[i];
|
|
266
|
+
const srcUpper = src[i].toUpperCase();
|
|
267
|
+
const dstUpper = dst[i].toUpperCase();
|
|
444
268
|
|
|
445
|
-
|
|
446
|
-
index = (index + listLength) % listLength;
|
|
269
|
+
result = result.split(srcLower).join(dstLower).split(srcUpper).join(dstUpper);
|
|
447
270
|
}
|
|
448
271
|
|
|
449
|
-
return
|
|
272
|
+
return result;
|
|
450
273
|
};
|
|
451
274
|
|
|
452
275
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
* @param {Element}
|
|
456
|
-
* @
|
|
457
|
-
* @
|
|
458
|
-
*
|
|
276
|
+
* Получает следующий/предыдущий элемент с циклом.
|
|
277
|
+
* @param {Array<Element>} list
|
|
278
|
+
* @param {Element} active
|
|
279
|
+
* @param {boolean} getNext
|
|
280
|
+
* @param {boolean} cycle
|
|
281
|
+
* @returns {Element}
|
|
459
282
|
*/
|
|
460
|
-
|
|
461
|
-
|
|
283
|
+
const getNextActiveElement = (list, active, getNext, cycle) => {
|
|
284
|
+
const len = list.length;
|
|
285
|
+
let idx = list.indexOf(active);
|
|
462
286
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
287
|
+
if (idx === -1) return getNext || !cycle ? list[0] : list[len - 1];
|
|
288
|
+
|
|
289
|
+
idx += getNext ? 1 : -1;
|
|
290
|
+
if (cycle) idx = ((idx % len) + len) % len;
|
|
291
|
+
|
|
292
|
+
return list[Math.max(0, Math.min(idx, len - 1))];
|
|
293
|
+
};
|
|
466
294
|
|
|
467
|
-
|
|
468
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Возвращает самый глубокий последний дочерний элемент.
|
|
297
|
+
* @param {Element} el
|
|
298
|
+
* @returns {Element}
|
|
299
|
+
*/
|
|
300
|
+
const getDeepestLastChild = (el) => {
|
|
301
|
+
let node = el;
|
|
302
|
+
while (node.lastElementChild) node = node.lastElementChild;
|
|
303
|
+
return node;
|
|
304
|
+
};
|
|
469
305
|
|
|
470
306
|
/**
|
|
471
|
-
* Проверяет,
|
|
472
|
-
*
|
|
473
|
-
* @returns {boolean} `true`, если `dir="rtl"`, иначе `false`
|
|
474
|
-
* @example
|
|
475
|
-
* isRTL() → true (если <html dir="rtl">)
|
|
307
|
+
* Проверяет, включён ли RTL.
|
|
308
|
+
* @returns {boolean}
|
|
476
309
|
*/
|
|
477
310
|
const isRTL = () => document.documentElement.dir === 'rtl';
|
|
478
311
|
|
|
312
|
+
// экспорт
|
|
479
313
|
export {
|
|
480
314
|
getDeepestLastChild,
|
|
481
315
|
isElement,
|