vgapp 1.1.0 → 1.1.2

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.
@@ -274,13 +274,18 @@ class VGFormSender extends BaseModule {
274
274
  * @param {FormData|null} data - Дополнительные данные для отправки
275
275
  */
276
276
  request(event, data = null) {
277
- const _this = this;
278
- const mergeFormData = (target, source) => {
279
- source.forEach((value, key) => {
280
- target.set(key, value);
281
- });
282
- return target;
283
- }
277
+ const _this = this;
278
+ const mergeFormData = (target, source) => {
279
+ const replacedKeys = new Set();
280
+ source.forEach((value, key) => {
281
+ if (!replacedKeys.has(key)) {
282
+ target.delete(key);
283
+ replacedKeys.add(key);
284
+ }
285
+ target.append(key, value);
286
+ });
287
+ return target;
288
+ }
284
289
 
285
290
  _this._alertBefore();
286
291
 
@@ -777,4 +782,4 @@ EventHandler.on(document, EVENT_SUBMIT_DATA_API, function (event) {
777
782
  }
778
783
  })
779
784
 
780
- export default VGFormSender;
785
+ export default VGFormSender;
@@ -88,7 +88,13 @@ class VGRollup extends BaseModule {
88
88
  button: {
89
89
  enabled: true,
90
90
  more: "Показать",
91
- less: "Свернуть"
91
+ less: "Свернуть",
92
+ classes: ''
93
+ },
94
+ callbacks: {
95
+ init: () => {},
96
+ expand: () => {},
97
+ collapse: () => {}
92
98
  }
93
99
  }, params));
94
100
 
@@ -255,6 +261,8 @@ class VGRollup extends BaseModule {
255
261
  } else if (content === 'elements') {
256
262
  this._setupElementsContent(element, elementClass, cnt, fade, transition, isEllipsis, isButton, showNum);
257
263
  }
264
+
265
+ execute(this._params.callbacks.init, [element, this])
258
266
  }
259
267
 
260
268
  /**
@@ -336,7 +344,7 @@ class VGRollup extends BaseModule {
336
344
 
337
345
  const btnTextMore = this._params.button.more;
338
346
  const btnHTML = `<div class="${this.classes.button}">
339
- <a href="#" aria-expanded="false" data-vg-toggle="rollup" data-vg-target="#${element.id}">
347
+ <a href="#" aria-expanded="false" data-vg-toggle="rollup" class="${this._params.button.classes}" data-vg-target="#${element.id}">
340
348
  ${btnTextMore}${textNum}
341
349
  </a>
342
350
  </div>`;
@@ -369,6 +377,8 @@ class VGRollup extends BaseModule {
369
377
 
370
378
  if (this._params.fade) Classes.add(el, this.classes.fade);
371
379
  if (this._params.transition) Classes.add(el, this.classes.transition);
380
+
381
+ execute(this._params.callbacks.expand, [el, this])
372
382
  } else if (content === 'elements') {
373
383
  const items = Selectors.findAll('.' + this._params.elements, el);
374
384
  items.forEach((item, index) => {
@@ -376,6 +386,8 @@ class VGRollup extends BaseModule {
376
386
  Classes.add(item, CLASS_NAME_HIDE);
377
387
  }
378
388
  });
389
+
390
+ execute(this._params.callbacks.collapse, [el, items, this])
379
391
  }
380
392
 
381
393
  Classes.add(el, this.classes.container);
@@ -388,6 +400,8 @@ class VGRollup extends BaseModule {
388
400
  const items = Selectors.findAll('.' + this._params.elements, el);
389
401
  items.forEach(item => Classes.remove(item, CLASS_NAME_HIDE));
390
402
  }
403
+
404
+ execute(this._params.callbacks.expand, [el, this])
391
405
  }
392
406
  }
393
407
 
@@ -52,6 +52,7 @@ const SELECTOR_DROPDOWN = `.${CLASS_NAME_DROPDOWN}`;
52
52
  const SELECTOR_SEARCH_INPUT = `.${CLASS_NAME_SEARCH} input`;
53
53
  const SELECTOR_LIST = `.${CLASS_NAME_LIST}`;
54
54
  const SELECTOR_LOAD_MORE_BTN = `.${CLASS_NAME_LOAD_MORE}`;
55
+ const DATA_ATTR_COPY_EXCLUDE_DEFAULT = ['inited', 'updating', 'exclude'];
55
56
 
56
57
  /**
57
58
  * Класс VGSelect
@@ -67,8 +68,8 @@ class VGSelect extends BaseModule {
67
68
  constructor(element, params = {}) {
68
69
  super(element, params);
69
70
 
70
- this._params = this._getParams(element, mergeDeepObject({
71
- lang: document.documentElement.lang || 'ru',
71
+ this._params = this._getParams(element, mergeDeepObject({
72
+ lang: document.documentElement.lang || 'ru',
72
73
  // Dropdown placement behavior:
73
74
  // - none: default CSS positioning (no JS)
74
75
  // - auto: choose top/bottom based on available space in overflow ancestor/viewport
@@ -87,9 +88,10 @@ class VGSelect extends BaseModule {
87
88
  perpage: 20,
88
89
  loadMoreText: 'Загрузить ещё',
89
90
  },
90
- close: true,
91
- tree: false,
92
- placeholder: '',
91
+ close: true,
92
+ tree: false,
93
+ exclude: 'data-filter-param',
94
+ placeholder: '',
93
95
  onInit: null,
94
96
  onShow: null,
95
97
  onHide: null,
@@ -147,46 +149,46 @@ class VGSelect extends BaseModule {
147
149
  * @param {HTMLElement} drop - Контейнер выпадающего списка
148
150
  * @returns {HTMLElement} - Обновлённый список
149
151
  */
150
- static buildListOptions(selector, drop, params = {}) {
151
- let list = drop.querySelector(`.${CLASS_NAME_LIST}`);
152
- if (!list) {
153
- list = document.createElement('ul');
152
+ static buildListOptions(selector, drop, params = {}) {
153
+ let list = drop.querySelector(`.${CLASS_NAME_LIST}`);
154
+ if (!list) {
155
+ list = document.createElement('ul');
154
156
  Classes.add(list, CLASS_NAME_LIST);
155
157
  drop.appendChild(list);
156
158
  } else {
157
159
  list.innerHTML = '';
158
160
  }
159
161
 
160
- const optGroups = Selectors.findAll('optgroup', selector);
161
- const fragment = document.createDocumentFragment();
162
- const isTree = this._isTreeEnabled(selector, params);
163
-
164
- if (optGroups.length > 0) {
165
- optGroups.forEach(optGroup => {
166
- const ol = document.createElement('ol');
167
- Classes.add(ol, CLASS_NAME_OPTGROUP);
162
+ const optGroups = Selectors.findAll('optgroup', selector);
163
+ const fragment = document.createDocumentFragment();
164
+ const isTree = this._isTreeEnabled(selector, params);
165
+
166
+ if (optGroups.length > 0) {
167
+ optGroups.forEach(optGroup => {
168
+ const ol = document.createElement('ol');
169
+ Classes.add(ol, CLASS_NAME_OPTGROUP);
168
170
 
169
171
  const label = document.createElement('li');
170
- label.textContent = optGroup.label.trim();
171
- Classes.add(label, CLASS_NAME_OPTGROUP_TITLE);
172
- ol.appendChild(label);
173
-
174
- VGSelect._createListItems(Selectors.findAll('option', optGroup), ol, selector, {
175
- tree: isTree,
176
- depth: isTree ? 1 : 0,
177
- });
178
- fragment.appendChild(ol);
179
- });
180
- list.appendChild(fragment);
181
- } else {
182
- VGSelect._createListItems(selector.options, list, selector, {
183
- tree: isTree,
184
- depth: 0,
185
- });
186
- }
187
-
188
- return list;
189
- }
172
+ label.textContent = optGroup.label.trim();
173
+ Classes.add(label, CLASS_NAME_OPTGROUP_TITLE);
174
+ ol.appendChild(label);
175
+
176
+ VGSelect._createListItems(Selectors.findAll('option', optGroup), ol, selector, {
177
+ tree: isTree,
178
+ depth: isTree ? 1 : 0,
179
+ });
180
+ fragment.appendChild(ol);
181
+ });
182
+ list.appendChild(fragment);
183
+ } else {
184
+ VGSelect._createListItems(selector.options, list, selector, {
185
+ tree: isTree,
186
+ depth: 0,
187
+ });
188
+ }
189
+
190
+ return list;
191
+ }
190
192
 
191
193
  /**
192
194
  * Создаёт <li> элементы из списка <option>
@@ -195,13 +197,13 @@ class VGSelect extends BaseModule {
195
197
  * @param {HTMLSelectElement} selector - Исходный <select>
196
198
  * @private
197
199
  */
198
- static _createListItems(options, parent, selector, config = {}) {
199
- const frag = document.createDocumentFragment();
200
- const baseDepth = Number.isInteger(config.depth) ? config.depth : 0;
201
- const treeEnabled = !!config.tree;
202
-
203
- [...options].forEach((option) => {
204
- if (option.hidden) return;
200
+ static _createListItems(options, parent, selector, config = {}) {
201
+ const frag = document.createDocumentFragment();
202
+ const baseDepth = Number.isInteger(config.depth) ? config.depth : 0;
203
+ const treeEnabled = !!config.tree;
204
+
205
+ [...options].forEach((option) => {
206
+ if (option.hidden) return;
205
207
 
206
208
  // value атрибута может не быть или он может быть пустым -> для маппинга используем индекс
207
209
  const rawValueAttr = option.getAttribute('value'); // null если атрибута нет
@@ -212,16 +214,16 @@ class VGSelect extends BaseModule {
212
214
  const li = document.createElement('li');
213
215
  li.textContent = text;
214
216
  li.dataset.index = String(option.index);
215
- li.dataset.value = value;
216
- li.classList.add(CLASS_NAME_OPTION);
217
- Manipulator.set(li, 'data-vg-toggle', 'select-option');
218
- if (treeEnabled) {
219
- const optionLevelRaw = option.dataset.level;
220
- const optionLevel = Number.isFinite(Number(optionLevelRaw)) ? parseInt(optionLevelRaw, 10) : null;
221
- const level = Math.max(0, optionLevel == null ? baseDepth : optionLevel);
222
- li.dataset.level = String(level);
223
- li.style.paddingLeft = `${16 + (level * 16)}px`;
224
- }
217
+ li.dataset.value = value;
218
+ li.classList.add(CLASS_NAME_OPTION);
219
+ Manipulator.set(li, 'data-vg-toggle', 'select-option');
220
+ if (treeEnabled) {
221
+ const optionLevelRaw = option.dataset.level;
222
+ const optionLevel = Number.isFinite(Number(optionLevelRaw)) ? parseInt(optionLevelRaw, 10) : null;
223
+ const level = Math.max(0, optionLevel == null ? baseDepth : optionLevel);
224
+ li.dataset.level = String(level);
225
+ li.style.paddingLeft = `${16 + (level * 16)}px`;
226
+ }
225
227
 
226
228
  // Раньше подсветка зависела от "явно выбранных" option (атрибут selected),
227
229
  // из-за этого UI мог показывать placeholder, но DOM считал, что выбрана 1-я опция.
@@ -249,16 +251,35 @@ class VGSelect extends BaseModule {
249
251
  frag.appendChild(li);
250
252
  });
251
253
 
252
- parent.appendChild(frag);
253
- }
254
-
255
- static _isTreeEnabled(selector, params = {}) {
256
- if (typeof params.tree === 'boolean') return params.tree;
257
- if (selector?.dataset && typeof selector.dataset.tree !== 'undefined') {
258
- return normalizeData(selector.dataset.tree) === true;
259
- }
260
-
261
- return false;
254
+ parent.appendChild(frag);
255
+ }
256
+
257
+ static _isTreeEnabled(selector, params = {}) {
258
+ if (typeof params.tree === 'boolean') return params.tree;
259
+ if (selector?.dataset && typeof selector.dataset.tree !== 'undefined') {
260
+ return normalizeData(selector.dataset.tree) === true;
261
+ }
262
+
263
+ return false;
264
+ }
265
+
266
+ static _getDataAttrCopyExclusions(selector, params = {}) {
267
+ const rawExclude = typeof params.exclude === 'string'
268
+ ? params.exclude
269
+ : (selector.dataset.exclude || '');
270
+ const customExcluded = rawExclude
271
+ .split(',')
272
+ .map(item => this._normalizeDataAttrKey(item))
273
+ .filter(Boolean);
274
+
275
+ return new Set([...DATA_ATTR_COPY_EXCLUDE_DEFAULT, ...customExcluded]);
276
+ }
277
+
278
+ static _normalizeDataAttrKey(value) {
279
+ return String(value || '')
280
+ .trim()
281
+ .replace(/^data-/, '')
282
+ .toLowerCase();
262
283
  }
263
284
 
264
285
  /**
@@ -319,7 +340,9 @@ class VGSelect extends BaseModule {
319
340
 
320
341
  const elData = Manipulator.get(selector);
321
342
  if (!isEmptyObj(elData)) {
343
+ const excludeDataAttrs = this._getDataAttrCopyExclusions(selector, params);
322
344
  Object.keys(elData).forEach(key => {
345
+ if (excludeDataAttrs.has(key)) return;
323
346
  Manipulator.set(container, `data-${key}`, elData[key]);
324
347
  });
325
348
  }
@@ -339,11 +362,11 @@ class VGSelect extends BaseModule {
339
362
  tags.classList.add(CLASS_NAME_TAGS);
340
363
  current.appendChild(tags);
341
364
 
342
- const input = document.createElement('input');
343
- input.type = 'text';
344
- input.className = 'vg-select-multiple-input';
345
- input.style.cssText = 'border:0;outline:none;background:transparent;padding:0;margin:0;min-width:1px;width:1px;height:1px;line-height:1;font:inherit;opacity:0;';
346
- tags.appendChild(input);
365
+ const input = document.createElement('input');
366
+ input.type = 'text';
367
+ input.className = 'vg-select-multiple-input';
368
+ input.style.cssText = 'border:0;outline:none;background:transparent;padding:0;margin:0;min-width:1px;width:1px;height:1px;line-height:1;font:inherit;opacity:0;';
369
+ tags.appendChild(input);
347
370
 
348
371
  input.addEventListener('focus', () => {
349
372
  const inst = VGSelect.getInstance(input.closest(`.${CLASS_NAME_CONTAINER}`));
@@ -368,7 +391,7 @@ class VGSelect extends BaseModule {
368
391
  dropdown.classList.add(CLASS_NAME_DROPDOWN);
369
392
  container.appendChild(dropdown);
370
393
 
371
- this.buildListOptions(selector, dropdown, params);
394
+ this.buildListOptions(selector, dropdown, params);
372
395
 
373
396
  selector.insertAdjacentElement('afterend', container);
374
397
  selector.dataset.inited = 'true';
@@ -571,7 +594,7 @@ class VGSelect extends BaseModule {
571
594
  }
572
595
 
573
596
  const drop = this._element.querySelector(SELECTOR_DROPDOWN);
574
- VGSelect.buildListOptions(select, drop, this._params);
597
+ VGSelect.buildListOptions(select, drop, this._params);
575
598
  VGSelect.updateUI(select);
576
599
  }
577
600
 
@@ -765,26 +788,28 @@ class VGSelect extends BaseModule {
765
788
  * @param {string} value - Значение для выбора
766
789
  * @param {Object} [data] - Дополнительные данные
767
790
  */
768
- static changeSelector(select, value, data = {}) {
769
- const container = select.nextElementSibling;
770
- const instance = container ? VGSelect.getInstance(container) : null;
771
- const prevValue = select.value;
772
-
773
- select.setAttribute('data-updating', 'true');
774
- try {
775
- const opt = select.querySelector(`option[value="${CSS.escape(normalizeData(value))}"]`);
791
+ static changeSelector(select, value, data = {}) {
792
+ const container = select.nextElementSibling;
793
+ const instance = container ? VGSelect.getInstance(container) : null;
794
+
795
+ select.setAttribute('data-updating', 'true');
796
+ try {
797
+ const opt = select.querySelector(`option[value="${CSS.escape(normalizeData(value))}"]`);
776
798
  if (!opt) {
777
799
  instance?._triggerEvent(EVENT_KEY_ERROR, { error: 'Option not found', value });
778
800
  return;
779
801
  }
780
802
 
781
- const oldValue = select.value;
782
- const wasSelected = opt.selected;
783
- const selectedText = opt.textContent.trim();
784
-
785
- [...select.options].forEach(o => o.selected = false);
786
- opt.selected = true;
787
- select.value = opt.value;
803
+ const wasSelected = opt.selected;
804
+ const selectedText = opt.textContent.trim();
805
+
806
+ if (select.multiple) {
807
+ opt.selected = data?.selected === false ? false : true;
808
+ } else {
809
+ [...select.options].forEach(o => o.selected = false);
810
+ opt.selected = true;
811
+ select.value = opt.value;
812
+ }
788
813
 
789
814
  this.updateUI(select);
790
815
 
@@ -807,9 +832,9 @@ class VGSelect extends BaseModule {
807
832
  * @param {number} index
808
833
  * @param {Object} [data]
809
834
  */
810
- static changeSelectorByIndex(select, index, data = {}) {
811
- const container = select.nextElementSibling;
812
- const instance = container ? VGSelect.getInstance(container) : null;
835
+ static changeSelectorByIndex(select, index, data = {}) {
836
+ const container = select.nextElementSibling;
837
+ const instance = container ? VGSelect.getInstance(container) : null;
813
838
 
814
839
  select.setAttribute('data-updating', 'true');
815
840
  try {
@@ -819,13 +844,17 @@ class VGSelect extends BaseModule {
819
844
  return;
820
845
  }
821
846
 
822
- const wasSelected = opt.selected;
823
- const selectedText = opt.textContent.trim();
824
- const value = opt.value;
825
-
826
- [...select.options].forEach(o => o.selected = false);
827
- opt.selected = true;
828
- select.value = opt.value;
847
+ const wasSelected = opt.selected;
848
+ const selectedText = opt.textContent.trim();
849
+ const value = opt.value;
850
+
851
+ if (select.multiple) {
852
+ opt.selected = data?.selected === false ? false : true;
853
+ } else {
854
+ [...select.options].forEach(o => o.selected = false);
855
+ opt.selected = true;
856
+ select.value = opt.value;
857
+ }
829
858
 
830
859
  this.updateUI(select);
831
860
 
@@ -1131,13 +1160,13 @@ class VGSelect extends BaseModule {
1131
1160
  optionsData = data.results;
1132
1161
  }
1133
1162
 
1134
- if (!Array.isArray(optionsData)) {
1135
- instance?._triggerEvent(EVENT_KEY_ERROR, { error: 'Invalid data format: expected array' });
1136
- return;
1137
- }
1138
- const treeEnabled = this._isTreeEnabled(select, instance?._params || {});
1139
-
1140
- if (!preserve) {
1163
+ if (!Array.isArray(optionsData)) {
1164
+ instance?._triggerEvent(EVENT_KEY_ERROR, { error: 'Invalid data format: expected array' });
1165
+ return;
1166
+ }
1167
+ const treeEnabled = this._isTreeEnabled(select, instance?._params || {});
1168
+
1169
+ if (!preserve) {
1141
1170
  // Удаление только не помеченных как data-preserve
1142
1171
  [...select.querySelectorAll('option')].forEach(option => {
1143
1172
  const parentOptGroup = option.closest('optgroup');
@@ -1151,62 +1180,62 @@ class VGSelect extends BaseModule {
1151
1180
  if (og.children.length === 0 && !og.hasAttribute('data-preserve')) {
1152
1181
  og.remove();
1153
1182
  }
1154
- });
1155
- }
1156
-
1157
- const appendOption = (item, parent, level = null) => {
1158
- const option = document.createElement('option');
1159
- const hasChildren = Array.isArray(item.children) && item.children.length > 0;
1160
-
1161
- option.value = item.id || '';
1162
- option.textContent = item.text || '';
1163
- if (item.selected) option.selected = true;
1164
- if (item.disabled) option.disabled = true;
1165
- if (treeEnabled && Number.isInteger(level)) {
1166
- option.setAttribute('data-level', String(level));
1167
- if (hasChildren && !option.value) {
1168
- option.disabled = true;
1169
- }
1170
- }
1171
-
1172
- const dataAttrs = Object.keys(item).filter(k => !['id', 'text', 'selected', 'disabled', 'children'].includes(k));
1173
- dataAttrs.forEach(key => {
1174
- option.setAttribute(`data-${key}`, item[key]);
1175
- });
1176
-
1177
- parent.appendChild(option);
1178
- };
1179
-
1180
- const appendTreeOptions = (items, parent, level = 0) => {
1181
- items.forEach(item => {
1182
- appendOption(item, parent, level);
1183
- if (Array.isArray(item.children) && item.children.length > 0) {
1184
- appendTreeOptions(item.children, parent, level + 1);
1185
- }
1186
- });
1187
- };
1188
-
1189
- if (treeEnabled) {
1190
- appendTreeOptions(optionsData, select, 0);
1191
- } else {
1192
- optionsData.forEach(item => {
1193
- if (item.children && Array.isArray(item.children)) {
1194
- const optgroup = document.createElement('optgroup');
1195
- optgroup.label = item.text || '';
1196
- if (item.disabled) optgroup.disabled = true;
1197
-
1198
- item.children.forEach(child => appendOption(child, optgroup));
1199
-
1200
- select.appendChild(optgroup);
1201
- } else {
1202
- appendOption(item, select);
1203
- }
1204
- });
1205
- }
1183
+ });
1184
+ }
1185
+
1186
+ const appendOption = (item, parent, level = null) => {
1187
+ const option = document.createElement('option');
1188
+ const hasChildren = Array.isArray(item.children) && item.children.length > 0;
1189
+
1190
+ option.value = item.id || '';
1191
+ option.textContent = item.text || '';
1192
+ if (item.selected) option.selected = true;
1193
+ if (item.disabled) option.disabled = true;
1194
+ if (treeEnabled && Number.isInteger(level)) {
1195
+ option.setAttribute('data-level', String(level));
1196
+ if (hasChildren && !option.value) {
1197
+ option.disabled = true;
1198
+ }
1199
+ }
1200
+
1201
+ const dataAttrs = Object.keys(item).filter(k => !['id', 'text', 'selected', 'disabled', 'children'].includes(k));
1202
+ dataAttrs.forEach(key => {
1203
+ option.setAttribute(`data-${key}`, item[key]);
1204
+ });
1205
+
1206
+ parent.appendChild(option);
1207
+ };
1208
+
1209
+ const appendTreeOptions = (items, parent, level = 0) => {
1210
+ items.forEach(item => {
1211
+ appendOption(item, parent, level);
1212
+ if (Array.isArray(item.children) && item.children.length > 0) {
1213
+ appendTreeOptions(item.children, parent, level + 1);
1214
+ }
1215
+ });
1216
+ };
1217
+
1218
+ if (treeEnabled) {
1219
+ appendTreeOptions(optionsData, select, 0);
1220
+ } else {
1221
+ optionsData.forEach(item => {
1222
+ if (item.children && Array.isArray(item.children)) {
1223
+ const optgroup = document.createElement('optgroup');
1224
+ optgroup.label = item.text || '';
1225
+ if (item.disabled) optgroup.disabled = true;
1226
+
1227
+ item.children.forEach(child => appendOption(child, optgroup));
1228
+
1229
+ select.appendChild(optgroup);
1230
+ } else {
1231
+ appendOption(item, select);
1232
+ }
1233
+ });
1234
+ }
1206
1235
 
1207
1236
  if (isRebuild) {
1208
1237
  const drop = container.querySelector(`.${CLASS_NAME_DROPDOWN}`);
1209
- VGSelect.buildListOptions(select, drop, instance?._params || {});
1238
+ VGSelect.buildListOptions(select, drop, instance?._params || {});
1210
1239
  instance?._triggerEvent(EVENT_KEY_REBUILD);
1211
1240
  } else {
1212
1241
  this.updateUI(select);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgapp",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "",
5
5
  "author": {
6
6
  "name": "Vegas Studio",