vgapp 0.7.6 → 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 -82
- 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 -2
- package/app/utils/js/components/alert.js +0 -8
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {mergeDeepObject, noop, normalizeData} from "../functions";
|
|
2
|
+
|
|
3
|
+
class Ajax {
|
|
4
|
+
/**
|
|
5
|
+
* Конфигурация запроса
|
|
6
|
+
* @param {Object} options
|
|
7
|
+
* @param {string} options.baseUrl - Базовый URL API (опционально)
|
|
8
|
+
* @param {Object} options.headers - Доп. заголовки (например, авторизация)
|
|
9
|
+
* @param {boolean} options.withCredentials - Отправлять ли куки (для авторизованных запросов)
|
|
10
|
+
* @param {string} options._token - Токен (авто-чтение из meta)
|
|
11
|
+
*/
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.baseUrl = options.baseUrl || '';
|
|
14
|
+
this.defaultHeaders = {
|
|
15
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
16
|
+
...options.headers
|
|
17
|
+
};
|
|
18
|
+
this.withCredentials = options.withCredentials || false;
|
|
19
|
+
this.csrfToken = options._token || this._getCsrfToken();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Получение csrf токена из тега meta
|
|
24
|
+
* @returns {string}
|
|
25
|
+
* @private
|
|
26
|
+
*/
|
|
27
|
+
_getCsrfToken() {
|
|
28
|
+
const meta = document.querySelector('meta[name="csrf-token"]');
|
|
29
|
+
if (meta) return meta.getAttribute('content');
|
|
30
|
+
console.warn('CSRF-токен не найден в <meta name="csrf-token">');
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Универсальный метод отправки запроса
|
|
36
|
+
* @param {string} url
|
|
37
|
+
* @param {Object} options
|
|
38
|
+
* @param {'GET'|'POST'|'PUT'|'DELETE'|'PATCH'} options.method
|
|
39
|
+
* @param {Object|FormData} options.body - Данные (обычный объект или FormData)
|
|
40
|
+
* @param {Object} options.headers - Дополнительные заголовки
|
|
41
|
+
* @param {Function} options.onProgress - Колбэк прогресса (только для POST/PUT)
|
|
42
|
+
* @param {Function} options.onSuccess
|
|
43
|
+
* @param {Function} options.onError
|
|
44
|
+
* @param {Function} options.onUploadStart
|
|
45
|
+
* @param {Function} options.onUploadEnd
|
|
46
|
+
*/
|
|
47
|
+
request(url, {
|
|
48
|
+
method = 'GET',
|
|
49
|
+
body = null,
|
|
50
|
+
headers = {},
|
|
51
|
+
onProgress = null,
|
|
52
|
+
onSuccess = (data) => noop(),
|
|
53
|
+
onError = (error) => noop(),
|
|
54
|
+
onUploadStart = () => {},
|
|
55
|
+
onUploadEnd = () => {}
|
|
56
|
+
} = {}) {
|
|
57
|
+
const fullUrl = this.baseUrl + url;
|
|
58
|
+
const isFormData = body instanceof FormData;
|
|
59
|
+
const requestHeaders = { ...this.defaultHeaders, ...headers };
|
|
60
|
+
const token = {};
|
|
61
|
+
|
|
62
|
+
if (!isFormData && this.csrfToken) {
|
|
63
|
+
token.body = JSON.stringify({
|
|
64
|
+
_token: this.csrfToken
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Для JSON устанавливаем заголовок, для FormData — НЕЛЬЗЯ
|
|
69
|
+
if (!isFormData && !('Content-Type' in headers)) {
|
|
70
|
+
requestHeaders['Content-Type'] = 'application/json';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Если это GET-запрос — тело игнорируется
|
|
74
|
+
if (method.toUpperCase() === 'GET') {
|
|
75
|
+
return this._makeFetch(fullUrl, {
|
|
76
|
+
method,
|
|
77
|
+
headers: requestHeaders,
|
|
78
|
+
withCredentials: this.withCredentials
|
|
79
|
+
}, onSuccess, onError);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Для FormData — используем XMLHttpRequest, чтобы отслеживать прогресс
|
|
83
|
+
if (isFormData || onProgress) {
|
|
84
|
+
return this._makeXHR({
|
|
85
|
+
method,
|
|
86
|
+
url: fullUrl,
|
|
87
|
+
body,
|
|
88
|
+
headers: requestHeaders,
|
|
89
|
+
onProgress,
|
|
90
|
+
onSuccess,
|
|
91
|
+
onError,
|
|
92
|
+
onUploadStart,
|
|
93
|
+
onUploadEnd
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Остальные случаи — fetch
|
|
98
|
+
return this._makeFetch(fullUrl, mergeDeepObject({
|
|
99
|
+
method,
|
|
100
|
+
headers: requestHeaders,
|
|
101
|
+
withCredentials: this.withCredentials
|
|
102
|
+
}, token), onSuccess, onError);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Использование fetch (для JSON)
|
|
107
|
+
*/
|
|
108
|
+
_makeFetch(url, config, onSuccess, onError) {
|
|
109
|
+
return fetch(url, config)
|
|
110
|
+
.then(response => {
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
113
|
+
}
|
|
114
|
+
const contentType = response.headers.get('content-type');
|
|
115
|
+
if (contentType && contentType.includes('application/json')) {
|
|
116
|
+
return response.json();
|
|
117
|
+
}
|
|
118
|
+
return response.text();
|
|
119
|
+
})
|
|
120
|
+
.then(data => onSuccess(data))
|
|
121
|
+
.catch(error => onError(error));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Использование XHR (для FormData и прогресса)
|
|
126
|
+
*/
|
|
127
|
+
_makeXHR({
|
|
128
|
+
method,
|
|
129
|
+
url,
|
|
130
|
+
body,
|
|
131
|
+
headers,
|
|
132
|
+
onProgress,
|
|
133
|
+
onSuccess,
|
|
134
|
+
onError,
|
|
135
|
+
onUploadStart,
|
|
136
|
+
onUploadEnd
|
|
137
|
+
}) {
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
const xhr = new XMLHttpRequest();
|
|
140
|
+
|
|
141
|
+
xhr.open(method, url, true);
|
|
142
|
+
xhr.withCredentials = this.withCredentials;
|
|
143
|
+
|
|
144
|
+
// Устанавливаем только пользовательские заголовки (кроме Content-Type для FormData)
|
|
145
|
+
Object.keys(headers).forEach(key => {
|
|
146
|
+
if (key.toLowerCase() !== 'content-type' || !(body instanceof FormData)) {
|
|
147
|
+
xhr.setRequestHeader(key, headers[key]);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Отслеживание прогресса загрузки
|
|
152
|
+
if (onProgress) {
|
|
153
|
+
xhr.upload.addEventListener('progress', (e) => {
|
|
154
|
+
if (e.lengthComputable) {
|
|
155
|
+
const percent = (e.loaded / e.total) * 100;
|
|
156
|
+
onProgress(percent, e);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
xhr.onload = () => {
|
|
162
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
163
|
+
let data = {
|
|
164
|
+
code: xhr.status,
|
|
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
|
+
};
|
|
180
|
+
|
|
181
|
+
xhr.onerror = () => {
|
|
182
|
+
const error = new Error('Network Error');
|
|
183
|
+
onError(error);
|
|
184
|
+
reject(error);
|
|
185
|
+
onUploadEnd();
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
onUploadStart();
|
|
189
|
+
xhr.send(body);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// === Сокращённые методы ===
|
|
194
|
+
get(url, options = {}) {
|
|
195
|
+
return this.request(url, { method: 'GET', ...options });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
post(url, body, options = {}) {
|
|
199
|
+
return this.request(url, { method: 'POST', body, ...options });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
put(url, body, options = {}) {
|
|
203
|
+
return this.request(url, { method: 'PUT', body, ...options });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
delete(url, options = {}) {
|
|
207
|
+
return this.request(url, { method: 'DELETE', ...options });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
patch(url, body, options = {}) {
|
|
211
|
+
return this.request(url, { method: 'PATCH', body, ...options });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export default Ajax;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {normalizeData} from "../functions";
|
|
2
|
+
|
|
3
|
+
const langs = {
|
|
4
|
+
ru: {
|
|
5
|
+
messages: {
|
|
6
|
+
errors: {
|
|
7
|
+
went_wrong: 'Что-то пошло не так, повторите позже',
|
|
8
|
+
"400": 'Неверный запрос',
|
|
9
|
+
"401": 'Не авторизован',
|
|
10
|
+
"403": 'Запрещено',
|
|
11
|
+
"404": 'Не найдено',
|
|
12
|
+
"413": 'Слишком большой запрос',
|
|
13
|
+
"419": 'Проблемы с токеном CSRF',
|
|
14
|
+
"422": 'Неверный запрос',
|
|
15
|
+
"500": 'Внутренняя ошибка сервера',
|
|
16
|
+
"504": 'Превышено время ожидания'
|
|
17
|
+
},
|
|
18
|
+
'form-sender': {
|
|
19
|
+
'bootstrap_not_found': 'VGApp не удалось найти bootstrap, модалки не будут закрыты, попробуйте сделать это через коллбек afterSend.'
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
titles: {
|
|
23
|
+
errors: {
|
|
24
|
+
title: 'Ошибка',
|
|
25
|
+
titles: 'Ошибки'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
en: {
|
|
30
|
+
messages: {
|
|
31
|
+
errors: {
|
|
32
|
+
went_wrong: 'Something went wrong, please repeat later',
|
|
33
|
+
"400": 'Bad Request',
|
|
34
|
+
"401": 'Unauthorized',
|
|
35
|
+
"403": 'Forbidden',
|
|
36
|
+
"404": 'Not Found',
|
|
37
|
+
"413": 'Payload Too Large',
|
|
38
|
+
"419": 'Problems with the CSRF token',
|
|
39
|
+
"422": 'Unprocessable Entity',
|
|
40
|
+
"500": 'Internal Server Error',
|
|
41
|
+
"504": 'Gateway Timeout'
|
|
42
|
+
},
|
|
43
|
+
'form-sender': {
|
|
44
|
+
'bootstrap_not_found': 'VGApp could not find bootstrap, the modals will not be closed, try to do this through the afterSend callback.'
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
titles: {
|
|
48
|
+
errors: {
|
|
49
|
+
title: 'Error',
|
|
50
|
+
titles: 'Errors'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
class Lang {
|
|
57
|
+
constructor(lang = 'en') {
|
|
58
|
+
this.lang = lang;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get() {
|
|
62
|
+
let data = langs[this.lang];
|
|
63
|
+
|
|
64
|
+
if (!data) data = langs['en'];
|
|
65
|
+
|
|
66
|
+
return normalizeData(data);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function lang(lg, mode, module) {
|
|
71
|
+
return new Lang(lg).get()[mode][module];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function lang_titles(lg, module) {
|
|
75
|
+
return lang(lg, 'titles', module) || {};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function lang_messages(lg, module) {
|
|
79
|
+
return lang(lg, 'messages', module) || {};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export {lang, lang_messages, lang_titles};
|
|
@@ -37,6 +37,11 @@ class Params {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
for (let key in mParams) {
|
|
40
|
+
if ('params' in mParams) {
|
|
41
|
+
mParams = mergeDeepObject(mParams, mParams.params);
|
|
42
|
+
delete mParams.params;
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
if (key.indexOf('-') !== -1) {
|
|
41
46
|
mParams = stringToNestedObjectWithValue(key, mParams[key], mParams);
|
|
42
47
|
delete mParams[key];
|
|
@@ -3,138 +3,141 @@ import {Classes} from "../dom/manipulator";
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Класс Placement, определяет и устанавливает местоположение элемента на странице.
|
|
6
|
-
* TODO класс не дописан, не определяет сверху и снизу
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
|
-
const CLASS_NAME_RIGHT = 'right';
|
|
10
|
-
const CLASS_NAME_LEFT = 'left';
|
|
11
|
-
const CLASS_NAME_TOP = 'top';
|
|
12
|
-
const CLASS_NAME_BOTTOM = 'bottom';
|
|
13
|
-
|
|
14
8
|
class Placement {
|
|
15
|
-
constructor(
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.reference = config.reference;
|
|
11
|
+
this.drop = config.drop;
|
|
12
|
+
this.offset = config.offset || [0, 0];
|
|
13
|
+
this.boundary = config.boundary || 'viewport';
|
|
14
|
+
this.autoFlip = config.autoFlip !== false;
|
|
15
|
+
this.overflowProtection = config.overflowProtection !== false;
|
|
16
|
+
this.placement = config.placement || 'bottom';
|
|
17
|
+
this.fallbackPlacements = config.fallbackPlacements || [];
|
|
18
|
+
|
|
19
|
+
this._builtInPlacements = {
|
|
20
|
+
top: 'top',
|
|
21
|
+
'top-start': 'top-start',
|
|
22
|
+
'top-end': 'top-end',
|
|
23
|
+
bottom: 'bottom',
|
|
24
|
+
'bottom-start': 'bottom-start',
|
|
25
|
+
'bottom-end': 'bottom-end',
|
|
26
|
+
left: 'left',
|
|
27
|
+
'left-start': 'left-start',
|
|
28
|
+
'left-end': 'left-end',
|
|
29
|
+
right: 'right',
|
|
30
|
+
'right-start': 'right-start',
|
|
31
|
+
'right-end': 'right-end'
|
|
32
|
+
};
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
return
|
|
35
|
+
_getPlacementRect(element) {
|
|
36
|
+
return element.getBoundingClientRect();
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
_getViewportRect() {
|
|
40
|
+
const doc = document.documentElement;
|
|
41
|
+
return {
|
|
42
|
+
width: doc.clientWidth,
|
|
43
|
+
height: doc.clientHeight,
|
|
44
|
+
left: 0,
|
|
45
|
+
top: 0,
|
|
46
|
+
right: doc.clientWidth,
|
|
47
|
+
bottom: doc.clientHeight
|
|
48
|
+
};
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Classes.remove(this.drop, CLASS_NAME_LEFT);
|
|
59
|
-
Classes.add(this.drop, CLASS_NAME_RIGHT);
|
|
60
|
-
}
|
|
51
|
+
_getOverflowConstraints() {
|
|
52
|
+
const refRect = this._getPlacementRect(this.reference);
|
|
53
|
+
const dropRect = this._getPlacementRect(this.drop);
|
|
54
|
+
const viewRect = this._getViewportRect();
|
|
55
|
+
const [xOffset, yOffset] = this.offset;
|
|
61
56
|
|
|
62
|
-
|
|
63
|
-
Classes.remove(this.drop, CLASS_NAME_RIGHT);
|
|
64
|
-
Classes.add(this.drop, CLASS_NAME_LEFT);
|
|
65
|
-
}
|
|
57
|
+
let placement = this.placement;
|
|
66
58
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
59
|
+
if (this.overflowProtection) {
|
|
60
|
+
const fallbacks = [this.placement, ...this.fallbackPlacements];
|
|
61
|
+
let best = null;
|
|
71
62
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
63
|
+
for (let p of fallbacks) {
|
|
64
|
+
let pos = this._calculatePosition(p, refRect, dropRect, xOffset, yOffset);
|
|
65
|
+
let overflow = this._calculateOverflow(pos, viewRect);
|
|
78
66
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
let parent = self.parentNode,
|
|
83
|
-
overflow = getComputedStyle(parent).overflow;
|
|
84
|
-
|
|
85
|
-
if (parent.tagName !== 'BODY') {
|
|
86
|
-
if (overflow === 'visible') {
|
|
87
|
-
_parent(parent)
|
|
88
|
-
} else {
|
|
89
|
-
return parent;
|
|
67
|
+
if (!best || overflow < best.overflow) {
|
|
68
|
+
best = { placement: p, position: pos, overflow };
|
|
69
|
+
if (overflow === 0) break;
|
|
90
70
|
}
|
|
91
|
-
} else {
|
|
92
|
-
return null;
|
|
93
71
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let isFixed = false, top, left,
|
|
97
|
-
bounds = _this.params.drop.getBoundingClientRect(),
|
|
98
|
-
parent = _this.params.element.getBoundingClientRect();
|
|
99
72
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
top = bounds.top;
|
|
103
|
-
left = bounds.left;
|
|
73
|
+
placement = best.placement;
|
|
74
|
+
this._setStyles(best.position);
|
|
104
75
|
} else {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
left = normalizeData(styles.left.slice(0, -2));
|
|
76
|
+
const pos = this._calculatePosition(placement, refRect, dropRect, xOffset, yOffset);
|
|
77
|
+
this._setStyles(pos);
|
|
108
78
|
}
|
|
109
79
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
80
|
+
this.drop.setAttribute('data-vg-placement', placement);
|
|
81
|
+
}
|
|
113
82
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
83
|
+
_calculatePosition(placement, refRect, dropRect, xOffset = 0, yOffset = 0) {
|
|
84
|
+
let top, left;
|
|
85
|
+
|
|
86
|
+
switch (placement) {
|
|
87
|
+
case 'top':
|
|
88
|
+
case 'top-start':
|
|
89
|
+
top = refRect.top - dropRect.height - yOffset;
|
|
90
|
+
left = placement === 'top-start' ? refRect.left + xOffset : refRect.left + (refRect.width - dropRect.width) / 2;
|
|
91
|
+
break;
|
|
92
|
+
case 'top-end':
|
|
93
|
+
top = refRect.top - dropRect.height - yOffset;
|
|
94
|
+
left = refRect.right - dropRect.width - xOffset;
|
|
95
|
+
break;
|
|
96
|
+
case 'bottom':
|
|
97
|
+
case 'bottom-start':
|
|
98
|
+
top = refRect.bottom + yOffset;
|
|
99
|
+
left = placement === 'bottom-start' ? refRect.left + xOffset : refRect.left + (refRect.width - dropRect.width) / 2;
|
|
100
|
+
break;
|
|
101
|
+
case 'bottom-end':
|
|
102
|
+
top = refRect.bottom + yOffset;
|
|
103
|
+
left = refRect.right - dropRect.width - xOffset;
|
|
104
|
+
break;
|
|
105
|
+
case 'left':
|
|
106
|
+
top = refRect.top + (refRect.height - dropRect.height) / 2;
|
|
107
|
+
left = refRect.left - dropRect.width - xOffset;
|
|
108
|
+
break;
|
|
109
|
+
case 'right':
|
|
110
|
+
top = refRect.top + (refRect.height - dropRect.height) / 2;
|
|
111
|
+
left = refRect.right + xOffset;
|
|
112
|
+
break;
|
|
113
|
+
default:
|
|
114
|
+
top = refRect.bottom + yOffset;
|
|
115
|
+
left = refRect.left + xOffset;
|
|
118
116
|
}
|
|
117
|
+
|
|
118
|
+
return { top, left };
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
_calculateOverflow(pos, viewRect) {
|
|
122
|
+
let overflow = 0;
|
|
123
|
+
if (pos.left < viewRect.left) overflow += viewRect.left - pos.left;
|
|
124
|
+
if (pos.top < viewRect.top) overflow += viewRect.top - pos.top;
|
|
125
|
+
if (pos.left + this.drop.offsetWidth > viewRect.right) overflow += (pos.left + this.drop.offsetWidth) - viewRect.right;
|
|
126
|
+
if (pos.top + this.drop.offsetHeight > viewRect.bottom) overflow += (pos.top + this.drop.offsetHeight) - viewRect.bottom;
|
|
127
|
+
return overflow;
|
|
128
|
+
}
|
|
125
129
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
};
|
|
130
|
+
_setStyles(pos) {
|
|
131
|
+
mergeDeepObject(this.drop.style, {
|
|
132
|
+
position: 'absolute',
|
|
133
|
+
top: `${pos.top}px`,
|
|
134
|
+
left: `${pos.left}px`,
|
|
135
|
+
margin: 0
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
_setPlacement() {
|
|
140
|
+
this._getOverflowConstraints();
|
|
138
141
|
}
|
|
139
142
|
}
|
|
140
143
|
|