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.
- package/app/modules/vgselect/js/vgselect.js +132 -95
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
193
|
-
const
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
if (item.
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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);
|