vgapp 0.7.8 → 0.8.0
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/LICENSE +22 -0
- package/app/langs/en/buttons.json +10 -0
- package/app/langs/en/messages.json +32 -0
- package/app/langs/en/titles.json +6 -0
- package/app/langs/ru/buttons.json +10 -0
- package/app/langs/ru/messages.json +32 -0
- package/app/langs/ru/titles.json +6 -0
- package/app/modules/base-module.js +23 -2
- package/app/modules/module-fn.js +20 -9
- package/app/modules/vgalert/js/vgalert.js +362 -214
- package/app/modules/vgalert/readme.md +242 -0
- package/app/modules/vgcollapse/js/vgcollapse.js +216 -62
- package/app/modules/vgcollapse/readme.md +56 -0
- package/app/modules/vgcollapse/scss/_variables.scss +5 -0
- package/app/modules/vgcollapse/scss/vgcollapse.scss +41 -0
- 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 +30 -1
- package/app/modules/vglawcookie/js/vglawcookie.js +96 -62
- package/app/modules/vglawcookie/readme.md +102 -0
- package/app/modules/vgsidebar/js/vgsidebar.js +6 -4
- package/app/utils/js/components/ajax.js +176 -104
- 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 +71 -64
- 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/index.scss +3 -0
- package/package.json +1 -1
- package/app/utils/js/components/overflow.js +0 -28
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {mergeDeepObject, noop, normalizeData} from "../functions";
|
|
1
|
+
import { mergeDeepObject, noop, normalizeData } from "../functions";
|
|
2
2
|
|
|
3
3
|
class Ajax {
|
|
4
4
|
/**
|
|
@@ -10,25 +10,24 @@ class Ajax {
|
|
|
10
10
|
* @param {string} options._token - Токен (авто-чтение из meta)
|
|
11
11
|
*/
|
|
12
12
|
constructor(options = {}) {
|
|
13
|
-
this.baseUrl = options.baseUrl ||
|
|
13
|
+
this.baseUrl = options.baseUrl || "";
|
|
14
14
|
this.defaultHeaders = {
|
|
15
|
-
|
|
16
|
-
...options.headers
|
|
15
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
16
|
+
...options.headers,
|
|
17
17
|
};
|
|
18
18
|
this.withCredentials = options.withCredentials || false;
|
|
19
19
|
this.csrfToken = options._token || this._getCsrfToken();
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Получение
|
|
23
|
+
* Получение CSRF-токена из тега meta
|
|
24
24
|
* @returns {string}
|
|
25
|
-
* @private
|
|
26
25
|
*/
|
|
27
26
|
_getCsrfToken() {
|
|
28
27
|
const meta = document.querySelector('meta[name="csrf-token"]');
|
|
29
|
-
if (meta) return meta.getAttribute(
|
|
28
|
+
if (meta) return meta.getAttribute("content");
|
|
30
29
|
console.warn('CSRF-токен не найден в <meta name="csrf-token">');
|
|
31
|
-
return
|
|
30
|
+
return "";
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
/**
|
|
@@ -36,179 +35,252 @@ class Ajax {
|
|
|
36
35
|
* @param {string} url
|
|
37
36
|
* @param {Object} options
|
|
38
37
|
* @param {'GET'|'POST'|'PUT'|'DELETE'|'PATCH'} options.method
|
|
39
|
-
* @param {Object|FormData} options.body
|
|
40
|
-
* @param {Object} options.headers
|
|
41
|
-
* @param {
|
|
38
|
+
* @param {Object|FormData} options.body
|
|
39
|
+
* @param {Object} options.headers
|
|
40
|
+
* @param {AbortSignal} [options.signal] - Для отмены запроса
|
|
41
|
+
* @param {Function} [options.onProgress] - Только для POST/PUT с FormData
|
|
42
42
|
* @param {Function} options.onSuccess
|
|
43
43
|
* @param {Function} options.onError
|
|
44
|
-
* @param {Function} options.onUploadStart
|
|
45
|
-
* @param {Function} options.onUploadEnd
|
|
44
|
+
* @param {Function} [options.onUploadStart]
|
|
45
|
+
* @param {Function} [options.onUploadEnd]
|
|
46
46
|
*/
|
|
47
47
|
request(url, {
|
|
48
|
-
method =
|
|
48
|
+
method = "GET",
|
|
49
49
|
body = null,
|
|
50
50
|
headers = {},
|
|
51
|
+
signal = null,
|
|
51
52
|
onProgress = null,
|
|
52
|
-
onSuccess =
|
|
53
|
-
onError =
|
|
54
|
-
onUploadStart =
|
|
55
|
-
onUploadEnd =
|
|
53
|
+
onSuccess = noop,
|
|
54
|
+
onError = noop,
|
|
55
|
+
onUploadStart = noop,
|
|
56
|
+
onUploadEnd = noop,
|
|
56
57
|
} = {}) {
|
|
57
58
|
const fullUrl = this.baseUrl + url;
|
|
58
59
|
const isFormData = body instanceof FormData;
|
|
59
60
|
const requestHeaders = { ...this.defaultHeaders, ...headers };
|
|
60
|
-
const
|
|
61
|
+
const isGet = method.toUpperCase() === "GET";
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
_token: this.csrfToken
|
|
65
|
-
})
|
|
66
|
-
}
|
|
63
|
+
// Удаление тела для GET
|
|
64
|
+
if (isGet) body = null;
|
|
67
65
|
|
|
68
|
-
//
|
|
69
|
-
if (!
|
|
70
|
-
|
|
66
|
+
// Установка CSRF токена: в body, если не FormData
|
|
67
|
+
if (!isGet && !isFormData && this.csrfToken) {
|
|
68
|
+
if (!body) body = {};
|
|
69
|
+
if (typeof body === "object" && !Array.isArray(body)) {
|
|
70
|
+
body._token = this.csrfToken;
|
|
71
|
+
}
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
//
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
method,
|
|
77
|
-
headers: requestHeaders,
|
|
78
|
-
withCredentials: this.withCredentials
|
|
79
|
-
}, onSuccess, onError);
|
|
74
|
+
// Content-Type только для JSON
|
|
75
|
+
if (!isFormData && !("Content-Type" in headers)) {
|
|
76
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
80
77
|
}
|
|
81
78
|
|
|
82
|
-
//
|
|
79
|
+
// Если нужно отслеживать прогресс или FormData — используем XHR
|
|
83
80
|
if (isFormData || onProgress) {
|
|
84
81
|
return this._makeXHR({
|
|
85
82
|
method,
|
|
86
83
|
url: fullUrl,
|
|
87
84
|
body,
|
|
88
85
|
headers: requestHeaders,
|
|
86
|
+
signal,
|
|
89
87
|
onProgress,
|
|
90
88
|
onSuccess,
|
|
91
89
|
onError,
|
|
92
90
|
onUploadStart,
|
|
93
|
-
onUploadEnd
|
|
91
|
+
onUploadEnd,
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
return this._makeFetch({
|
|
95
|
+
url: fullUrl,
|
|
96
|
+
method,
|
|
97
|
+
body: isGet ? undefined : this._serializeBody(body),
|
|
98
|
+
headers: requestHeaders,
|
|
99
|
+
signal,
|
|
100
|
+
withCredentials: this.withCredentials,
|
|
101
|
+
onSuccess,
|
|
102
|
+
onError,
|
|
94
103
|
});
|
|
95
104
|
}
|
|
96
|
-
|
|
97
|
-
// Остальные случаи — fetch
|
|
98
|
-
return this._makeFetch(fullUrl, mergeDeepObject({
|
|
99
|
-
method,
|
|
100
|
-
headers: requestHeaders,
|
|
101
|
-
withCredentials: this.withCredentials
|
|
102
|
-
}, token), onSuccess, onError);
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
/**
|
|
106
|
-
*
|
|
108
|
+
* fetch-реализация (для JSON)
|
|
107
109
|
*/
|
|
108
|
-
_makeFetch(
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
_makeFetch({
|
|
111
|
+
url,
|
|
112
|
+
method,
|
|
113
|
+
body,
|
|
114
|
+
headers,
|
|
115
|
+
signal,
|
|
116
|
+
withCredentials,
|
|
117
|
+
onSuccess,
|
|
118
|
+
onError,
|
|
119
|
+
}) {
|
|
120
|
+
const config = {
|
|
121
|
+
method,
|
|
122
|
+
headers,
|
|
123
|
+
signal,
|
|
124
|
+
withCredentials,
|
|
125
|
+
...(body !== undefined && { body }),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
fetch(url, config)
|
|
129
|
+
.then((response) => {
|
|
130
|
+
const contentType = response.headers.get("content-type");
|
|
131
|
+
const isJson = contentType && contentType.includes("application/json");
|
|
132
|
+
|
|
111
133
|
if (!response.ok) {
|
|
112
|
-
|
|
134
|
+
return Promise.reject(
|
|
135
|
+
normalizeData({
|
|
136
|
+
code: response.status,
|
|
137
|
+
response: isJson
|
|
138
|
+
? response.json().catch(() => response.text())
|
|
139
|
+
: response.text(),
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let data = { code: response.status };
|
|
145
|
+
if (isJson) {
|
|
146
|
+
data.response = response.json();
|
|
147
|
+
} else {
|
|
148
|
+
data.response = response.text();
|
|
113
149
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
150
|
+
|
|
151
|
+
return data;
|
|
152
|
+
})
|
|
153
|
+
.then((data) => {
|
|
154
|
+
if (data && data.response instanceof Promise) {
|
|
155
|
+
data.response.then(
|
|
156
|
+
(resolved) => onSuccess({ ...data, response: resolved }),
|
|
157
|
+
() => {}
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
console.log(data)
|
|
161
|
+
onSuccess(data);
|
|
117
162
|
}
|
|
118
|
-
return response.text();
|
|
119
163
|
})
|
|
120
|
-
.
|
|
121
|
-
|
|
164
|
+
.catch((error) => {
|
|
165
|
+
if (error.name === "AbortError") return; // отмена — не ошибка
|
|
166
|
+
|
|
167
|
+
if (error && error.response instanceof Promise) {
|
|
168
|
+
error.response.then((errData) => {
|
|
169
|
+
onError({ ...error, response: errData });
|
|
170
|
+
}, () => {
|
|
171
|
+
onError({ ...error, response: "Request failed" });
|
|
172
|
+
});
|
|
173
|
+
} else {
|
|
174
|
+
onError(error);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
122
177
|
}
|
|
123
178
|
|
|
124
179
|
/**
|
|
125
|
-
*
|
|
180
|
+
* XHR-реализация (с прогрессом и AbortController)
|
|
126
181
|
*/
|
|
127
182
|
_makeXHR({
|
|
128
183
|
method,
|
|
129
184
|
url,
|
|
130
185
|
body,
|
|
131
186
|
headers,
|
|
187
|
+
signal,
|
|
132
188
|
onProgress,
|
|
133
189
|
onSuccess,
|
|
134
190
|
onError,
|
|
135
191
|
onUploadStart,
|
|
136
|
-
onUploadEnd
|
|
192
|
+
onUploadEnd,
|
|
137
193
|
}) {
|
|
138
|
-
|
|
139
|
-
const xhr = new XMLHttpRequest();
|
|
194
|
+
const xhr = new XMLHttpRequest();
|
|
140
195
|
|
|
141
|
-
|
|
142
|
-
|
|
196
|
+
xhr.open(method, url, true);
|
|
197
|
+
xhr.withCredentials = this.withCredentials;
|
|
143
198
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
199
|
+
// Установка заголовков
|
|
200
|
+
Object.keys(headers).forEach((key) => {
|
|
201
|
+
if (key.toLowerCase() !== "content-type" || !(body instanceof FormData)) {
|
|
202
|
+
xhr.setRequestHeader(key, headers[key]);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Прогресс
|
|
207
|
+
if (onProgress) {
|
|
208
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
209
|
+
if (e.lengthComputable) {
|
|
210
|
+
onProgress(Math.round((e.loaded / e.total) * 100), e);
|
|
148
211
|
}
|
|
149
212
|
});
|
|
213
|
+
}
|
|
150
214
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
215
|
+
// События
|
|
216
|
+
xhr.onload = () => {
|
|
217
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
218
|
+
const data = {
|
|
219
|
+
code: xhr.status,
|
|
220
|
+
response: normalizeData(xhr.responseText),
|
|
221
|
+
};
|
|
222
|
+
onSuccess(data);
|
|
223
|
+
} else {
|
|
224
|
+
const error = normalizeData({
|
|
225
|
+
code: xhr.status,
|
|
226
|
+
response: xhr.responseText || `HTTP ${xhr.status}`,
|
|
158
227
|
});
|
|
228
|
+
onError(error);
|
|
159
229
|
}
|
|
230
|
+
onUploadEnd();
|
|
231
|
+
};
|
|
160
232
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
response: normalizeData(xhr.responseText)
|
|
166
|
-
};
|
|
167
|
-
onSuccess(data);
|
|
168
|
-
resolve(data);
|
|
169
|
-
} else {
|
|
170
|
-
const error = new Error(`Ошибка ${xhr.status}: ${xhr.statusText}`);
|
|
171
|
-
let data = {
|
|
172
|
-
code: xhr.status,
|
|
173
|
-
response: error
|
|
174
|
-
}
|
|
175
|
-
onError(data);
|
|
176
|
-
reject(data);
|
|
177
|
-
}
|
|
178
|
-
onUploadEnd();
|
|
179
|
-
};
|
|
233
|
+
xhr.onerror = () => {
|
|
234
|
+
onError(normalizeData({ code: 0, response: "Network Error" }));
|
|
235
|
+
onUploadEnd();
|
|
236
|
+
};
|
|
180
237
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
onUploadEnd();
|
|
186
|
-
};
|
|
238
|
+
xhr.ontimeout = () => {
|
|
239
|
+
onError(normalizeData({ code: 0, response: "Request Timeout" }));
|
|
240
|
+
onUploadEnd();
|
|
241
|
+
};
|
|
187
242
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
243
|
+
// Привязка AbortController
|
|
244
|
+
if (signal) {
|
|
245
|
+
signal.addEventListener("abort", () => {
|
|
246
|
+
xhr.abort();
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
onUploadStart();
|
|
251
|
+
xhr.send(body);
|
|
252
|
+
|
|
253
|
+
return xhr; // для отмены снаружи
|
|
191
254
|
}
|
|
192
255
|
|
|
193
256
|
// === Сокращённые методы ===
|
|
257
|
+
|
|
194
258
|
get(url, options = {}) {
|
|
195
|
-
return this.request(url, { method:
|
|
259
|
+
return this.request(url, { method: "GET", ...options });
|
|
196
260
|
}
|
|
197
261
|
|
|
198
262
|
post(url, body, options = {}) {
|
|
199
|
-
return this.request(url, { method:
|
|
263
|
+
return this.request(url, { method: "POST", body, ...options });
|
|
200
264
|
}
|
|
201
265
|
|
|
202
266
|
put(url, body, options = {}) {
|
|
203
|
-
return this.request(url, { method:
|
|
267
|
+
return this.request(url, { method: "PUT", body, ...options });
|
|
204
268
|
}
|
|
205
269
|
|
|
206
270
|
delete(url, options = {}) {
|
|
207
|
-
return this.request(url, { method:
|
|
271
|
+
return this.request(url, { method: "DELETE", ...options });
|
|
208
272
|
}
|
|
209
273
|
|
|
210
274
|
patch(url, body, options = {}) {
|
|
211
|
-
return this.request(url, { method:
|
|
275
|
+
return this.request(url, { method: "PATCH", body, ...options });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Сериализация тела (если не FormData)
|
|
280
|
+
*/
|
|
281
|
+
_serializeBody(body) {
|
|
282
|
+
if (!body || body instanceof FormData) return undefined;
|
|
283
|
+
return JSON.stringify(body);
|
|
212
284
|
}
|
|
213
285
|
}
|
|
214
286
|
|
|
@@ -1,61 +1,146 @@
|
|
|
1
|
-
import {isElement, mergeDeepObject} from "../functions";
|
|
1
|
+
import { isElement, mergeDeepObject } from "../functions";
|
|
2
2
|
import EventHandler from "../dom/event";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Анимация на основе Animate.css
|
|
6
|
+
* Поддерживает модули с событиях: show, shown, hide, hidden
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* @see https://animate.style/
|
|
9
9
|
*/
|
|
10
10
|
class Animation {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
static get DEFAULTS() {
|
|
12
|
+
return {
|
|
13
13
|
enable: false,
|
|
14
|
-
in: '
|
|
15
|
-
out: '
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
in: 'animate__fadeIn', // Анимация при появлении
|
|
15
|
+
out: 'animate__fadeOut', // Анимация при скрытии
|
|
16
|
+
duration: 500, // Длительность анимации (мс)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
constructor(element, key, userParams = {}) {
|
|
21
|
+
this._element = element;
|
|
22
|
+
this._nameKey = key;
|
|
23
|
+
|
|
24
|
+
// Объединение параметров
|
|
25
|
+
this._params = mergeDeepObject(Animation.DEFAULTS, userParams);
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
// Ранний выход, если анимация отключена или элемент не валиден
|
|
28
|
+
if (!this._params.enable || !isElement(element)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this._classes = {
|
|
21
33
|
animated: 'animate__animated',
|
|
22
|
-
duration: '
|
|
34
|
+
duration: 'animate__fast' // Используем классы animate.css
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
this._init();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Инициализация анимации
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
_init() {
|
|
45
|
+
const { classList } = this._element;
|
|
46
|
+
|
|
47
|
+
// Добавляем общие классы
|
|
48
|
+
classList.add(this._classes.animated, this._classes.duration);
|
|
49
|
+
|
|
50
|
+
// Удаляем стандартный класс 'fade', если используется animate.css
|
|
51
|
+
if (classList.contains('fade')) {
|
|
52
|
+
classList.remove('fade');
|
|
23
53
|
}
|
|
24
54
|
|
|
25
|
-
|
|
26
|
-
|
|
55
|
+
// Назначаем обработчики событий
|
|
56
|
+
this._setupEventListeners();
|
|
57
|
+
}
|
|
27
58
|
|
|
28
|
-
|
|
29
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Назначение обработчиков событий
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
_setupEventListeners() {
|
|
64
|
+
EventHandler.on(this._element, `${this._nameKey}.show`, this._handleShow.bind(this), null);
|
|
65
|
+
EventHandler.on(this._element, `${this._nameKey}.shown`, this._handleShown.bind(this), null);
|
|
66
|
+
EventHandler.on(this._element, `${this._nameKey}.hide`, this._handleHide.bind(this), null);
|
|
67
|
+
EventHandler.on(this._element, `${this._nameKey}.hidden`, this._handleHidden.bind(this), null);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Обработка события "show" — запуск анимации входа
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
_handleShow() {
|
|
75
|
+
const { classList } = this._element;
|
|
76
|
+
const { in: inClass, out: outClass } = this._params;
|
|
77
|
+
|
|
78
|
+
// Убираем выходную анимацию, если была
|
|
79
|
+
if (classList.contains(outClass)) {
|
|
80
|
+
classList.remove(outClass);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Добавляем входную анимацию
|
|
84
|
+
classList.add(inClass);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Обработка события "shown" — завершение анимации входа
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
_handleShown() {
|
|
92
|
+
this._element.classList.add(this._classes.animated);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Обработка события "hide" — запуск анимации выхода
|
|
97
|
+
* @private
|
|
98
|
+
*/
|
|
99
|
+
_handleHide() {
|
|
100
|
+
const { classList } = this._element;
|
|
101
|
+
const { in: inClass, out: outClass } = this._params;
|
|
102
|
+
|
|
103
|
+
// Убираем входную анимацию
|
|
104
|
+
if (classList.contains(inClass)) {
|
|
105
|
+
classList.remove(inClass);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Добавляем выходную анимацию
|
|
109
|
+
classList.add(outClass);
|
|
110
|
+
}
|
|
30
111
|
|
|
31
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Обработка события "hidden" — очистка анимационных классов
|
|
114
|
+
* @private
|
|
115
|
+
*/
|
|
116
|
+
_handleHidden() {
|
|
117
|
+
const { classList } = this._element;
|
|
118
|
+
const { in: inClass, out: outClass } = this._params;
|
|
32
119
|
|
|
33
|
-
|
|
120
|
+
// Удаляем все анимационные классы animate.css
|
|
121
|
+
[...classList]
|
|
122
|
+
.filter(cls => cls.startsWith('animate__'))
|
|
123
|
+
.forEach(cls => classList.remove(cls));
|
|
34
124
|
|
|
35
|
-
|
|
125
|
+
// Восстанавливаем базовые классы для будущих анимаций
|
|
126
|
+
classList.add(this._classes.animated, this._classes.duration);
|
|
36
127
|
}
|
|
37
128
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
43
|
-
EventHandler.
|
|
44
|
-
|
|
45
|
-
});
|
|
129
|
+
/**
|
|
130
|
+
* Уничтожение экземпляра (очистка событий)
|
|
131
|
+
*/
|
|
132
|
+
dispose() {
|
|
133
|
+
EventHandler.off(this._element, `${this._nameKey}.show`, null, null);
|
|
134
|
+
EventHandler.off(this._element, `${this._nameKey}.shown`, null, null);
|
|
135
|
+
EventHandler.off(this._element, `${this._nameKey}.hide`, null, null);
|
|
136
|
+
EventHandler.off(this._element, `${this._nameKey}.hidden`, null, null);
|
|
46
137
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
138
|
+
// Очищаем анимационные классы
|
|
139
|
+
[...this._element.classList]
|
|
140
|
+
.filter(cls => cls.startsWith('animate__'))
|
|
141
|
+
.forEach(cls => this._element.classList.remove(cls));
|
|
51
142
|
|
|
52
|
-
|
|
53
|
-
[... this._element.classList].forEach((cl) => {
|
|
54
|
-
if (cl.indexOf('animate__') !== -1) {
|
|
55
|
-
this._element.classList.remove(cl);
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
});
|
|
143
|
+
this._element = null;
|
|
59
144
|
}
|
|
60
145
|
}
|
|
61
146
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {execute} from "../functions";
|
|
2
|
-
import Selectors from "../dom/selectors";
|
|
1
|
+
import { execute } from "../functions";
|
|
3
2
|
import EventHandler from "../dom/event";
|
|
4
|
-
import
|
|
3
|
+
import Html from "../components/templater";
|
|
4
|
+
import { Classes } from "../dom/manipulator";
|
|
5
|
+
import ScrollBarHelper from "./scrollbar";
|
|
5
6
|
|
|
6
7
|
const NAME = 'backdrop';
|
|
7
8
|
const CLASS_NAME = 'vg-backdrop';
|
|
@@ -9,50 +10,72 @@ const CLASS_NAME_FADE = 'fade';
|
|
|
9
10
|
const CLASS_NAME_SHOW = 'show';
|
|
10
11
|
const EVENT_MOUSEDOWN = `mousedown.vg.${NAME}`;
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
const backdropDelay = 150; // Уменьшено для более плавного UX
|
|
13
14
|
|
|
14
15
|
class Backdrop {
|
|
16
|
+
static _rootEl = document.body;
|
|
17
|
+
static _scrollbar = new ScrollBarHelper();
|
|
18
|
+
static _backdrop = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Показывает бэкдроп
|
|
22
|
+
* @param {Function} callback - вызывается после отображения
|
|
23
|
+
*/
|
|
15
24
|
static show(callback) {
|
|
16
|
-
|
|
25
|
+
if (!this._backdrop) {
|
|
26
|
+
this._append();
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
execute(callback);
|
|
18
30
|
}
|
|
19
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Скрывает бэкдроп
|
|
34
|
+
* @param {Function} callback - вызывается после скрытия
|
|
35
|
+
*/
|
|
20
36
|
static hide(callback) {
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
if (!this._backdrop) return;
|
|
38
|
+
|
|
39
|
+
this._destroy().then(execute.bind(null, callback));
|
|
23
40
|
}
|
|
24
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Создаёт и добавляет элемент бэкдропа
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
25
46
|
static _append() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
47
|
+
const html = Html('dom');
|
|
48
|
+
this._backdrop = html.div({ class: CLASS_NAME });
|
|
29
49
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
backdrop.classList.add(CLASS_NAME_FADE)
|
|
38
|
-
}, 50);
|
|
50
|
+
this._rootEl.appendChild(this._backdrop);
|
|
51
|
+
requestAnimationFrame(() => {
|
|
52
|
+
Classes.add(this._backdrop, CLASS_NAME_SHOW);
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
Classes.add(this._backdrop, CLASS_NAME_FADE);
|
|
55
|
+
}, backdropDelay);
|
|
56
|
+
});
|
|
39
57
|
|
|
40
|
-
EventHandler.on(
|
|
41
|
-
|
|
42
|
-
Overflow.destroy();
|
|
58
|
+
EventHandler.on(this._backdrop, EVENT_MOUSEDOWN, () => {
|
|
59
|
+
this.hide();
|
|
43
60
|
});
|
|
44
61
|
}
|
|
45
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Удаляет бэкдроп с анимацией
|
|
65
|
+
* @returns {Promise}
|
|
66
|
+
* @private
|
|
67
|
+
*/
|
|
46
68
|
static _destroy() {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
Classes.remove(this._backdrop, CLASS_NAME_FADE);
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
Classes.remove(this._backdrop, CLASS_NAME_SHOW);
|
|
73
|
+
this._backdrop.remove();
|
|
74
|
+
this._backdrop = null;
|
|
75
|
+
this._scrollbar.reset();
|
|
76
|
+
resolve();
|
|
77
|
+
}, backdropDelay);
|
|
78
|
+
});
|
|
56
79
|
}
|
|
57
80
|
}
|
|
58
81
|
|