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._preventOriginalInputFromSubmit();
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
- Number(this._params?.limits?.count) === 1;
77
+ isSingle;
67
78
 
68
79
  if (shouldReplaceOnSingle) {
69
- this.clear();
70
- this.append(filesArray, true);
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
- this._generateHiddenInputs(this._files);
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 = this._element.querySelector('label').getAttribute('for') || '';
430
- const name = this._element.querySelector('#' + idInput)?.name || this._element.querySelector('#' + idInput)?.dataset.originalName || 'files[]';
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.addEventListener('change', () => this.change(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
- el.removeEventListener('change', this.change.bind(this));
186
- el.addEventListener('change', e => this._handleChange(e));
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
- this._element = document.body;
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
- if (value !== null) {
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 container.getElementById(escaped) || container.querySelector(`#${escaped}`);
98
+ return scope.querySelector(`#${escaped}`);
76
99
  } catch (e) {
77
100
  console.warn('Invalid ID in findID:', id, e);
78
101
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgapp",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "",
5
5
  "author": {
6
6
  "name": "Vegas Studio",