vgapp 0.9.4 → 0.9.6
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.
|
@@ -31,6 +31,8 @@ class VGFilesBase extends BaseModule {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
this.template = '<li data-file="" class="file"><div class="file-image"></div><div class="file-info"></div><div class="file-remove"></div></li>';
|
|
34
|
+
this._onNativeInputChange = (e) => this.change(e?.target);
|
|
35
|
+
|
|
34
36
|
this._init();
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -51,7 +53,15 @@ class VGFilesBase extends BaseModule {
|
|
|
51
53
|
_init() {
|
|
52
54
|
if (!this._isInitialized) return;
|
|
53
55
|
|
|
54
|
-
this.
|
|
56
|
+
const isSingle = Number(this._params?.limits?.count) === 1;
|
|
57
|
+
|
|
58
|
+
if (isSingle) {
|
|
59
|
+
this._preventOriginalInputFromSubmit(true);
|
|
60
|
+
this._cleanupFakeInputs();
|
|
61
|
+
} else {
|
|
62
|
+
this._preventOriginalInputFromSubmit();
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
this._addEventListener();
|
|
56
66
|
}
|
|
57
67
|
|
|
@@ -61,13 +71,19 @@ class VGFilesBase extends BaseModule {
|
|
|
61
71
|
|
|
62
72
|
const filesArray = Array.from(incomingFiles);
|
|
63
73
|
|
|
74
|
+
const isSingle = Number(this._params?.limits?.count) === 1;
|
|
64
75
|
const shouldReplaceOnSingle =
|
|
65
76
|
Boolean(this._params?.replace) &&
|
|
66
|
-
|
|
77
|
+
isSingle;
|
|
67
78
|
|
|
68
79
|
if (shouldReplaceOnSingle) {
|
|
69
|
-
this.
|
|
70
|
-
this.
|
|
80
|
+
this._revokeUrls();
|
|
81
|
+
this._cleanupFakeInputs();
|
|
82
|
+
this._cleanupErrors();
|
|
83
|
+
|
|
84
|
+
this._files = this._filterFiles(filesArray);
|
|
85
|
+
if (this._params.prepend) this._files.reverse();
|
|
86
|
+
|
|
71
87
|
this.build();
|
|
72
88
|
return;
|
|
73
89
|
}
|
|
@@ -94,7 +110,13 @@ class VGFilesBase extends BaseModule {
|
|
|
94
110
|
this._renderUI(this._files);
|
|
95
111
|
} else {
|
|
96
112
|
this._renderUI(this._files);
|
|
97
|
-
|
|
113
|
+
|
|
114
|
+
const isSingle = Number(this._params?.limits?.count) === 1;
|
|
115
|
+
if (!isSingle) {
|
|
116
|
+
this._generateHiddenInputs(this._files);
|
|
117
|
+
} else {
|
|
118
|
+
this._cleanupFakeInputs();
|
|
119
|
+
}
|
|
98
120
|
}
|
|
99
121
|
}
|
|
100
122
|
|
|
@@ -426,21 +448,18 @@ class VGFilesBase extends BaseModule {
|
|
|
426
448
|
_generateHiddenInputs(files) {
|
|
427
449
|
this._cleanupFakeInputs();
|
|
428
450
|
const fragment = document.createDocumentFragment();
|
|
429
|
-
const idInput =
|
|
430
|
-
const name = this._element
|
|
451
|
+
const idInput = Manipulator.get(Selectors.find('label', this._element), 'for') || '';
|
|
452
|
+
const name = Selectors.findID(idInput, this._element)?.name || Selectors.findID(idInput, this._element)?.dataset.originalName || 'files[]';
|
|
431
453
|
|
|
432
|
-
// если name уже "files[]" — убираем скобки, чтобы дальше корректно собрать имя
|
|
433
454
|
const baseName = name.endsWith('[]') ? name.slice(0, -2) : name;
|
|
434
455
|
const isSingle = Number(this._params?.limits?.count) === 1;
|
|
435
456
|
|
|
457
|
+
if (isSingle) return;
|
|
458
|
+
|
|
436
459
|
files.forEach((file, index) => {
|
|
437
460
|
const input = document.createElement('input');
|
|
438
461
|
input.type = 'file';
|
|
439
|
-
|
|
440
|
-
// count=1 => name без массива (без [] и без [0])
|
|
441
|
-
// иначе => name как массив: baseName[index]
|
|
442
|
-
input.name = isSingle ? baseName : `${baseName}[${index}]`;
|
|
443
|
-
|
|
462
|
+
input.name = `${baseName}[${index}]`;
|
|
444
463
|
input.dataset.vgFiles = 'generated';
|
|
445
464
|
Manipulator.hide(input);
|
|
446
465
|
|
|
@@ -506,7 +525,8 @@ class VGFilesBase extends BaseModule {
|
|
|
506
525
|
|
|
507
526
|
_addEventListener() {
|
|
508
527
|
Selectors.findAll('[data-vg-toggle="files"]', this._element).forEach(el => {
|
|
509
|
-
el.
|
|
528
|
+
el.removeEventListener('change', this._onNativeInputChange);
|
|
529
|
+
el.addEventListener('change', this._onNativeInputChange);
|
|
510
530
|
});
|
|
511
531
|
}
|
|
512
532
|
|
|
@@ -182,12 +182,18 @@ class VGFiles extends VGFilesBase {
|
|
|
182
182
|
|
|
183
183
|
_addEventListenerExtended() {
|
|
184
184
|
Selectors.findAll(SELECTOR_DATA_TOGGLE, this._element).forEach(el => {
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
if (this._onNativeInputChange) {
|
|
186
|
+
el.removeEventListener('change', this._onNativeInputChange);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
el.addEventListener('change', (e) => this._handleChange(e));
|
|
187
190
|
});
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
_handleChange(e) {
|
|
194
|
+
const input = e?.target;
|
|
195
|
+
this.change(input);
|
|
196
|
+
|
|
191
197
|
if (this._params.ajax) this.uploadAll(this._files);
|
|
192
198
|
|
|
193
199
|
const payload = { files: this._files, input: e?.target || e?.src || '' };
|
|
@@ -26,7 +26,12 @@ const PROPERTY_MARGIN = 'margin-right';
|
|
|
26
26
|
*/
|
|
27
27
|
class ScrollBarHelper {
|
|
28
28
|
constructor() {
|
|
29
|
-
|
|
29
|
+
// Не кэшируем body: при Turbo/partial navigation DOM может меняться.
|
|
30
|
+
// Актуальный body берём через геттер.
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get _element() {
|
|
34
|
+
return document.body;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
/**
|
|
@@ -50,7 +55,7 @@ class ScrollBarHelper {
|
|
|
50
55
|
* Скрывает скроллбар и корректирует макет
|
|
51
56
|
*/
|
|
52
57
|
hide() {
|
|
53
|
-
if (this.isOverflowing && !isMobileDevice()) {
|
|
58
|
+
if (this.isOverflowing() && !isMobileDevice()) {
|
|
54
59
|
const width = this.getWidth();
|
|
55
60
|
this._disableOverflow();
|
|
56
61
|
|
|
@@ -137,13 +142,15 @@ class ScrollBarHelper {
|
|
|
137
142
|
* @private
|
|
138
143
|
*/
|
|
139
144
|
_saveInitialStyle(element, property) {
|
|
145
|
+
// Сохраняем только один раз, чтобы "оригинал" не перетирался
|
|
146
|
+
// при повторных открытиях/закрытиях.
|
|
147
|
+
if (Manipulator.get(element, property) !== null) return;
|
|
148
|
+
|
|
140
149
|
const value = element.style.getPropertyValue(property);
|
|
141
150
|
|
|
142
|
-
// Важно: сохраняем даже пустую строку (если inline-стиля не было)
|
|
151
|
+
// Важно: сохраняем даже пустую строку (если inline-стиля не было),
|
|
143
152
|
// чтобы reset() мог корректно восстановить "как было".
|
|
144
|
-
|
|
145
|
-
//Manipulator.set(element, property, value);
|
|
146
|
-
}
|
|
153
|
+
Manipulator.set(element, property, value);
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
/**
|
|
@@ -69,10 +69,33 @@ const Selectors = {
|
|
|
69
69
|
* @param {Element} [container=document]
|
|
70
70
|
* @returns {Element|null}
|
|
71
71
|
*/
|
|
72
|
+
/**
|
|
73
|
+
* Находит элемент по ID с экранированием
|
|
74
|
+
* @param {string} id
|
|
75
|
+
* @param {Document|Element} [container=document]
|
|
76
|
+
* @returns {Element|null}
|
|
77
|
+
*/
|
|
72
78
|
findID(id, container = document) {
|
|
73
79
|
try {
|
|
80
|
+
if (typeof id !== 'string' || id.trim() === '') return null;
|
|
81
|
+
|
|
82
|
+
const doc = container && container.nodeType === 9
|
|
83
|
+
? container
|
|
84
|
+
: (container?.ownerDocument || document);
|
|
85
|
+
|
|
86
|
+
const byId = doc.getElementById(id);
|
|
87
|
+
if (byId) {
|
|
88
|
+
if (container && container.nodeType !== 9) {
|
|
89
|
+
return container.contains(byId) ? byId : null;
|
|
90
|
+
}
|
|
91
|
+
return byId;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const scope = (container && container.nodeType === 9) ? container.documentElement : container;
|
|
95
|
+
if (!scope || typeof scope.querySelector !== 'function') return null;
|
|
96
|
+
|
|
74
97
|
const escaped = escapeId(id);
|
|
75
|
-
return
|
|
98
|
+
return scope.querySelector(`#${escaped}`);
|
|
76
99
|
} catch (e) {
|
|
77
100
|
console.warn('Invalid ID in findID:', id, e);
|
|
78
101
|
return null;
|