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
|
-
|
|
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
|
|
193
|
-
const
|
|
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
|
-
|
|
299
|
-
|
|
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:
|
|
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
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1154
|
-
|
|
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
|
-
|
|
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);
|