vgapp 1.0.9 → 1.1.1

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.
@@ -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
 
@@ -46,12 +46,13 @@ const EVENT_KEY_CLEAR = `${NAME_KEY}.clear`;
46
46
  const EVENT_KEY_ERROR = `${NAME_KEY}.error`;
47
47
  const EVENT_KEY_LOAD_NEXT = `${NAME_KEY}.loadNext`;
48
48
 
49
- const SELECTOR_DATA_TOGGLE = '[data-vg-toggle="select"]';
50
- const SELECTOR_CURRENT = `.${CLASS_NAME_CURRENT}`;
51
- const SELECTOR_DROPDOWN = `.${CLASS_NAME_DROPDOWN}`;
52
- const SELECTOR_SEARCH_INPUT = `.${CLASS_NAME_SEARCH} input`;
53
- const SELECTOR_LIST = `.${CLASS_NAME_LIST}`;
54
- const SELECTOR_LOAD_MORE_BTN = `.${CLASS_NAME_LOAD_MORE}`;
49
+ const SELECTOR_DATA_TOGGLE = '[data-vg-toggle="select"]';
50
+ const SELECTOR_CURRENT = `.${CLASS_NAME_CURRENT}`;
51
+ const SELECTOR_DROPDOWN = `.${CLASS_NAME_DROPDOWN}`;
52
+ const SELECTOR_SEARCH_INPUT = `.${CLASS_NAME_SEARCH} input`;
53
+ const SELECTOR_LIST = `.${CLASS_NAME_LIST}`;
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,8 +88,10 @@ class VGSelect extends BaseModule {
87
88
  perpage: 20,
88
89
  loadMoreText: 'Загрузить ещё',
89
90
  },
90
- close: true,
91
- placeholder: '',
91
+ close: true,
92
+ tree: false,
93
+ exclude: '',
94
+ placeholder: '',
92
95
  onInit: null,
93
96
  onShow: null,
94
97
  onHide: null,
@@ -146,7 +149,7 @@ class VGSelect extends BaseModule {
146
149
  * @param {HTMLElement} drop - Контейнер выпадающего списка
147
150
  * @returns {HTMLElement} - Обновлённый список
148
151
  */
149
- static buildListOptions(selector, drop) {
152
+ static buildListOptions(selector, drop, params = {}) {
150
153
  let list = drop.querySelector(`.${CLASS_NAME_LIST}`);
151
154
  if (!list) {
152
155
  list = document.createElement('ul');
@@ -158,6 +161,7 @@ class VGSelect extends BaseModule {
158
161
 
159
162
  const optGroups = Selectors.findAll('optgroup', selector);
160
163
  const fragment = document.createDocumentFragment();
164
+ const isTree = this._isTreeEnabled(selector, params);
161
165
 
162
166
  if (optGroups.length > 0) {
163
167
  optGroups.forEach(optGroup => {
@@ -169,12 +173,18 @@ class VGSelect extends BaseModule {
169
173
  Classes.add(label, CLASS_NAME_OPTGROUP_TITLE);
170
174
  ol.appendChild(label);
171
175
 
172
- VGSelect._createListItems(Selectors.findAll('option', optGroup), ol, selector);
176
+ VGSelect._createListItems(Selectors.findAll('option', optGroup), ol, selector, {
177
+ tree: isTree,
178
+ depth: isTree ? 1 : 0,
179
+ });
173
180
  fragment.appendChild(ol);
174
181
  });
175
182
  list.appendChild(fragment);
176
183
  } else {
177
- VGSelect._createListItems(selector.options, list, selector);
184
+ VGSelect._createListItems(selector.options, list, selector, {
185
+ tree: isTree,
186
+ depth: 0,
187
+ });
178
188
  }
179
189
 
180
190
  return list;
@@ -187,10 +197,10 @@ class VGSelect extends BaseModule {
187
197
  * @param {HTMLSelectElement} selector - Исходный <select>
188
198
  * @private
189
199
  */
190
- static _createListItems(options, parent, selector) {
200
+ static _createListItems(options, parent, selector, config = {}) {
191
201
  const frag = document.createDocumentFragment();
192
- const selectedIndex = selector.selectedIndex;
193
- const hasExplicitSelected = VGSelect.hasExplicitSelectedOption(selector);
202
+ const baseDepth = Number.isInteger(config.depth) ? config.depth : 0;
203
+ const treeEnabled = !!config.tree;
194
204
 
195
205
  [...options].forEach((option) => {
196
206
  if (option.hidden) return;
@@ -207,6 +217,13 @@ class VGSelect extends BaseModule {
207
217
  li.dataset.value = value;
208
218
  li.classList.add(CLASS_NAME_OPTION);
209
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
+ }
210
227
 
211
228
  // Раньше подсветка зависела от "явно выбранных" option (атрибут selected),
212
229
  // из-за этого UI мог показывать placeholder, но DOM считал, что выбрана 1-я опция.
@@ -237,6 +254,35 @@ class VGSelect extends BaseModule {
237
254
  parent.appendChild(frag);
238
255
  }
239
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()
283
+ .replace(/-([a-z0-9])/g, (_, chr) => chr.toUpperCase());
284
+ }
285
+
240
286
  /**
241
287
  * Проверяет, является ли значение "пустым" (соответствует placeholder)
242
288
  * @param {HTMLSelectElement} select - Элемент <select>
@@ -293,12 +339,14 @@ class VGSelect extends BaseModule {
293
339
  container.classList.add('disabled');
294
340
  }
295
341
 
296
- const elData = Manipulator.get(selector);
297
- if (!isEmptyObj(elData)) {
298
- Object.keys(elData).forEach(key => {
299
- Manipulator.set(container, `data-${key}`, elData[key]);
300
- });
301
- }
342
+ const elData = Manipulator.get(selector);
343
+ if (!isEmptyObj(elData)) {
344
+ const excludeDataAttrs = this._getDataAttrCopyExclusions(selector, params);
345
+ Object.keys(elData).forEach(key => {
346
+ if (excludeDataAttrs.has(key)) return;
347
+ Manipulator.set(container, `data-${key}`, elData[key]);
348
+ });
349
+ }
302
350
 
303
351
  const placeholder = selector.dataset.placeholder || '';
304
352
  const isMultiple = selector.multiple;
@@ -318,7 +366,7 @@ class VGSelect extends BaseModule {
318
366
  const input = document.createElement('input');
319
367
  input.type = 'text';
320
368
  input.className = 'vg-select-multiple-input';
321
- input.style.cssText = 'border:none;outline:none;background:transparent;padding:0;margin:0;min-width:40px;font:inherit;';
369
+ 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;';
322
370
  tags.appendChild(input);
323
371
 
324
372
  input.addEventListener('focus', () => {
@@ -344,7 +392,7 @@ class VGSelect extends BaseModule {
344
392
  dropdown.classList.add(CLASS_NAME_DROPDOWN);
345
393
  container.appendChild(dropdown);
346
394
 
347
- this.buildListOptions(selector, dropdown);
395
+ this.buildListOptions(selector, dropdown, params);
348
396
 
349
397
  selector.insertAdjacentElement('afterend', container);
350
398
  selector.dataset.inited = 'true';
@@ -353,6 +401,8 @@ class VGSelect extends BaseModule {
353
401
  this.updateUI(selector);
354
402
  const instance = VGSelect.getInstance(container);
355
403
 
404
+ console.log(instance);
405
+
356
406
  let searchInput = null;
357
407
  if (Manipulator.has(selector, 'data-search-enabled')) {
358
408
  const search = document.createElement('div');
@@ -547,7 +597,7 @@ class VGSelect extends BaseModule {
547
597
  }
548
598
 
549
599
  const drop = this._element.querySelector(SELECTOR_DROPDOWN);
550
- VGSelect.buildListOptions(select, drop);
600
+ VGSelect.buildListOptions(select, drop, this._params);
551
601
  VGSelect.updateUI(select);
552
602
  }
553
603
 
@@ -1111,6 +1161,7 @@ class VGSelect extends BaseModule {
1111
1161
  instance?._triggerEvent(EVENT_KEY_ERROR, { error: 'Invalid data format: expected array' });
1112
1162
  return;
1113
1163
  }
1164
+ const treeEnabled = this._isTreeEnabled(select, instance?._params || {});
1114
1165
 
1115
1166
  if (!preserve) {
1116
1167
  // Удаление только не помеченных как data-preserve
@@ -1129,47 +1180,59 @@ class VGSelect extends BaseModule {
1129
1180
  });
1130
1181
  }
1131
1182
 
1132
- optionsData.forEach(item => {
1133
- if (item.children && Array.isArray(item.children)) {
1134
- const optgroup = document.createElement('optgroup');
1135
- optgroup.label = item.text || '';
1136
- if (item.disabled) optgroup.disabled = true;
1137
-
1138
- item.children.forEach(child => {
1139
- const option = document.createElement('option');
1140
- option.value = child.id || '';
1141
- option.textContent = child.text || '';
1142
- if (child.selected) option.selected = true;
1143
- if (child.disabled) option.disabled = true;
1144
-
1145
- const dataAttrs = Object.keys(child).filter(k => !['id', 'text', 'selected', 'disabled'].includes(k));
1146
- dataAttrs.forEach(key => {
1147
- option.setAttribute(`data-${key}`, child[key]);
1148
- });
1183
+ const appendOption = (item, parent, level = null) => {
1184
+ const option = document.createElement('option');
1185
+ const hasChildren = Array.isArray(item.children) && item.children.length > 0;
1186
+
1187
+ option.value = item.id || '';
1188
+ option.textContent = item.text || '';
1189
+ if (item.selected) option.selected = true;
1190
+ if (item.disabled) option.disabled = true;
1191
+ if (treeEnabled && Number.isInteger(level)) {
1192
+ option.setAttribute('data-level', String(level));
1193
+ if (hasChildren && !option.value) {
1194
+ option.disabled = true;
1195
+ }
1196
+ }
1149
1197
 
1150
- optgroup.appendChild(option);
1151
- });
1198
+ const dataAttrs = Object.keys(item).filter(k => !['id', 'text', 'selected', 'disabled', 'children'].includes(k));
1199
+ dataAttrs.forEach(key => {
1200
+ option.setAttribute(`data-${key}`, item[key]);
1201
+ });
1152
1202
 
1153
- select.appendChild(optgroup);
1154
- } else {
1155
- const option = document.createElement('option');
1156
- option.value = item.id || '';
1157
- option.textContent = item.text || '';
1158
- if (item.selected) option.selected = true;
1159
- if (item.disabled) option.disabled = true;
1160
-
1161
- const dataAttrs = Object.keys(item).filter(k => !['id', 'text', 'selected', 'disabled'].includes(k));
1162
- dataAttrs.forEach(key => {
1163
- option.setAttribute(`data-${key}`, item[key]);
1164
- });
1203
+ parent.appendChild(option);
1204
+ };
1165
1205
 
1166
- select.appendChild(option);
1167
- }
1168
- });
1206
+ const appendTreeOptions = (items, parent, level = 0) => {
1207
+ items.forEach(item => {
1208
+ appendOption(item, parent, level);
1209
+ if (Array.isArray(item.children) && item.children.length > 0) {
1210
+ appendTreeOptions(item.children, parent, level + 1);
1211
+ }
1212
+ });
1213
+ };
1214
+
1215
+ if (treeEnabled) {
1216
+ appendTreeOptions(optionsData, select, 0);
1217
+ } else {
1218
+ optionsData.forEach(item => {
1219
+ if (item.children && Array.isArray(item.children)) {
1220
+ const optgroup = document.createElement('optgroup');
1221
+ optgroup.label = item.text || '';
1222
+ if (item.disabled) optgroup.disabled = true;
1223
+
1224
+ item.children.forEach(child => appendOption(child, optgroup));
1225
+
1226
+ select.appendChild(optgroup);
1227
+ } else {
1228
+ appendOption(item, select);
1229
+ }
1230
+ });
1231
+ }
1169
1232
 
1170
1233
  if (isRebuild) {
1171
1234
  const drop = container.querySelector(`.${CLASS_NAME_DROPDOWN}`);
1172
- VGSelect.buildListOptions(select, drop);
1235
+ VGSelect.buildListOptions(select, drop, instance?._params || {});
1173
1236
  instance?._triggerEvent(EVENT_KEY_REBUILD);
1174
1237
  } else {
1175
1238
  this.updateUI(select);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgapp",
3
- "version": "1.0.9",
3
+ "version": "1.1.1",
4
4
  "description": "",
5
5
  "author": {
6
6
  "name": "Vegas Studio",