vgapp 0.9.2 → 0.9.4

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.
@@ -61,6 +61,17 @@ class VGFilesBase extends BaseModule {
61
61
 
62
62
  const filesArray = Array.from(incomingFiles);
63
63
 
64
+ const shouldReplaceOnSingle =
65
+ Boolean(this._params?.replace) &&
66
+ Number(this._params?.limits?.count) === 1;
67
+
68
+ if (shouldReplaceOnSingle) {
69
+ this.clear();
70
+ this.append(filesArray, true);
71
+ this.build();
72
+ return;
73
+ }
74
+
64
75
  if (!this._params.allowed) {
65
76
  this.append(filesArray, false);
66
77
  this.build();
@@ -262,7 +273,7 @@ class VGFilesBase extends BaseModule {
262
273
  const fragment = document.createDocumentFragment();
263
274
 
264
275
  files.forEach((file) => {
265
- let classes = $itemsTemplateClasses;;
276
+ let classes = $itemsTemplateClasses;
266
277
 
267
278
  if (this._params.detach) classes.push('with-remove')
268
279
  if (this._params.sortable.enabled) classes.push('with-sortable')
@@ -415,12 +426,21 @@ class VGFilesBase extends BaseModule {
415
426
  _generateHiddenInputs(files) {
416
427
  this._cleanupFakeInputs();
417
428
  const fragment = document.createDocumentFragment();
418
- const name = this._element.querySelector('[data-vg-toggle]')?.name || 'files[]';
429
+ const idInput = this._element.querySelector('label').getAttribute('for') || '';
430
+ const name = this._element.querySelector('#' + idInput)?.name || this._element.querySelector('#' + idInput)?.dataset.originalName || 'files[]';
431
+
432
+ // если name уже "files[]" — убираем скобки, чтобы дальше корректно собрать имя
433
+ const baseName = name.endsWith('[]') ? name.slice(0, -2) : name;
434
+ const isSingle = Number(this._params?.limits?.count) === 1;
419
435
 
420
436
  files.forEach((file, index) => {
421
437
  const input = document.createElement('input');
422
438
  input.type = 'file';
423
- input.name = `${name.replace('[]', '')}[${index}]`;
439
+
440
+ // count=1 => name без массива (без [] и без [0])
441
+ // иначе => name как массив: baseName[index]
442
+ input.name = isSingle ? baseName : `${baseName}[${index}]`;
443
+
424
444
  input.dataset.vgFiles = 'generated';
425
445
  Manipulator.hide(input);
426
446
 
@@ -6,7 +6,7 @@ import { Classes, Manipulator } from "../../../utils/js/dom/manipulator";
6
6
  class VGFilesTemplateRender {
7
7
  constructor(vgFilesInstance, element, params = {}) {
8
8
  this.module = vgFilesInstance;
9
- this.element = isElement(element);
9
+ this.element = isElement(element) ? element : null;
10
10
 
11
11
  if (!this.element) {
12
12
  console.error('Invalid element provided:', element);
@@ -41,6 +41,7 @@ class VGFiles extends VGFilesBase {
41
41
  types: [],
42
42
  ajax: false,
43
43
  prepend: true,
44
+ replace: true,
44
45
  uploads: {
45
46
  mode: 'sequential',
46
47
  route: '',
@@ -98,13 +99,15 @@ class VGFiles extends VGFilesBase {
98
99
 
99
100
  if (this._params.ajax) {
100
101
  this._params.allowed = false;
102
+ }
101
103
 
102
- if (this.isRenderNonInit) return;
103
- this.isRenderNonInit = this._render.init();
104
- if (this._render.parsedFiles.length) {
105
- this._addExternalFiles(this._render.parsedFiles);
106
- }
104
+ if (this.isRenderNonInit) return;
105
+ this.isRenderNonInit = this._render.init();
106
+
107
+ if (this._render.parsedFiles.length) {
108
+ this._addExternalFiles(this._render.parsedFiles);
107
109
  }
110
+
108
111
  if (this._params.allowed && !this._params.ajax) this._params.detach = false;
109
112
  if (this._nodes.drop) {
110
113
  this._params.image = true;
@@ -186,8 +189,11 @@ class VGFiles extends VGFilesBase {
186
189
 
187
190
  _handleChange(e) {
188
191
  if (this._params.ajax) this.uploadAll(this._files);
189
- this._triggerCallback('onChange', { files: this._files, input: e?.target || e?.src || '' });
190
- EventHandler.trigger(this._element, `${NAME_KEY}.change`, { files: this._files });
192
+
193
+ const payload = { files: this._files, input: e?.target || e?.src || '' };
194
+
195
+ this._triggerCallback('onChange', payload);
196
+ this._triggerEvent('change', { files: this._files, input: payload.input });
191
197
  }
192
198
 
193
199
  async uploadAll(files) {
@@ -232,6 +238,10 @@ class VGFiles extends VGFilesBase {
232
238
  files: notUploadedFiles,
233
239
  total: notUploadedFiles.length
234
240
  });
241
+ this._triggerEvent('upload.start', {
242
+ files: notUploadedFiles,
243
+ total: notUploadedFiles.length
244
+ });
235
245
 
236
246
  try {
237
247
  await this._uploader.uploadFiles(notUploadedFiles, this._params.uploads.route, uploadParams);
@@ -322,12 +332,15 @@ class VGFiles extends VGFilesBase {
322
332
  }
323
333
  }
324
334
 
325
- this._triggerCallback('onUploadProgress', {
335
+ const payload = {
326
336
  file: uploadData.file,
327
337
  progress: uploadData.progress,
328
338
  bytesSent: uploadData.bytesSent,
329
339
  totalBytes: uploadData.totalBytes
330
- });
340
+ };
341
+
342
+ this._triggerCallback('onUploadProgress', payload);
343
+ this._triggerEvent('upload.progress', payload);
331
344
  });
332
345
 
333
346
  this._uploader.onComplete((uploadData) => {
@@ -372,12 +385,15 @@ class VGFiles extends VGFilesBase {
372
385
  this._setStatItem('completed', this._uploadedKeys.size);
373
386
  this._setStatItem('pending', this._pendingUploadedKeys.size);
374
387
 
375
- this._triggerCallback('onUploadComplete', {
388
+ const payload = {
376
389
  file: uploadData.file,
377
390
  response: uploadData.result?.response,
378
391
  status: uploadData.result?.status,
379
392
  id: file.id
380
- });
393
+ };
394
+
395
+ this._triggerCallback('onUploadComplete', payload);
396
+ this._triggerEvent('upload.complete', payload);
381
397
  });
382
398
 
383
399
  this._uploader.onError((uploadData) => {
@@ -413,13 +429,14 @@ class VGFiles extends VGFilesBase {
413
429
  }
414
430
  }
415
431
 
416
- this._triggerCallback('onUploadError', {
417
- file: uploadData.file
418
- });
432
+ const payload = { file: uploadData.file };
433
+
434
+ this._triggerCallback('onUploadError', payload);
435
+ this._triggerEvent('upload.error', payload);
419
436
  });
420
437
 
421
438
  this._uploader.onAllComplete(() => {
422
- EventHandler.trigger(this._element, `${NAME_KEY}.upload.allComplete`);
439
+ this._triggerEvent('upload.allComplete');
423
440
  this._updateStat(false);
424
441
 
425
442
  if (!this._failingUploadedKeys.size) {
@@ -436,11 +453,13 @@ class VGFiles extends VGFilesBase {
436
453
  }
437
454
  }
438
455
 
439
- this._triggerCallback('onUploadAllComplete', {
456
+ const payload = {
440
457
  uploaded: this._uploadedKeys.size,
441
458
  failed: this._failingUploadedKeys.size,
442
459
  total: this._files.length
443
- });
460
+ };
461
+
462
+ this._triggerCallback('onUploadAllComplete', payload);
444
463
  });
445
464
  }
446
465
 
@@ -467,10 +486,13 @@ class VGFiles extends VGFilesBase {
467
486
  this._setStatItem('failing', this._failingUploadedKeys.size);
468
487
  this._setStatItem('pending', this._pendingUploadedKeys.size);
469
488
 
470
- this._triggerCallback('onReload', {
489
+ const payload = {
471
490
  button: button,
472
491
  file: fileToRetry
473
- });
492
+ };
493
+
494
+ this._triggerCallback('onReload', payload);
495
+ this._triggerEvent('reload', payload);
474
496
 
475
497
  this.upload(fileToRetry);
476
498
  }
@@ -567,13 +589,16 @@ class VGFiles extends VGFilesBase {
567
589
 
568
590
  this._resetFileInput();
569
591
 
570
- this._triggerCallback('onRemoveFile', {
592
+ const payload = {
571
593
  button: button,
572
594
  name: name,
573
595
  size: size,
574
596
  id: id,
575
597
  remaining: this._files.length
576
- });
598
+ };
599
+
600
+ this._triggerCallback('onRemoveFile', payload);
601
+ this._triggerEvent('remove', payload);
577
602
  }
578
603
 
579
604
  _updateStatsAfterRemove() {
@@ -707,6 +732,10 @@ class VGFiles extends VGFilesBase {
707
732
  }
708
733
  }
709
734
 
735
+ _triggerEvent(suffix, payload = {}) {
736
+ EventHandler.trigger(this._element, `${NAME_KEY}.${suffix}`, payload);
737
+ }
738
+
710
739
  _getUploadedIds() {
711
740
  return Array.from(this._uploadedKeys).map(key => {
712
741
  const file = this._files.find(f => this._getFileKey(f) === key);
@@ -87,7 +87,9 @@ const _handlersVGSelect = () => {
87
87
 
88
88
  container.querySelectorAll(`.${CLASS_NAME_OPTION}`).forEach(o => o.classList.remove('selected'));
89
89
  option.classList.add('selected');
90
- VGSelect.changeSelector(select, value, {
90
+
91
+ VGSelect.changeSelectorByIndex(select, idx, {
92
+ index: idx,
91
93
  value,
92
94
  title: option.textContent,
93
95
  ...Manipulator.get(option)
@@ -656,6 +656,47 @@ class VGSelect extends BaseModule {
656
656
  }
657
657
  }
658
658
 
659
+ /**
660
+ * Программно устанавливает выбор <select> по индексу option (надёжно даже при пустом/отсутствующем value)
661
+ * @param {HTMLSelectElement} select
662
+ * @param {number} index
663
+ * @param {Object} [data]
664
+ */
665
+ static changeSelectorByIndex(select, index, data = {}) {
666
+ const container = select.nextElementSibling;
667
+ const instance = container ? VGSelect.getInstance(container) : null;
668
+
669
+ select.setAttribute('data-updating', 'true');
670
+ try {
671
+ const opt = Number.isInteger(index) ? select.options[index] : null;
672
+ if (!opt) {
673
+ instance?._triggerEvent(EVENT_KEY_ERROR, { error: 'Option not found by index', index });
674
+ return;
675
+ }
676
+
677
+ const wasSelected = opt.selected;
678
+ const selectedText = opt.textContent.trim();
679
+ const value = opt.value;
680
+
681
+ [...select.options].forEach(o => o.selected = false);
682
+ opt.selected = true;
683
+ select.value = opt.value;
684
+
685
+ this.updateUI(select);
686
+
687
+ const e = new Event('change', { bubbles: true, cancelable: true });
688
+ select.dispatchEvent(e);
689
+
690
+ if (!wasSelected) {
691
+ EventHandler.trigger(select, EVENT_KEY_CHANGE, { data });
692
+ instance?._triggerEvent(EVENT_KEY_SELECT, { value, text: selectedText, data });
693
+ instance?._callCallback('onSelect', { value, text: selectedText, data });
694
+ }
695
+ } finally {
696
+ select.removeAttribute('data-updating');
697
+ }
698
+ }
699
+
659
700
  /**
660
701
  * Вызывает кастомное событие
661
702
  * @param {string} eventName - Имя события
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgapp",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "",
5
5
  "author": {
6
6
  "name": "Vegas Studio",