vgapp 1.0.1 → 1.0.3
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/app/langs/en/messages.json +3 -2
- package/app/langs/ru/messages.json +36 -35
- package/app/modules/vgfiles/js/base.js +97 -41
- package/app/modules/vgfiles/js/droppable.js +259 -64
- package/app/modules/vgfiles/js/vgfiles.js +60 -29
- package/app/modules/vgfiles/readme.md +2 -2
- package/app/modules/vgfiles/scss/_variables.scss +4 -2
- package/app/modules/vgfiles/scss/vgfiles.scss +56 -54
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/package.json +1 -1
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"titles": "Deleting files",
|
|
23
23
|
"title": "Deleting file",
|
|
24
24
|
"descriptions": "Do you really want to delete all uploaded files from the server?",
|
|
25
|
-
"description": "It will not be possible to return it"
|
|
25
|
+
"description": "It will not be possible to return it",
|
|
26
|
+
"drop-active": "Release to upload"
|
|
26
27
|
},
|
|
27
28
|
"alert": {
|
|
28
29
|
"title": "Default header",
|
|
@@ -32,4 +33,4 @@
|
|
|
32
33
|
"select": {
|
|
33
34
|
"loading": "Loading"
|
|
34
35
|
}
|
|
35
|
-
}
|
|
36
|
+
}
|
|
@@ -1,35 +1,36 @@
|
|
|
1
|
-
{
|
|
2
|
-
"errors": {
|
|
3
|
-
"went_wrong": "Что-то пошло не так, повторите позже",
|
|
4
|
-
"400": "Неверный запрос",
|
|
5
|
-
"401": "Не авторизован",
|
|
6
|
-
"403": "Запрещено",
|
|
7
|
-
"404": "Не найдено",
|
|
8
|
-
"413": "Слишком большой запрос",
|
|
9
|
-
"419": "Проблемы с токеном CSRF",
|
|
10
|
-
"422": "Неверный запрос",
|
|
11
|
-
"500": "Внутренняя ошибка сервера",
|
|
12
|
-
"504": "Превышено время ожидания"
|
|
13
|
-
},
|
|
14
|
-
"form-sender": {
|
|
15
|
-
"bootstrap_not_found": "VGApp не удалось найти bootstrap, модалки не будут закрыты, попробуйте сделать это через коллбек afterSend."
|
|
16
|
-
},
|
|
17
|
-
"files": {
|
|
18
|
-
"is-count": "Превышен лимит по количеству файлов",
|
|
19
|
-
"is-sizes": "Превышен размер файл",
|
|
20
|
-
"is-types": "Недопустимый тип файла",
|
|
21
|
-
"is-total-size": "Превышен максимально разрешённый размер для выбранных файлов",
|
|
22
|
-
"titles": "Удаление файлов",
|
|
23
|
-
"title": "Удаление файлa",
|
|
24
|
-
"descriptions": "Вы действительно хотите удалить все загруженные файлы с сервера?",
|
|
25
|
-
"description": "Вернуть будет невозможно"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"errors": {
|
|
3
|
+
"went_wrong": "Что-то пошло не так, повторите позже",
|
|
4
|
+
"400": "Неверный запрос",
|
|
5
|
+
"401": "Не авторизован",
|
|
6
|
+
"403": "Запрещено",
|
|
7
|
+
"404": "Не найдено",
|
|
8
|
+
"413": "Слишком большой запрос",
|
|
9
|
+
"419": "Проблемы с токеном CSRF",
|
|
10
|
+
"422": "Неверный запрос",
|
|
11
|
+
"500": "Внутренняя ошибка сервера",
|
|
12
|
+
"504": "Превышено время ожидания"
|
|
13
|
+
},
|
|
14
|
+
"form-sender": {
|
|
15
|
+
"bootstrap_not_found": "VGApp не удалось найти bootstrap, модалки не будут закрыты, попробуйте сделать это через коллбек afterSend."
|
|
16
|
+
},
|
|
17
|
+
"files": {
|
|
18
|
+
"is-count": "Превышен лимит по количеству файлов",
|
|
19
|
+
"is-sizes": "Превышен размер файл",
|
|
20
|
+
"is-types": "Недопустимый тип файла",
|
|
21
|
+
"is-total-size": "Превышен максимально разрешённый размер для выбранных файлов",
|
|
22
|
+
"titles": "Удаление файлов",
|
|
23
|
+
"title": "Удаление файлa",
|
|
24
|
+
"descriptions": "Вы действительно хотите удалить все загруженные файлы с сервера?",
|
|
25
|
+
"description": "Вернуть будет невозможно",
|
|
26
|
+
"drop-active": "Отпустите, чтобы загрузить"
|
|
27
|
+
},
|
|
28
|
+
"alert": {
|
|
29
|
+
"title": "Заголовок по умолчанию",
|
|
30
|
+
"description": "Описание текущего действия",
|
|
31
|
+
"reason": "Алерт уже открыт"
|
|
32
|
+
},
|
|
33
|
+
"select": {
|
|
34
|
+
"loading": "Загрузка"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -137,12 +137,12 @@ class VGFilesBase extends BaseModule {
|
|
|
137
137
|
Boolean(this._params?.replace) &&
|
|
138
138
|
isSingle;
|
|
139
139
|
|
|
140
|
-
if (shouldReplaceOnSingle) {
|
|
141
|
-
this.clear();
|
|
142
|
-
this.append(filesArray, true);
|
|
143
|
-
this._revokeUrls();
|
|
144
|
-
this._cleanupFakeInputs();
|
|
145
|
-
this._cleanupErrors();
|
|
140
|
+
if (shouldReplaceOnSingle) {
|
|
141
|
+
this.clear(false);
|
|
142
|
+
this.append(filesArray, true);
|
|
143
|
+
this._revokeUrls();
|
|
144
|
+
this._cleanupFakeInputs();
|
|
145
|
+
this._cleanupErrors();
|
|
146
146
|
|
|
147
147
|
this._files = this._filterFiles(filesArray);
|
|
148
148
|
if (this._params.prepend) this._files.reverse();
|
|
@@ -314,7 +314,8 @@ class VGFilesBase extends BaseModule {
|
|
|
314
314
|
|
|
315
315
|
_parseTemplate() {
|
|
316
316
|
const render = this._render;
|
|
317
|
-
|
|
317
|
+
const fallbackTemplate = '<li data-file="" class="file"><div class="file-image"></div><div class="file-custom"><div class="file-info"></div><div class="file-remove"></div></div></li>';
|
|
318
|
+
let tmpl = this.template || fallbackTemplate;
|
|
318
319
|
|
|
319
320
|
if (render) {
|
|
320
321
|
if (render.bufferTemplate) tmpl = render.bufferTemplate;
|
|
@@ -322,7 +323,12 @@ class VGFilesBase extends BaseModule {
|
|
|
322
323
|
|
|
323
324
|
const temp = document.createElement('div');
|
|
324
325
|
temp.innerHTML = tmpl;
|
|
325
|
-
|
|
326
|
+
let liElement = temp.firstElementChild;
|
|
327
|
+
|
|
328
|
+
if (!liElement) {
|
|
329
|
+
temp.innerHTML = fallbackTemplate;
|
|
330
|
+
liElement = temp.firstElementChild;
|
|
331
|
+
}
|
|
326
332
|
const liClasses = liElement.className || '';
|
|
327
333
|
const liClassList = liClasses ? liClasses.split(' ').filter(cls => cls.trim() !== '') : [];
|
|
328
334
|
|
|
@@ -372,16 +378,11 @@ class VGFilesBase extends BaseModule {
|
|
|
372
378
|
}
|
|
373
379
|
}
|
|
374
380
|
|
|
375
|
-
let parts = [];
|
|
376
|
-
$itemsTemplate.forEach(tmpl => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
parts.push(this._renderUIDetach(file))
|
|
381
|
-
} else {
|
|
382
|
-
parts.push(tmpl.element.cloneNode(true));
|
|
383
|
-
}
|
|
384
|
-
});
|
|
381
|
+
let parts = [];
|
|
382
|
+
$itemsTemplate.forEach(tmpl => {
|
|
383
|
+
const part = this._renderTemplatePart(tmpl.element, file, null, { isDrop: true });
|
|
384
|
+
if (part) parts.push(part);
|
|
385
|
+
});
|
|
385
386
|
|
|
386
387
|
const $li = this._tpl.li(
|
|
387
388
|
{ 'data-name': file.name, 'data-size': file.size, 'data-id': file.id || '', class: 'file ' + classes.join(' ') }, parts
|
|
@@ -393,8 +394,17 @@ class VGFilesBase extends BaseModule {
|
|
|
393
394
|
$list.innerHTML = '';
|
|
394
395
|
$list.appendChild(fragment);
|
|
395
396
|
|
|
396
|
-
const $message = Selectors.find(`.${this._getClass('drop-message')}`, this._nodes.drop);
|
|
397
|
-
if ($message)
|
|
397
|
+
const $message = Selectors.find(`.${this._getClass('drop-message')}`, this._nodes.drop);
|
|
398
|
+
if ($message) {
|
|
399
|
+
if (files.length) Classes.add($message, 'has-files');
|
|
400
|
+
else Classes.remove($message, 'has-files');
|
|
401
|
+
|
|
402
|
+
const isSingle = Number(this._params?.limits?.count) === 1;
|
|
403
|
+
if (isSingle) {
|
|
404
|
+
if (files.length) Classes.remove($message, 'show');
|
|
405
|
+
else Classes.add($message, 'show');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
398
408
|
|
|
399
409
|
this._nodes.drop.appendChild($list);
|
|
400
410
|
Classes.add(this._nodes.drop, 'active');
|
|
@@ -425,18 +435,11 @@ class VGFilesBase extends BaseModule {
|
|
|
425
435
|
if (this._params.detach) classes.push('with-remove')
|
|
426
436
|
if (this._params.sortable.enabled) classes.push('with-sortable');
|
|
427
437
|
|
|
428
|
-
let parts = [];
|
|
429
|
-
$itemsTemplate.forEach(tmpl => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
parts.push(this._renderUIInfo(file, i))
|
|
434
|
-
} else if (tmpl.className === 'file-remove') {
|
|
435
|
-
parts.push(this._renderUIDetach(file))
|
|
436
|
-
} else {
|
|
437
|
-
parts.push(tmpl.element.cloneNode(true));
|
|
438
|
-
}
|
|
439
|
-
});
|
|
438
|
+
let parts = [];
|
|
439
|
+
$itemsTemplate.forEach(tmpl => {
|
|
440
|
+
const part = this._renderTemplatePart(tmpl.element, file, i);
|
|
441
|
+
if (part) parts.push(part);
|
|
442
|
+
});
|
|
440
443
|
|
|
441
444
|
const $li = this._tpl.li(
|
|
442
445
|
{ 'data-name': file.name, 'data-size': file.size, 'data-type': file.type, 'data-id': file.id || '', class: 'file ' + classes.join(' ') + ' ' }, parts
|
|
@@ -448,6 +451,50 @@ class VGFilesBase extends BaseModule {
|
|
|
448
451
|
Classes.add(this._nodes.info, 'show')
|
|
449
452
|
}
|
|
450
453
|
|
|
454
|
+
_renderTemplatePart(element, file, index = null, options = {}) {
|
|
455
|
+
if (!element) return null;
|
|
456
|
+
const { isDrop = false } = options;
|
|
457
|
+
|
|
458
|
+
const classList = element?.classList;
|
|
459
|
+
|
|
460
|
+
if (classList?.contains('file-image')) return this._renderUIImage(file);
|
|
461
|
+
if (classList?.contains('file-info')) {
|
|
462
|
+
if (isDrop) return null;
|
|
463
|
+
return this._renderUIInfo(file, index);
|
|
464
|
+
}
|
|
465
|
+
if (classList?.contains('file-remove')) return this._wrapInFileCustom(this._renderUIDetach(file));
|
|
466
|
+
|
|
467
|
+
const $part = element.cloneNode(true);
|
|
468
|
+
|
|
469
|
+
this._replaceTemplateSlot($part, '.file-image', () => this._renderUIImage(file));
|
|
470
|
+
this._replaceTemplateSlot($part, '.file-info', () => isDrop ? null : this._renderUIInfo(file, index));
|
|
471
|
+
this._replaceTemplateSlot($part, '.file-remove', () => this._renderUIDetach(file));
|
|
472
|
+
|
|
473
|
+
return this._wrapInFileCustom($part);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
_wrapInFileCustom(node) {
|
|
477
|
+
if (!node) return null;
|
|
478
|
+
if (node.classList?.contains('file-custom')) return node;
|
|
479
|
+
|
|
480
|
+
const wrapper = document.createElement('div');
|
|
481
|
+
wrapper.className = 'file-custom';
|
|
482
|
+
wrapper.appendChild(node);
|
|
483
|
+
|
|
484
|
+
return wrapper;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
_replaceTemplateSlot(container, selector, renderer) {
|
|
488
|
+
const isMatchSelf = container?.matches && container.matches(selector);
|
|
489
|
+
const matched = isMatchSelf ? [container] : Array.from(container.querySelectorAll(selector));
|
|
490
|
+
|
|
491
|
+
matched.forEach((node) => {
|
|
492
|
+
const replacement = renderer();
|
|
493
|
+
if (replacement) node.replaceWith(replacement);
|
|
494
|
+
else node.remove();
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
451
498
|
_renderUIDetach(file) {
|
|
452
499
|
if (this._params.detach) {
|
|
453
500
|
return this._tpl.div({ class: 'file-remove' }, [
|
|
@@ -539,12 +586,14 @@ class VGFilesBase extends BaseModule {
|
|
|
539
586
|
Selectors.findAll('[data-vg-files="generated"]', this._element).forEach(el => el.remove());
|
|
540
587
|
}
|
|
541
588
|
|
|
542
|
-
clear() {
|
|
543
|
-
this._revokeUrls();
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
this.
|
|
589
|
+
clear(resetInput = true) {
|
|
590
|
+
this._revokeUrls();
|
|
591
|
+
if (resetInput) {
|
|
592
|
+
this._resetFileInput();
|
|
593
|
+
}
|
|
594
|
+
this._cleanupFakeInputs();
|
|
595
|
+
this._cleanupErrors();
|
|
596
|
+
this._files = [];
|
|
548
597
|
|
|
549
598
|
if (this._nodes.info) {
|
|
550
599
|
Classes.remove(this._nodes.info, 'show');
|
|
@@ -555,8 +604,15 @@ class VGFilesBase extends BaseModule {
|
|
|
555
604
|
const $list = Selectors.find(`.${this._getClass('drop-list')}`, this._element);
|
|
556
605
|
if ($list) $list.innerHTML = '';
|
|
557
606
|
|
|
558
|
-
const $message = Selectors.find(`.${this._getClass('drop-message')}`, this._element);
|
|
559
|
-
if ($message)
|
|
607
|
+
const $message = Selectors.find(`.${this._getClass('drop-message')}`, this._element);
|
|
608
|
+
if ($message) {
|
|
609
|
+
Classes.remove($message, 'has-files');
|
|
610
|
+
|
|
611
|
+
const isSingle = Number(this._params?.limits?.count) === 1;
|
|
612
|
+
if (isSingle) {
|
|
613
|
+
Classes.add($message, 'show');
|
|
614
|
+
}
|
|
615
|
+
}
|
|
560
616
|
|
|
561
617
|
Classes.remove(this._nodes.drop, 'active');
|
|
562
618
|
}
|
|
@@ -599,4 +655,4 @@ class VGFilesBase extends BaseModule {
|
|
|
599
655
|
}
|
|
600
656
|
}
|
|
601
657
|
|
|
602
|
-
export default VGFilesBase;
|
|
658
|
+
export default VGFilesBase;
|
|
@@ -13,11 +13,11 @@ const CLASS_NAME_DROP_HOVER = 'drop-hover';
|
|
|
13
13
|
const NAME = 'drop-and-drop';
|
|
14
14
|
const NAME_KEY = 'vg.drop-and-drop';
|
|
15
15
|
|
|
16
|
-
class VGFilesDroppable extends BaseModule {
|
|
17
|
-
/**
|
|
18
|
-
* @param {HTMLElement} element
|
|
19
|
-
* @param {Object} params
|
|
20
|
-
*/
|
|
16
|
+
class VGFilesDroppable extends BaseModule {
|
|
17
|
+
/**
|
|
18
|
+
* @param {HTMLElement} element
|
|
19
|
+
* @param {Object} params
|
|
20
|
+
*/
|
|
21
21
|
constructor(element, params = {}) {
|
|
22
22
|
super(element, params);
|
|
23
23
|
this._element = element;
|
|
@@ -31,10 +31,15 @@ class VGFilesDroppable extends BaseModule {
|
|
|
31
31
|
static get NAME() { return NAME; }
|
|
32
32
|
static get NAME_KEY() { return NAME_KEY; }
|
|
33
33
|
|
|
34
|
-
_init() {
|
|
35
|
-
|
|
36
|
-
this.
|
|
37
|
-
|
|
34
|
+
_init() {
|
|
35
|
+
VGFilesDroppable._instances.add(this);
|
|
36
|
+
if (this._params?.smartdrop) {
|
|
37
|
+
VGFilesDroppable._smartInstances.add(this);
|
|
38
|
+
this._bindGlobalEvents();
|
|
39
|
+
}
|
|
40
|
+
this._findInput();
|
|
41
|
+
this._setupEvents();
|
|
42
|
+
}
|
|
38
43
|
|
|
39
44
|
/**
|
|
40
45
|
* Поиск связанного input[type="file"] внутри или рядом с drop-зоной
|
|
@@ -53,8 +58,8 @@ class VGFilesDroppable extends BaseModule {
|
|
|
53
58
|
/**
|
|
54
59
|
* Настройка событий перетаскивания
|
|
55
60
|
*/
|
|
56
|
-
_setupEvents() {
|
|
57
|
-
if (!isElement(this._element)) return;
|
|
61
|
+
_setupEvents() {
|
|
62
|
+
if (!isElement(this._element)) return;
|
|
58
63
|
|
|
59
64
|
const isSortableDrag = (e) => {
|
|
60
65
|
// 1) если sortable реально активен — на элементе есть класс dragging
|
|
@@ -64,70 +69,76 @@ class VGFilesDroppable extends BaseModule {
|
|
|
64
69
|
let plain = '';
|
|
65
70
|
try { plain = e.dataTransfer?.getData?.('text/plain') || ''; } catch (_) {}
|
|
66
71
|
return plain === 'vgsortable';
|
|
67
|
-
};
|
|
72
|
+
};
|
|
68
73
|
|
|
69
|
-
EventHandler.on(this._element, 'dragover', (e) => {
|
|
70
|
-
e.preventDefault();
|
|
71
|
-
e.stopPropagation();
|
|
74
|
+
EventHandler.on(this._element, 'dragover', (e) => {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
e.stopPropagation();
|
|
72
77
|
|
|
73
78
|
// ✅ Сортировка: НЕ подсвечиваем dropzone
|
|
74
|
-
if (isSortableDrag(e)) {
|
|
75
|
-
Classes.remove(this._element, [CLASS_NAME_DROP_ACTIVE, CLASS_NAME_DROP_HOVER]);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
if (isSortableDrag(e)) {
|
|
80
|
+
Classes.remove(this._element, [CLASS_NAME_DROP_ACTIVE, CLASS_NAME_DROP_HOVER]);
|
|
81
|
+
VGFilesDroppable._setDropMessageTitleState(this._element, false);
|
|
82
|
+
e.dataTransfer.dropEffect = 'none';
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
79
85
|
|
|
80
86
|
if (!Classes.has(this._element, CLASS_NAME_DROP_ACTIVE)) {
|
|
81
87
|
Classes.add(this._element, CLASS_NAME_DROP_HOVER);
|
|
82
88
|
}
|
|
83
89
|
});
|
|
84
90
|
|
|
85
|
-
EventHandler.on(this._element, 'dragenter', (e) => {
|
|
86
|
-
e.preventDefault();
|
|
87
|
-
e.stopPropagation();
|
|
91
|
+
EventHandler.on(this._element, 'dragenter', (e) => {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
e.stopPropagation();
|
|
88
94
|
|
|
89
95
|
// ✅ Сортировка: НЕ подсвечиваем dropzone
|
|
90
|
-
if (isSortableDrag(e)) {
|
|
91
|
-
Classes.remove(this._element, [CLASS_NAME_DROP_ACTIVE, CLASS_NAME_DROP_HOVER]);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
if (isSortableDrag(e)) {
|
|
97
|
+
Classes.remove(this._element, [CLASS_NAME_DROP_ACTIVE, CLASS_NAME_DROP_HOVER]);
|
|
98
|
+
VGFilesDroppable._setDropMessageTitleState(this._element, false);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Classes.add(this._element, [CLASS_NAME_DROP_ACTIVE, CLASS_NAME_DROP_HOVER]);
|
|
103
|
+
VGFilesDroppable._setDropMessageTitleState(this._element, true);
|
|
104
|
+
});
|
|
97
105
|
|
|
98
106
|
EventHandler.on(this._element, 'dragleave', (e) => {
|
|
99
107
|
e.preventDefault();
|
|
100
108
|
e.stopPropagation();
|
|
101
109
|
|
|
102
110
|
// ✅ Сортировка: гарантированно без подсветки
|
|
103
|
-
if (isSortableDrag(e)) {
|
|
104
|
-
Classes.remove(this._element, [CLASS_NAME_DROP_ACTIVE, CLASS_NAME_DROP_HOVER]);
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
if (isSortableDrag(e)) {
|
|
112
|
+
Classes.remove(this._element, [CLASS_NAME_DROP_ACTIVE, CLASS_NAME_DROP_HOVER]);
|
|
113
|
+
VGFilesDroppable._setDropMessageTitleState(this._element, false);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
107
116
|
|
|
108
117
|
if (e.target === this._element || e.target.closest('.' + CLASS_NAME_DROP) === this._element) {
|
|
109
118
|
Classes.remove(this._element, CLASS_NAME_DROP_HOVER);
|
|
110
119
|
setTimeout(() => {
|
|
111
|
-
if (!this._element.matches(':hover')) {
|
|
112
|
-
Classes.remove(this._element, CLASS_NAME_DROP_ACTIVE);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
EventHandler.on(this._element, 'drop', (e) => {
|
|
119
|
-
e.preventDefault();
|
|
120
|
-
e.stopPropagation();
|
|
120
|
+
if (!this._element.matches(':hover')) {
|
|
121
|
+
Classes.remove(this._element, CLASS_NAME_DROP_ACTIVE);
|
|
122
|
+
VGFilesDroppable._setDropMessageTitleState(this._element, false);
|
|
123
|
+
}
|
|
124
|
+
}, 50);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
121
127
|
|
|
122
|
-
|
|
128
|
+
EventHandler.on(this._element, 'drop', (e) => {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
e.stopPropagation();
|
|
131
|
+
|
|
132
|
+
Classes.remove(this._element, [CLASS_NAME_DROP_ACTIVE, CLASS_NAME_DROP_HOVER]);
|
|
133
|
+
VGFilesDroppable._setDropMessageTitleState(this._element, false);
|
|
123
134
|
|
|
124
135
|
// ✅ сортировка: не трогаем input
|
|
125
|
-
if (isSortableDrag(e)) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const files = e.dataTransfer?.files;
|
|
130
|
-
if (!files || !files.length) return;
|
|
136
|
+
if (isSortableDrag(e)) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const files = e.dataTransfer?.files;
|
|
141
|
+
if (!files || !files.length) return;
|
|
131
142
|
|
|
132
143
|
this._files = files;
|
|
133
144
|
|
|
@@ -135,25 +146,209 @@ class VGFilesDroppable extends BaseModule {
|
|
|
135
146
|
this._input.files = files;
|
|
136
147
|
EventHandler.trigger(this._input, 'change');
|
|
137
148
|
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_bindGlobalEvents() {
|
|
153
|
+
if (VGFilesDroppable._isGlobalEventsBound) return;
|
|
154
|
+
|
|
155
|
+
VGFilesDroppable._globalHandlers = {
|
|
156
|
+
dragenter: (e) => this._updateSuggestedDrop(e),
|
|
157
|
+
dragover: (e) => this._updateSuggestedDrop(e),
|
|
158
|
+
dragleave: (e) => {
|
|
159
|
+
if (!this._isFileDrag(e)) return;
|
|
160
|
+
if (e.relatedTarget === null || e.target === document || e.target === document.documentElement) {
|
|
161
|
+
VGFilesDroppable._clearSuggestedDrop();
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
drop: (e) => {
|
|
165
|
+
if (!this._isFileDrag(e)) {
|
|
166
|
+
VGFilesDroppable._clearSuggestedDrop();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const activeDrop = VGFilesDroppable._activeSuggestedDrop;
|
|
171
|
+
if (activeDrop) {
|
|
172
|
+
const instance = Array.from(VGFilesDroppable._smartInstances).find(i => i._element === activeDrop);
|
|
173
|
+
const files = e.dataTransfer?.files;
|
|
174
|
+
|
|
175
|
+
if (instance && files && files.length && isElement(instance._input)) {
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
e.stopPropagation();
|
|
178
|
+
instance._files = files;
|
|
179
|
+
instance._input.files = files;
|
|
180
|
+
EventHandler.trigger(instance._input, 'change');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
VGFilesDroppable._clearSuggestedDrop();
|
|
185
|
+
},
|
|
186
|
+
dragend: () => VGFilesDroppable._clearSuggestedDrop(),
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
EventHandler.on(document, 'dragenter', VGFilesDroppable._globalHandlers.dragenter);
|
|
190
|
+
EventHandler.on(document, 'dragover', VGFilesDroppable._globalHandlers.dragover);
|
|
191
|
+
EventHandler.on(document, 'dragleave', VGFilesDroppable._globalHandlers.dragleave);
|
|
192
|
+
EventHandler.on(document, 'drop', VGFilesDroppable._globalHandlers.drop);
|
|
193
|
+
EventHandler.on(document, 'dragend', VGFilesDroppable._globalHandlers.dragend);
|
|
194
|
+
|
|
195
|
+
EventHandler.on(window, 'dragenter', VGFilesDroppable._globalHandlers.dragenter);
|
|
196
|
+
EventHandler.on(window, 'dragover', VGFilesDroppable._globalHandlers.dragover);
|
|
197
|
+
EventHandler.on(window, 'dragleave', VGFilesDroppable._globalHandlers.dragleave);
|
|
198
|
+
EventHandler.on(window, 'drop', VGFilesDroppable._globalHandlers.drop);
|
|
199
|
+
EventHandler.on(window, 'dragend', VGFilesDroppable._globalHandlers.dragend);
|
|
200
|
+
|
|
201
|
+
VGFilesDroppable._isGlobalEventsBound = true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
_updateSuggestedDrop(e) {
|
|
205
|
+
if (!this._isFileDrag(e)) {
|
|
206
|
+
VGFilesDroppable._clearSuggestedDrop();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const visibleDrops = this._getVisibleDropZonesInViewport();
|
|
211
|
+
|
|
212
|
+
if (visibleDrops.length === 1) {
|
|
213
|
+
const [dropZone] = visibleDrops;
|
|
214
|
+
if (VGFilesDroppable._activeSuggestedDrop !== dropZone) {
|
|
215
|
+
VGFilesDroppable._clearSuggestedDrop();
|
|
216
|
+
Classes.add(dropZone, CLASS_NAME_DROP_ACTIVE);
|
|
217
|
+
VGFilesDroppable._setDropMessageTitleState(dropZone, true);
|
|
218
|
+
VGFilesDroppable._activeSuggestedDrop = dropZone;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
VGFilesDroppable._clearSuggestedDrop();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
_isFileDrag(e) {
|
|
230
|
+
try {
|
|
231
|
+
const dt = e?.dataTransfer;
|
|
232
|
+
if (!dt) return false;
|
|
233
|
+
|
|
234
|
+
if (dt.files && dt.files.length > 0) return true;
|
|
235
|
+
|
|
236
|
+
if (dt.items && dt.items.length > 0) {
|
|
237
|
+
return Array.from(dt.items).some(item => item.kind === 'file');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (dt.types) {
|
|
241
|
+
return Array.from(dt.types).includes('Files');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return false;
|
|
245
|
+
} catch (_) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
_getVisibleDropZonesInViewport() {
|
|
251
|
+
const dropZones = Array.from(Selectors.findAll(`.${CLASS_NAME_DROP}`) || []);
|
|
252
|
+
return dropZones.filter((el) => this._isVisibleInViewport(el));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_isVisibleInViewport(el) {
|
|
256
|
+
if (!isElement(el) || !el.isConnected) return false;
|
|
257
|
+
|
|
258
|
+
const style = window.getComputedStyle(el);
|
|
259
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const rect = el.getBoundingClientRect();
|
|
264
|
+
if (!rect.width || !rect.height) return false;
|
|
265
|
+
|
|
266
|
+
const inViewport =
|
|
267
|
+
rect.bottom > 0 &&
|
|
268
|
+
rect.right > 0 &&
|
|
269
|
+
rect.top < window.innerHeight &&
|
|
270
|
+
rect.left < window.innerWidth;
|
|
271
|
+
|
|
272
|
+
return inViewport;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
static _clearSuggestedDrop() {
|
|
276
|
+
if (!VGFilesDroppable._activeSuggestedDrop) return;
|
|
277
|
+
|
|
278
|
+
VGFilesDroppable._setDropMessageTitleState(VGFilesDroppable._activeSuggestedDrop, false);
|
|
279
|
+
Classes.remove(VGFilesDroppable._activeSuggestedDrop, CLASS_NAME_DROP_ACTIVE);
|
|
280
|
+
VGFilesDroppable._activeSuggestedDrop = null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
static _setDropMessageTitleState(dropElement, isActive) {
|
|
284
|
+
if (!isElement(dropElement)) return;
|
|
285
|
+
|
|
286
|
+
const title = Selectors.find('.vg-files-drop-message .title', dropElement);
|
|
287
|
+
if (!title) return;
|
|
288
|
+
|
|
289
|
+
const originalText = (title.getAttribute('data-drop-original-text') || '').trim() || (title.textContent || '').trim();
|
|
290
|
+
title.setAttribute('data-drop-original-text', originalText);
|
|
291
|
+
|
|
292
|
+
const activeText = (dropElement.getAttribute('data-drop-active-text') || '').trim();
|
|
293
|
+
if (isActive && activeText) {
|
|
294
|
+
title.textContent = activeText;
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
title.textContent = originalText;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
static _unbindGlobalEvents() {
|
|
302
|
+
if (!VGFilesDroppable._isGlobalEventsBound) return;
|
|
303
|
+
|
|
304
|
+
const handlers = VGFilesDroppable._globalHandlers || {};
|
|
305
|
+
|
|
306
|
+
EventHandler.off(document, 'dragenter', handlers.dragenter);
|
|
307
|
+
EventHandler.off(document, 'dragover', handlers.dragover);
|
|
308
|
+
EventHandler.off(document, 'dragleave', handlers.dragleave);
|
|
309
|
+
EventHandler.off(document, 'drop', handlers.drop);
|
|
310
|
+
EventHandler.off(document, 'dragend', handlers.dragend);
|
|
311
|
+
|
|
312
|
+
EventHandler.off(window, 'dragenter', handlers.dragenter);
|
|
313
|
+
EventHandler.off(window, 'dragover', handlers.dragover);
|
|
314
|
+
EventHandler.off(window, 'dragleave', handlers.dragleave);
|
|
315
|
+
EventHandler.off(window, 'drop', handlers.drop);
|
|
316
|
+
EventHandler.off(window, 'dragend', handlers.dragend);
|
|
317
|
+
|
|
318
|
+
VGFilesDroppable._isGlobalEventsBound = false;
|
|
319
|
+
VGFilesDroppable._globalHandlers = null;
|
|
320
|
+
VGFilesDroppable._clearSuggestedDrop();
|
|
321
|
+
}
|
|
140
322
|
|
|
141
323
|
getFiles() {
|
|
142
324
|
return this._files;
|
|
143
325
|
}
|
|
144
326
|
|
|
145
|
-
dispose() {
|
|
146
|
-
EventHandler.off(this._element, 'dragover');
|
|
147
|
-
EventHandler.off(this._element, 'dragenter');
|
|
148
|
-
EventHandler.off(this._element, 'dragleave');
|
|
149
|
-
EventHandler.off(this._element, 'drop');
|
|
150
|
-
|
|
151
|
-
this
|
|
152
|
-
|
|
327
|
+
dispose() {
|
|
328
|
+
EventHandler.off(this._element, 'dragover');
|
|
329
|
+
EventHandler.off(this._element, 'dragenter');
|
|
330
|
+
EventHandler.off(this._element, 'dragleave');
|
|
331
|
+
EventHandler.off(this._element, 'drop');
|
|
332
|
+
|
|
333
|
+
VGFilesDroppable._instances.delete(this);
|
|
334
|
+
VGFilesDroppable._smartInstances.delete(this);
|
|
335
|
+
if (!VGFilesDroppable._smartInstances.size) {
|
|
336
|
+
VGFilesDroppable._unbindGlobalEvents();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
this._input = null;
|
|
340
|
+
this._files = null;
|
|
341
|
+
}
|
|
153
342
|
|
|
154
343
|
init() {
|
|
155
344
|
return this;
|
|
156
345
|
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
VGFilesDroppable._instances = new Set();
|
|
349
|
+
VGFilesDroppable._smartInstances = new Set();
|
|
350
|
+
VGFilesDroppable._isGlobalEventsBound = false;
|
|
351
|
+
VGFilesDroppable._activeSuggestedDrop = null;
|
|
352
|
+
VGFilesDroppable._globalHandlers = null;
|
|
353
|
+
|
|
354
|
+
export default VGFilesDroppable;
|