vgapp 1.0.9 → 1.1.0

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.
@@ -67,8 +67,8 @@ class VGSelect extends BaseModule {
67
67
  constructor(element, params = {}) {
68
68
  super(element, params);
69
69
 
70
- this._params = this._getParams(element, mergeDeepObject({
71
- lang: document.documentElement.lang || 'ru',
70
+ this._params = this._getParams(element, mergeDeepObject({
71
+ lang: document.documentElement.lang || 'ru',
72
72
  // Dropdown placement behavior:
73
73
  // - none: default CSS positioning (no JS)
74
74
  // - auto: choose top/bottom based on available space in overflow ancestor/viewport
@@ -87,8 +87,9 @@ class VGSelect extends BaseModule {
87
87
  perpage: 20,
88
88
  loadMoreText: 'Загрузить ещё',
89
89
  },
90
- close: true,
91
- placeholder: '',
90
+ close: true,
91
+ tree: false,
92
+ placeholder: '',
92
93
  onInit: null,
93
94
  onShow: null,
94
95
  onHide: null,
@@ -146,39 +147,46 @@ class VGSelect extends BaseModule {
146
147
  * @param {HTMLElement} drop - Контейнер выпадающего списка
147
148
  * @returns {HTMLElement} - Обновлённый список
148
149
  */
149
- static buildListOptions(selector, drop) {
150
- let list = drop.querySelector(`.${CLASS_NAME_LIST}`);
151
- if (!list) {
152
- list = document.createElement('ul');
150
+ static buildListOptions(selector, drop, params = {}) {
151
+ let list = drop.querySelector(`.${CLASS_NAME_LIST}`);
152
+ if (!list) {
153
+ list = document.createElement('ul');
153
154
  Classes.add(list, CLASS_NAME_LIST);
154
155
  drop.appendChild(list);
155
156
  } else {
156
157
  list.innerHTML = '';
157
158
  }
158
159
 
159
- const optGroups = Selectors.findAll('optgroup', selector);
160
- const fragment = document.createDocumentFragment();
161
-
162
- if (optGroups.length > 0) {
163
- optGroups.forEach(optGroup => {
164
- const ol = document.createElement('ol');
165
- Classes.add(ol, CLASS_NAME_OPTGROUP);
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);
166
168
 
167
169
  const label = document.createElement('li');
168
- label.textContent = optGroup.label.trim();
169
- Classes.add(label, CLASS_NAME_OPTGROUP_TITLE);
170
- ol.appendChild(label);
171
-
172
- VGSelect._createListItems(Selectors.findAll('option', optGroup), ol, selector);
173
- fragment.appendChild(ol);
174
- });
175
- list.appendChild(fragment);
176
- } else {
177
- VGSelect._createListItems(selector.options, list, selector);
178
- }
179
-
180
- return list;
181
- }
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
+ }
182
190
 
183
191
  /**
184
192
  * Создаёт <li> элементы из списка <option>
@@ -187,13 +195,13 @@ class VGSelect extends BaseModule {
187
195
  * @param {HTMLSelectElement} selector - Исходный <select>
188
196
  * @private
189
197
  */
190
- static _createListItems(options, parent, selector) {
191
- const frag = document.createDocumentFragment();
192
- const selectedIndex = selector.selectedIndex;
193
- const hasExplicitSelected = VGSelect.hasExplicitSelectedOption(selector);
194
-
195
- [...options].forEach((option) => {
196
- if (option.hidden) return;
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;
197
205
 
198
206
  // value атрибута может не быть или он может быть пустым -> для маппинга используем индекс
199
207
  const rawValueAttr = option.getAttribute('value'); // null если атрибута нет
@@ -204,9 +212,16 @@ class VGSelect extends BaseModule {
204
212
  const li = document.createElement('li');
205
213
  li.textContent = text;
206
214
  li.dataset.index = String(option.index);
207
- li.dataset.value = value;
208
- li.classList.add(CLASS_NAME_OPTION);
209
- Manipulator.set(li, 'data-vg-toggle', 'select-option');
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
+ }
210
225
 
211
226
  // Раньше подсветка зависела от "явно выбранных" option (атрибут selected),
212
227
  // из-за этого UI мог показывать placeholder, но DOM считал, что выбрана 1-я опция.
@@ -234,8 +249,17 @@ class VGSelect extends BaseModule {
234
249
  frag.appendChild(li);
235
250
  });
236
251
 
237
- parent.appendChild(frag);
238
- }
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;
262
+ }
239
263
 
240
264
  /**
241
265
  * Проверяет, является ли значение "пустым" (соответствует placeholder)
@@ -315,11 +339,11 @@ class VGSelect extends BaseModule {
315
339
  tags.classList.add(CLASS_NAME_TAGS);
316
340
  current.appendChild(tags);
317
341
 
318
- const input = document.createElement('input');
319
- input.type = 'text';
320
- 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;';
322
- tags.appendChild(input);
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);
323
347
 
324
348
  input.addEventListener('focus', () => {
325
349
  const inst = VGSelect.getInstance(input.closest(`.${CLASS_NAME_CONTAINER}`));
@@ -344,7 +368,7 @@ class VGSelect extends BaseModule {
344
368
  dropdown.classList.add(CLASS_NAME_DROPDOWN);
345
369
  container.appendChild(dropdown);
346
370
 
347
- this.buildListOptions(selector, dropdown);
371
+ this.buildListOptions(selector, dropdown, params);
348
372
 
349
373
  selector.insertAdjacentElement('afterend', container);
350
374
  selector.dataset.inited = 'true';
@@ -547,7 +571,7 @@ class VGSelect extends BaseModule {
547
571
  }
548
572
 
549
573
  const drop = this._element.querySelector(SELECTOR_DROPDOWN);
550
- VGSelect.buildListOptions(select, drop);
574
+ VGSelect.buildListOptions(select, drop, this._params);
551
575
  VGSelect.updateUI(select);
552
576
  }
553
577
 
@@ -1107,12 +1131,13 @@ class VGSelect extends BaseModule {
1107
1131
  optionsData = data.results;
1108
1132
  }
1109
1133
 
1110
- if (!Array.isArray(optionsData)) {
1111
- instance?._triggerEvent(EVENT_KEY_ERROR, { error: 'Invalid data format: expected array' });
1112
- return;
1113
- }
1114
-
1115
- if (!preserve) {
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) {
1116
1141
  // Удаление только не помеченных как data-preserve
1117
1142
  [...select.querySelectorAll('option')].forEach(option => {
1118
1143
  const parentOptGroup = option.closest('optgroup');
@@ -1126,50 +1151,62 @@ class VGSelect extends BaseModule {
1126
1151
  if (og.children.length === 0 && !og.hasAttribute('data-preserve')) {
1127
1152
  og.remove();
1128
1153
  }
1129
- });
1130
- }
1131
-
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
- });
1149
-
1150
- optgroup.appendChild(option);
1151
- });
1152
-
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
- });
1165
-
1166
- select.appendChild(option);
1167
- }
1168
- });
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
+ }
1169
1206
 
1170
1207
  if (isRebuild) {
1171
1208
  const drop = container.querySelector(`.${CLASS_NAME_DROPDOWN}`);
1172
- VGSelect.buildListOptions(select, drop);
1209
+ VGSelect.buildListOptions(select, drop, instance?._params || {});
1173
1210
  instance?._triggerEvent(EVENT_KEY_REBUILD);
1174
1211
  } else {
1175
1212
  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.0",
4
4
  "description": "",
5
5
  "author": {
6
6
  "name": "Vegas Studio",