vgapp 0.7.7 → 0.7.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.
- package/CHANGELOG.md +15 -4
- package/app/modules/base-module.js +52 -17
- package/app/modules/module-fn.js +10 -100
- package/app/modules/vgdropdown/js/vgdropdown.js +104 -118
- package/app/modules/vgdropdown/scss/vgdropdown.scss +1 -2
- package/app/modules/vgformsender/js/hideshowpass.js +7 -4
- package/app/modules/vgformsender/js/vgformsender.js +343 -160
- package/app/modules/vgformsender/readme.md +221 -0
- package/app/modules/vgformsender/scss/vgformsender.scss +11 -3
- package/app/modules/vgnav/js/vgnav.js +98 -26
- package/app/modules/vgnav/scss/_placement.scss +8 -93
- package/app/modules/vgnav/scss/vgnav.scss +0 -1
- package/app/utils/js/components/ajax.js +215 -0
- package/app/utils/js/components/lang.js +82 -0
- package/app/utils/js/components/params.js +5 -0
- package/app/utils/js/components/placement.js +111 -108
- package/app/utils/js/components/templater.js +365 -33
- package/app/utils/js/functions.js +275 -143
- package/app/utils/scss/default.scss +1 -0
- package/app/utils/scss/placement.scss +72 -0
- package/app/utils/scss/variables.scss +10 -5
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/package.json +1 -1
- 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
|
-
*
|
|
8
|
-
*
|
|
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
|
-
*
|
|
22
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* @
|
|
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
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
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
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
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
|
|
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
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* @
|
|
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
|
-
*
|
|
124
|
-
*
|
|
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
|
-
*
|
|
157
|
-
* @param
|
|
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
|
-
*
|
|
166
|
-
*
|
|
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
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* @param
|
|
197
|
-
* @param
|
|
198
|
-
* @
|
|
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
|
-
*
|
|
206
|
-
* @
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
340
|
+
return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;
|
|
341
|
+
};
|
|
268
342
|
|
|
269
343
|
/**
|
|
270
|
-
*
|
|
344
|
+
* Принудительно генерирует событие окончания перехода на элементе.
|
|
271
345
|
*
|
|
272
|
-
* @param {
|
|
273
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
304
|
-
* @param
|
|
305
|
-
* @
|
|
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 =
|
|
309
|
-
let en =
|
|
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
|
|
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 {
|
|
324
|
-
* @param activeElement
|
|
325
|
-
* @param shouldGetNext
|
|
326
|
-
* @param isCycleAllowed
|
|
327
|
-
* @
|
|
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
|
-
*
|
|
351
|
-
* @
|
|
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 {
|
|
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
|
+
};
|