vgapp 1.5.5 → 1.5.7
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/vgalert/js/vgalert.js +7 -4
- package/app/modules/vgfiles/js/base.js +25 -30
- package/app/modules/vgmodal/js/vgmodal.js +9 -3
- package/app/modules/vgnav/js/vgnav.js +62 -58
- package/app/modules/vgrangeslider/js/skins.js +8 -3
- package/app/modules/vgrangeslider/js/vgrangeslider.js +29 -17
- package/app/modules/vgrollup/js/vgrollup.js +15 -7
- package/app/modules/vgselect/js/handlers.js +6 -53
- package/app/modules/vgselect/js/vgselect.js +107 -63
- package/app/modules/vgselect/scss/vgselect.scss +1 -1
- package/app/modules/vgsidebar/js/vgsidebar.js +21 -10
- package/app/modules/vgspy/js/vgspy.js +43 -22
- package/app/modules/vgtable/js/_options.js +16 -5
- package/app/modules/vgtable/js/_pagination.js +118 -24
- package/app/modules/vgtable/js/_responsive.js +79 -0
- package/app/modules/vgtable/js/vgtable.js +38 -11
- package/app/modules/vgtabs/js/vgtabs.js +91 -66
- package/app/modules/vgtoast/js/vgtoast.js +11 -5
- package/app/modules/vgtooltip/js/vgtooltip.js +19 -9
- package/app/utils/js/components/file-icon.js +7 -2
- package/app/utils/js/components/responsive.js +165 -0
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/build/vgapp.js +1 -1
- package/build/vgapp.js.map +1 -1
- package/index.js +7 -1
- package/package.json +1 -1
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Возможности: modal, overlay и dropdown-рендер, компактный размер, Data API, AJAX и Promise-сценарии.
|
|
4
4
|
*/
|
|
5
5
|
import BaseModule from "../../base-module";
|
|
6
|
-
import VGModal from "../../vgmodal";
|
|
6
|
+
import VGModal from "../../vgmodal";
|
|
7
|
+
import VGSidebar from "../../vgsidebar";
|
|
7
8
|
import VGDropdown from "../../vgdropdown";
|
|
8
9
|
|
|
9
10
|
import { execute, isElement, isVisible, makeRandomString, mergeDeepObject, reflow } from "../../../utils/js/functions";
|
|
@@ -450,8 +451,10 @@ class VGAlert {
|
|
|
450
451
|
};
|
|
451
452
|
}
|
|
452
453
|
|
|
453
|
-
const
|
|
454
|
-
|
|
454
|
+
const parent = containerWrap.classList.contains('vg-sidebar')
|
|
455
|
+
? VGSidebar.getOrCreateInstance(containerWrap)
|
|
456
|
+
: VGModal.getOrCreateInstance(containerWrap);
|
|
457
|
+
const container = Selectors.find('.vg-modal-content', containerWrap) || containerWrap;
|
|
455
458
|
|
|
456
459
|
const overlay = document.createElement('div');
|
|
457
460
|
|
|
@@ -463,7 +466,7 @@ class VGAlert {
|
|
|
463
466
|
|
|
464
467
|
return {
|
|
465
468
|
element: overlay,
|
|
466
|
-
render:
|
|
469
|
+
render: parent,
|
|
467
470
|
};
|
|
468
471
|
}
|
|
469
472
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Описание: базовая логика выбора, валидации и отображения файлов VGFiles.
|
|
3
|
-
* Возможности: управляет
|
|
3
|
+
* Возможности: управляет файлами, валидацией, списками, миниатюрами и иконками, действиями, статистикой и скрытыми полями.
|
|
4
4
|
*/
|
|
5
5
|
import BaseModule from "../../base-module";
|
|
6
6
|
import {mergeDeepObject} from "../../../utils/js/functions";
|
|
@@ -8,7 +8,8 @@ import Html from "../../../utils/js/components/templater";
|
|
|
8
8
|
import {lang_messages} from "../../../utils/js/components/lang";
|
|
9
9
|
import {Classes, Manipulator} from "../../../utils/js/dom/manipulator";
|
|
10
10
|
import Selectors from "../../../utils/js/dom/selectors";
|
|
11
|
-
import {getSVG} from "../../module-fn";
|
|
11
|
+
import {getSVG} from "../../module-fn";
|
|
12
|
+
import {resolveFileIconName} from "../../../utils/js/components/file-icon";
|
|
12
13
|
import VGFilePreview from "../../vgfilepreview";
|
|
13
14
|
import {extractAudioMetadata} from "../../../utils/js/components/audio-metadata";
|
|
14
15
|
|
|
@@ -813,34 +814,28 @@ class VGFilesBase extends BaseModule {
|
|
|
813
814
|
}
|
|
814
815
|
}
|
|
815
816
|
|
|
816
|
-
_renderUIImage(file) {
|
|
817
|
-
const $container = this._tpl.div({ class: 'file-image' });
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
return $container;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
const icon = this._getIconByFileType(file);
|
|
842
|
-
$container.appendChild(this._tpl.i({}, icon, { isHTML: true }));
|
|
843
|
-
return $container;
|
|
817
|
+
_renderUIImage(file) {
|
|
818
|
+
const $container = this._tpl.div({ class: 'file-image' });
|
|
819
|
+
const renderIcon = () => {
|
|
820
|
+
const icon = this._getIconByFileType(file);
|
|
821
|
+
$container.replaceChildren(this._tpl.i({}, icon, { isHTML: true }));
|
|
822
|
+
};
|
|
823
|
+
const customData = this._getFileCustomData(file);
|
|
824
|
+
let imageSrc = String(file?.image || '').trim() || String(customData.audioCover || '').trim();
|
|
825
|
+
|
|
826
|
+
if (!imageSrc && resolveFileIconName({ type: file?.type, name: file?.name || file?.src }) === 'file-image') {
|
|
827
|
+
imageSrc = String(file?.src || '').trim() || this._getFileObjectUrl(file);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (imageSrc) {
|
|
831
|
+
const image = this._tpl.img(imageSrc, this._resolveDisplayName(file), { class: 'file-preview' });
|
|
832
|
+
image.addEventListener('error', renderIcon, { once: true });
|
|
833
|
+
$container.appendChild(image);
|
|
834
|
+
} else {
|
|
835
|
+
renderIcon();
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return $container;
|
|
844
839
|
}
|
|
845
840
|
|
|
846
841
|
_resolveDisplayName(file) {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Описание: модальные окна VGModal с декларативным и программным управлением.
|
|
3
|
+
* Возможности: AJAX, backdrop, геометрия окна, сворачивание и синхронизация доступности.
|
|
4
|
+
*/
|
|
5
|
+
import BaseModule from "../../base-module";
|
|
2
6
|
import ScrollBarHelper from "../../../utils/js/components/scrollbar";
|
|
3
7
|
import Backdrop from "../../../utils/js/components/backdrop";
|
|
4
8
|
import Selectors from "../../../utils/js/dom/selectors";
|
|
@@ -346,7 +350,8 @@ class VGModal extends BaseModule {
|
|
|
346
350
|
this._saveInteractionState();
|
|
347
351
|
this._disableInteractionHandlers();
|
|
348
352
|
this._minimized.reset();
|
|
349
|
-
this._element.style.display = 'none';
|
|
353
|
+
this._element.style.display = 'none';
|
|
354
|
+
this._element.setAttribute('aria-hidden', 'true');
|
|
350
355
|
this._element.removeAttribute('aria-modal');
|
|
351
356
|
this._element.removeAttribute('role');
|
|
352
357
|
this._isTransitioning = false;
|
|
@@ -395,7 +400,8 @@ class VGModal extends BaseModule {
|
|
|
395
400
|
document.body.append(this._element);
|
|
396
401
|
}
|
|
397
402
|
|
|
398
|
-
this._element.style.display = 'block';
|
|
403
|
+
this._element.style.display = 'block';
|
|
404
|
+
this._element.removeAttribute('aria-hidden');
|
|
399
405
|
this._element.setAttribute('aria-modal', true);
|
|
400
406
|
this._element.setAttribute('role', 'dialog');
|
|
401
407
|
this._element.scrollTop = 0;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Описание: адаптивная навигация VGNav с выпадающими пунктами и гамбургером.
|
|
3
|
+
* Возможности: click/hover, вложенные меню, позиционирование, события и интеграция с VGSidebar.
|
|
4
|
+
*/
|
|
5
|
+
import BaseModule from "../../base-module";
|
|
2
6
|
import Selectors from "../../../utils/js/dom/selectors";
|
|
3
7
|
import {
|
|
4
8
|
execute,
|
|
@@ -29,7 +33,7 @@ const CLASS_NAME_ACTIVE = 'active';
|
|
|
29
33
|
/**
|
|
30
34
|
* Constants toggle
|
|
31
35
|
*/
|
|
32
|
-
const SELECTOR_DATA_TOGGLE = '.' + CLASS_NAME + ' a';
|
|
36
|
+
const SELECTOR_DATA_TOGGLE = '.' + CLASS_NAME + ' .dropdown > a';
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
39
|
* Constants Events
|
|
@@ -215,34 +219,43 @@ class VGNav extends BaseModule {
|
|
|
215
219
|
}
|
|
216
220
|
}
|
|
217
221
|
|
|
218
|
-
show(relatedTarget) {
|
|
219
|
-
let target = relatedTarget.relatedTarget;
|
|
220
|
-
|
|
221
|
-
if (!target || isDisabled(target)) return;
|
|
222
|
-
|
|
223
|
-
if (!
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
222
|
+
show(relatedTarget) {
|
|
223
|
+
let target = relatedTarget.relatedTarget;
|
|
224
|
+
|
|
225
|
+
if (!target || isDisabled(target)) return;
|
|
226
|
+
const drop = Selectors.find(':scope > .dropdown-content', target);
|
|
227
|
+
if (!drop) return;
|
|
228
|
+
|
|
229
|
+
const showEvent = EventHandler.trigger(target, EVENT_KEY_SHOW, { relatedTarget });
|
|
230
|
+
if (showEvent.defaultPrevented) return;
|
|
231
|
+
|
|
232
|
+
if (!target.closest('.dropdown-content')) {
|
|
233
|
+
target.classList.add('first');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const link = target.firstElementChild;
|
|
232
237
|
|
|
233
238
|
if (link) link.setAttribute('aria-expanded', 'true');
|
|
234
|
-
drop.classList.add(CLASS_NAME_SHOW);
|
|
235
|
-
target.classList.add(CLASS_NAME_ACTIVE);
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
239
|
+
drop.classList.add(CLASS_NAME_SHOW);
|
|
240
|
+
target.classList.add(CLASS_NAME_ACTIVE);
|
|
241
|
+
// Координаты Placement имеют приоритет над старой CSS-схемой top/bottom/right.
|
|
242
|
+
drop.style.bottom = 'auto';
|
|
243
|
+
drop.style.right = 'auto';
|
|
244
|
+
const openToSide = !!target.closest('.dropdown-content') || this._params.placement === 'vertical';
|
|
245
|
+
|
|
246
|
+
const $placement = new Placement({
|
|
247
|
+
reference: target,
|
|
248
|
+
drop: drop,
|
|
249
|
+
placement: openToSide ? 'right-start' : 'bottom-start',
|
|
250
|
+
fallbackPlacements: openToSide
|
|
251
|
+
? ['left-start', 'right-end', 'left-end', 'bottom-start', 'top-start']
|
|
252
|
+
: ['top-start', 'bottom-end', 'top-end'],
|
|
253
|
+
offset: openToSide ? [6, 0] : [0, 6],
|
|
254
|
+
boundary: 'clippingParents',
|
|
255
|
+
autoFlip: true,
|
|
256
|
+
overflowProtection: true,
|
|
257
|
+
clamp: true,
|
|
258
|
+
isMerge: false
|
|
246
259
|
});
|
|
247
260
|
|
|
248
261
|
$placement._setPlacement();
|
|
@@ -264,13 +277,8 @@ class VGNav extends BaseModule {
|
|
|
264
277
|
this._queueCallback(completeCallBack, drop, true, 10);
|
|
265
278
|
}
|
|
266
279
|
|
|
267
|
-
hide(relatedTarget) {
|
|
268
|
-
const _this = this;
|
|
269
|
-
if ('ontouchstart' in document.documentElement) {
|
|
270
|
-
for (const element of [].concat(...document.body.children)) {
|
|
271
|
-
EventHandler.off(element, 'mouseover', noop);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
280
|
+
hide(relatedTarget) {
|
|
281
|
+
const _this = this;
|
|
274
282
|
|
|
275
283
|
let element = relatedTarget.relatedTarget;
|
|
276
284
|
|
|
@@ -288,7 +296,7 @@ class VGNav extends BaseModule {
|
|
|
288
296
|
element.classList.remove('first');
|
|
289
297
|
}
|
|
290
298
|
|
|
291
|
-
[...Selectors.findAll('.' + CLASS_NAME_SHOW, element)].forEach(function (el
|
|
299
|
+
[...Selectors.findAll('.dropdown-content.' + CLASS_NAME_SHOW, element)].forEach(function (el) {
|
|
292
300
|
el.classList.remove(CLASS_NAME_FADE);
|
|
293
301
|
|
|
294
302
|
let parent = el.closest('.dropdown');
|
|
@@ -299,21 +307,14 @@ class VGNav extends BaseModule {
|
|
|
299
307
|
let link = el.previousElementSibling;
|
|
300
308
|
if (link) link.setAttribute('aria-expanded', 'false');
|
|
301
309
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const dropData = _this._openDrops.get(el);
|
|
312
|
-
if (dropData) {
|
|
313
|
-
window.removeEventListener('scroll', dropData.scrollHandler, { capture: true });
|
|
314
|
-
window.removeEventListener('resize', dropData.resizeHandler);
|
|
315
|
-
_this._openDrops.delete(el);
|
|
316
|
-
}
|
|
310
|
+
const completeCallback = () => {
|
|
311
|
+
if (parent.classList.contains(CLASS_NAME_ACTIVE)) return;
|
|
312
|
+
el.classList.remove(CLASS_NAME_SHOW);
|
|
313
|
+
EventHandler.trigger(el, EVENT_KEY_HIDDEN, relatedTarget);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
_this._queueCallback(completeCallback, el, true, 500);
|
|
317
|
+
_this._cleanupDrop(el);
|
|
317
318
|
});
|
|
318
319
|
}
|
|
319
320
|
}
|
|
@@ -341,13 +342,16 @@ class VGNav extends BaseModule {
|
|
|
341
342
|
}
|
|
342
343
|
}
|
|
343
344
|
|
|
344
|
-
_cleanupDrop(drop) {
|
|
345
|
-
const dropData = this._openDrops.get(drop);
|
|
346
|
-
if (dropData) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
this._openDrops.
|
|
350
|
-
|
|
345
|
+
_cleanupDrop(drop) {
|
|
346
|
+
const dropData = this._openDrops.get(drop);
|
|
347
|
+
if (dropData) {
|
|
348
|
+
this._openDrops.delete(drop);
|
|
349
|
+
// Обработчики общие для экземпляра: другие открытые уровни ещё нуждаются в них.
|
|
350
|
+
if (!this._openDrops.size) {
|
|
351
|
+
window.removeEventListener('scroll', dropData.scrollHandler, { capture: true });
|
|
352
|
+
window.removeEventListener('resize', dropData.resizeHandler);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
351
355
|
}
|
|
352
356
|
|
|
353
357
|
_isElementInViewport(el) {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Описание: визуальные режимы VGRangeSlider — default, ruler и status.
|
|
3
|
+
* Возможности: шкала с адаптивными координатами, активные отметки и пороговые цвета с текстовыми статусами.
|
|
4
|
+
*/
|
|
5
|
+
const SKIN_CLASS_DEFAULT = 'is-skin-default';
|
|
2
6
|
const SKIN_CLASS_RULER = 'is-skin-ruler';
|
|
3
7
|
const SKIN_CLASS_STATUS = 'is-skin-status';
|
|
4
8
|
const SKIN_CLASS_RULER_DIM = 'is-ruler-dim-inactive';
|
|
@@ -206,8 +210,9 @@ const syncRangeSliderSkin = (skin, state, helpers) => {
|
|
|
206
210
|
|
|
207
211
|
const isRange = state.from !== state.to || helpers.isRange;
|
|
208
212
|
|
|
209
|
-
skin.ticks.forEach((tick, index) => {
|
|
210
|
-
const value = skin.values[index];
|
|
213
|
+
skin.ticks.forEach((tick, index) => {
|
|
214
|
+
const value = skin.values[index];
|
|
215
|
+
tick.style.left = `${helpers.toPositionPx(value)}px`;
|
|
211
216
|
const isActive = isRange
|
|
212
217
|
? value >= state.from && value <= state.to
|
|
213
218
|
: value <= state.from;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Описание: одиночные и диапазонные слайдеры VGRangeSlider на нативных range-input.
|
|
3
|
+
* Возможности: Data API, шкала и статусы, форматирование, синхронизация формы, события и управление значениями.
|
|
4
|
+
*/
|
|
5
|
+
import BaseModule from "../../base-module";
|
|
2
6
|
import EventHandler from "../../../utils/js/dom/event";
|
|
3
7
|
import Selectors from "../../../utils/js/dom/selectors";
|
|
4
8
|
import { mergeDeepObject, normalizeData } from "../../../utils/js/functions";
|
|
@@ -259,9 +263,10 @@ class VGRangeSlider extends BaseModule {
|
|
|
259
263
|
this._dom.labelFrom = Selectors.find('.vg-range-slider__label--from', root);
|
|
260
264
|
this._dom.labelTo = Selectors.find('.vg-range-slider__label--to', root);
|
|
261
265
|
this._dom.labelSeparator = Selectors.find('.vg-range-slider__separator', root);
|
|
262
|
-
this._dom.hiddenMin
|
|
263
|
-
this._dom.hiddenMax
|
|
264
|
-
this._dom.skin = applyRangeSliderSkin(root, this._params, this._state, {
|
|
266
|
+
this._dom.hiddenMin ||= root.querySelector('input[data-vgrangeslider-hidden="min"]');
|
|
267
|
+
this._dom.hiddenMax ||= root.querySelector('input[data-vgrangeslider-hidden="max"]');
|
|
268
|
+
this._dom.skin = applyRangeSliderSkin(root, this._params, this._state, {
|
|
269
|
+
isRange: this._isRange,
|
|
265
270
|
formatValue: (value) => this._formatValue(value),
|
|
266
271
|
toPositionPx: (value) => this._toPositionPx(value),
|
|
267
272
|
});
|
|
@@ -338,11 +343,11 @@ class VGRangeSlider extends BaseModule {
|
|
|
338
343
|
root.appendChild(this._createLabels(this._isRange));
|
|
339
344
|
}
|
|
340
345
|
|
|
341
|
-
if (this._isRange) {
|
|
342
|
-
this._ensureHiddenInput(root, 'min', this._params.input?.min, this._params.name?.min);
|
|
343
|
-
this._ensureHiddenInput(root, 'max', this._params.input?.max, this._params.name?.max);
|
|
344
|
-
} else if (this._params.name?.min) {
|
|
345
|
-
this._ensureHiddenInput(root, 'min', this._params.input?.min, this._params.name.min);
|
|
346
|
+
if (this._isRange) {
|
|
347
|
+
this._dom.hiddenMin = this._ensureHiddenInput(root, 'min', this._params.input?.min, this._params.name?.min);
|
|
348
|
+
this._dom.hiddenMax = this._ensureHiddenInput(root, 'max', this._params.input?.max, this._params.name?.max);
|
|
349
|
+
} else if (this._params.name?.min) {
|
|
350
|
+
this._dom.hiddenMin = this._ensureHiddenInput(root, 'min', this._params.input?.min, this._params.name.min);
|
|
346
351
|
}
|
|
347
352
|
|
|
348
353
|
return root;
|
|
@@ -399,7 +404,8 @@ class VGRangeSlider extends BaseModule {
|
|
|
399
404
|
if (name) input.name = name;
|
|
400
405
|
root.appendChild(input);
|
|
401
406
|
}
|
|
402
|
-
input.setAttribute('data-vgrangeslider-hidden', role);
|
|
407
|
+
input.setAttribute('data-vgrangeslider-hidden', role);
|
|
408
|
+
return input;
|
|
403
409
|
}
|
|
404
410
|
|
|
405
411
|
_bindEvents() {
|
|
@@ -442,7 +448,8 @@ class VGRangeSlider extends BaseModule {
|
|
|
442
448
|
window.addEventListener('resize', this._handleResize);
|
|
443
449
|
}
|
|
444
450
|
|
|
445
|
-
_syncUI() {
|
|
451
|
+
_syncUI() {
|
|
452
|
+
if (!this._isRange) this._state.to = this._state.from;
|
|
446
453
|
const fromPercent = this._toPercent(this._state.from);
|
|
447
454
|
const toPercent = this._toPercent(this._isRange ? this._state.to : this._state.from);
|
|
448
455
|
const fromPosition = this._toPositionPx(this._state.from);
|
|
@@ -467,10 +474,11 @@ class VGRangeSlider extends BaseModule {
|
|
|
467
474
|
if (this._dom.labelSeparator) this._dom.labelSeparator.hidden = !this._isRange;
|
|
468
475
|
|
|
469
476
|
this._syncTargets();
|
|
470
|
-
syncRangeSliderSkin(this._dom.skin, this._state, {
|
|
471
|
-
isRange: this._isRange,
|
|
472
|
-
params: this._params,
|
|
473
|
-
dom: this._dom,
|
|
477
|
+
syncRangeSliderSkin(this._dom.skin, this._state, {
|
|
478
|
+
isRange: this._isRange,
|
|
479
|
+
params: this._params,
|
|
480
|
+
dom: this._dom,
|
|
481
|
+
toPositionPx: (value) => this._toPositionPx(value),
|
|
474
482
|
});
|
|
475
483
|
}
|
|
476
484
|
|
|
@@ -565,8 +573,12 @@ class VGRangeSlider extends BaseModule {
|
|
|
565
573
|
const numeric = Number(normalizeData(value));
|
|
566
574
|
const safeValue = Number.isFinite(numeric) ? numeric : fallback;
|
|
567
575
|
const clamped = Math.min(Math.max(safeValue, min), max);
|
|
568
|
-
const
|
|
569
|
-
const
|
|
576
|
+
const configuredStep = Number(this._state?.step ?? this._params.step);
|
|
577
|
+
const step = Number.isFinite(configuredStep) && configuredStep > 0 ? configuredStep : 1;
|
|
578
|
+
// Округление не должно выводить значение за max или с нативной сетки шага.
|
|
579
|
+
const maxSteps = Math.floor((max - min) / step + 1e-9);
|
|
580
|
+
const steps = Math.min(Math.round((clamped - min) / step), maxSteps);
|
|
581
|
+
const stepped = steps * step + min;
|
|
570
582
|
return Number(stepped.toFixed(5));
|
|
571
583
|
}
|
|
572
584
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Описание: сворачивание текста и списков VGRollup.
|
|
3
|
+
* Возможности: ограничение высоты, строк и количества элементов, локализация кнопок, Data API, callbacks и события.
|
|
4
|
+
*/
|
|
5
|
+
import BaseModule from "../../base-module";
|
|
2
6
|
import { execute, isDisabled, mergeDeepObject } from "../../../utils/js/functions";
|
|
3
7
|
import EventHandler from "../../../utils/js/dom/event";
|
|
4
8
|
import Selectors from "../../../utils/js/dom/selectors";
|
|
@@ -98,7 +102,7 @@ class VGRollup extends BaseModule {
|
|
|
98
102
|
}
|
|
99
103
|
};
|
|
100
104
|
|
|
101
|
-
|
|
105
|
+
const lang = Manipulator.get(element, 'data-lang') || params.lang || defaultParams.lang;
|
|
102
106
|
|
|
103
107
|
// Локализация текстов кнопок
|
|
104
108
|
defaultParams.button.more = lang_buttons(lang, NAME)['show'];
|
|
@@ -288,7 +292,8 @@ class VGRollup extends BaseModule {
|
|
|
288
292
|
|
|
289
293
|
if (isEllipsis && line) {
|
|
290
294
|
Classes.add(element, this.classes.ellipsis);
|
|
291
|
-
element.style.lineClamp = Number(line);
|
|
295
|
+
element.style.lineClamp = Number(line);
|
|
296
|
+
element.style.setProperty('-webkit-line-clamp', String(Number(line)));
|
|
292
297
|
} else if (isEllipsis) {
|
|
293
298
|
console.error("Переменная [data-line] или параметр[line] не должны быть пустыми");
|
|
294
299
|
}
|
|
@@ -377,13 +382,14 @@ class VGRollup extends BaseModule {
|
|
|
377
382
|
|
|
378
383
|
if (this._params.ellipsis.line) {
|
|
379
384
|
Classes.add(el, this.classes.ellipsis);
|
|
380
|
-
el.style.lineClamp = this._params.ellipsis.line;
|
|
385
|
+
el.style.lineClamp = this._params.ellipsis.line;
|
|
386
|
+
el.style.setProperty('-webkit-line-clamp', String(this._params.ellipsis.line));
|
|
381
387
|
}
|
|
382
388
|
|
|
383
389
|
if (this._params.fade) Classes.add(el, this.classes.fade);
|
|
384
390
|
if (this._params.transition) Classes.add(el, this.classes.transition);
|
|
385
391
|
|
|
386
|
-
execute(this._params.callbacks.
|
|
392
|
+
execute(this._params.callbacks.collapse, [el, this])
|
|
387
393
|
} else if (content === 'elements') {
|
|
388
394
|
const items = Selectors.findAll('.' + this._params.elements, el);
|
|
389
395
|
items.forEach((item, index) => {
|
|
@@ -399,7 +405,9 @@ class VGRollup extends BaseModule {
|
|
|
399
405
|
} else {
|
|
400
406
|
const { hidden, ellipsis, fade } = this.classes;
|
|
401
407
|
Classes.remove(el, [hidden, ellipsis, fade]);
|
|
402
|
-
|
|
408
|
+
el.style.removeProperty('height');
|
|
409
|
+
el.style.removeProperty('line-clamp');
|
|
410
|
+
el.style.removeProperty('-webkit-line-clamp');
|
|
403
411
|
|
|
404
412
|
if (this._params.content === 'elements') {
|
|
405
413
|
const items = Selectors.findAll('.' + this._params.elements, el);
|
|
@@ -455,4 +463,4 @@ EventHandler.on(document, EVENT_KEY_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, functi
|
|
|
455
463
|
VGRollup.toggle(target, this);
|
|
456
464
|
});
|
|
457
465
|
|
|
458
|
-
export default VGRollup;
|
|
466
|
+
export default VGRollup;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Описание: делегированные обработчики интерфейса VGSelect.
|
|
3
|
+
* Возможности: открытие списка, выбор опций, теги, клавиатура и сброс формы.
|
|
4
|
+
*/
|
|
5
|
+
import VGSelect from "./vgselect";
|
|
2
6
|
import EventHandler from "../../../utils/js/dom/event";
|
|
3
7
|
import Selectors from "../../../utils/js/dom/selectors";
|
|
4
8
|
import {Manipulator} from "../../../utils/js/dom/manipulator";
|
|
5
|
-
import {transliterate} from "../../../utils/js/functions";
|
|
6
9
|
|
|
7
10
|
const NAME_KEY = 'vg.select';
|
|
8
11
|
|
|
@@ -100,56 +103,6 @@ const _handlersVGSelect = () => {
|
|
|
100
103
|
}
|
|
101
104
|
});
|
|
102
105
|
|
|
103
|
-
EventHandler.on(document, EVENT_KEY_UP_DATA_API, SELECTOR_SEARCH_TOGGLE, function(e) {
|
|
104
|
-
const input = e.target;
|
|
105
|
-
const dropdown = input.closest(SELECTOR_DROPDOWN);
|
|
106
|
-
const list = dropdown?.querySelector(`.${CLASS_NAME_LIST}`);
|
|
107
|
-
if (!list) return;
|
|
108
|
-
|
|
109
|
-
const container = input.closest(`.${CLASS_NAME_CONTAINER}`);
|
|
110
|
-
const instance = VGSelect.getInstance(container);
|
|
111
|
-
|
|
112
|
-
const options = list.querySelectorAll(`.${CLASS_NAME_OPTION}`);
|
|
113
|
-
const groups = list.querySelectorAll(`.${CLASS_NAME_OPTGROUP}`);
|
|
114
|
-
const value = input.value.trim().toLowerCase();
|
|
115
|
-
const search = [value, transliterate(value), transliterate(value, true)];
|
|
116
|
-
|
|
117
|
-
options.forEach(el => { el.hidden = false; el.style.display = ''; });
|
|
118
|
-
groups.forEach(el => { el.hidden = false; el.style.display = ''; });
|
|
119
|
-
|
|
120
|
-
if (value) {
|
|
121
|
-
let visibleCount = 0;
|
|
122
|
-
groups.length ? groups.forEach(group => {
|
|
123
|
-
const items = group.querySelectorAll(`.${CLASS_NAME_OPTION}`);
|
|
124
|
-
const visible = Array.from(items).some(item => {
|
|
125
|
-
const t = item.textContent.toLowerCase();
|
|
126
|
-
return search.some(s => t.includes(s));
|
|
127
|
-
});
|
|
128
|
-
group.hidden = !visible;
|
|
129
|
-
group.style.display = visible ? '' : 'none';
|
|
130
|
-
items.forEach(item => {
|
|
131
|
-
const t = item.textContent.toLowerCase();
|
|
132
|
-
const match = search.some(s => t.includes(s));
|
|
133
|
-
item.hidden = !match;
|
|
134
|
-
item.style.display = match ? '' : 'none';
|
|
135
|
-
if (match) visibleCount++;
|
|
136
|
-
});
|
|
137
|
-
}) : options.forEach(option => {
|
|
138
|
-
const t = option.textContent.toLowerCase();
|
|
139
|
-
const match = search.some(s => t.includes(s));
|
|
140
|
-
option.hidden = !match;
|
|
141
|
-
option.style.display = match ? '' : 'none';
|
|
142
|
-
if (match) visibleCount++;
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
instance?._triggerEvent(EVENT_KEY_SEARCH, { query: value, results: visibleCount });
|
|
146
|
-
instance?._callCallback('onSearch', { query: value, results: visibleCount });
|
|
147
|
-
} else {
|
|
148
|
-
instance?._triggerEvent(EVENT_KEY_SEARCH, { query: '', results: options.length });
|
|
149
|
-
instance?._callCallback('onSearch', { query: '', results: options.length });
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
106
|
EventHandler.on(document, EVENT_RESET_DATA_API, 'form', function() {
|
|
154
107
|
Selectors.findAll('select[data-inited="true"]', this).forEach(select => VGSelect.build(select, true));
|
|
155
108
|
});
|
|
@@ -236,4 +189,4 @@ const _handlersVGSelect = () => {
|
|
|
236
189
|
});
|
|
237
190
|
}
|
|
238
191
|
|
|
239
|
-
export default _handlersVGSelect;
|
|
192
|
+
export default _handlersVGSelect;
|