Your IP : 216.73.216.86


Current Path : /var/www/homesaver/www/bitrix/components/bitrix/main.ui.filter/templates/.default/src/
Upload File :
Current File : /var/www/homesaver/www/bitrix/components/bitrix/main.ui.filter/templates/.default/src/fields.js

/* eslint-disable no-underscore-dangle */
/* eslint-disable class-methods-use-this */
import {Dom, Event, Runtime, Type, Text, Loc, Tag} from 'main.core';
import createDateInputDecl from './fields/create-date-input-decl';
import createNumberInputDecl from './fields/create-number-input-decl';
import createLineDecl from './fields/create-line-decl';
import createSelectDecl from './fields/create-select-decl';
import {Field} from './field/field';
import {AdditionalFilter} from './additional-filter';

const errorMessages = new WeakMap();
const errorMessagesTypes = new WeakMap();
const values = new WeakMap();

export class Fields
{
	constructor(parent)
	{
		this.parent = null;
		this.init(parent);
	}

	init(parent)
	{
		this.parent = parent;
		BX.addCustomEvent(window, 'UI::Select::change', this._onDateTypeChange.bind(this));
	}

	deleteField(node)
	{
		Dom.remove(node);
	}

	isFieldDelete(node)
	{
		return Dom.hasClass(node, this.parent.settings.classFieldDelete);
	}

	isFieldValueDelete(node)
	{
		return (
			Dom.hasClass(node, this.parent.settings.classValueDelete)
			|| Dom.hasClass(node.parentNode, this.parent.settings.classValueDelete)
		);
	}

	isDragButton(node)
	{
		return node && Dom.hasClass(node, this.parent.settings.classPresetDragButton);
	}

	/**
	 * Clears values of filter field node
	 * @param {HTMLElement} field
	 */
	clearFieldValue(field)
	{
		if (field)
		{
			const controls = [...field.querySelectorAll('.main-ui-control')];
			const squares = [...field.querySelectorAll('.main-ui-square')];

			squares.forEach((square) => Dom.remove(square));
			controls.forEach((control) => {
				if (Reflect.has(control, 'value'))
				{
					control.value = '';
				}
			});
		}
	}

	getField(node)
	{
		if (Type.isDomNode(node))
		{
			return node.closest('.main-ui-control-field, .main-ui-control-field-group');
		}

		return null;
	}

	render(template, data)
	{
		if (Type.isString(template) && Type.isPlainObject(data))
		{
			const html = Object.entries(data).reduce((acc, [key, value]) => {
				return acc.replace(new RegExp(`{{${key}}}`, 'g'), value);
			}, template);

			const wrapped = Dom.create('div', {html});

			const fieldGroup = wrapped.querySelector('.main-ui-control-field-group');
			if (fieldGroup)
			{
				return fieldGroup;
			}

			const field = wrapped.querySelector('.main-ui-control-field');
			if (field)
			{
				return field;
			}

			const fieldLine = wrapped.querySelector('.main-ui-filter-field-line');
			if (fieldLine)
			{
				return fieldLine;
			}
		}

		return null;
	}

	createInputText(fieldData)
	{
		const field = {
			block: 'main-ui-control-field',
			mix: this.parent.getParam('ENABLE_LABEL') ? [this.parent.settings.classFieldWithLabel] : null,
			deleteButton: true,
			valueDelete: true,
			name: fieldData.NAME,
			type: fieldData.TYPE,
			label: this.parent.getParam('ENABLE_LABEL') ? fieldData.LABEL : '',
			icon: (this.parent.getParam('ENABLE_LABEL') && fieldData.ICON) ? fieldData.ICON : null,
			dragTitle: this.parent.getParam('MAIN_UI_FILTER__DRAG_FIELD_TITLE'),
			deleteTitle: this.parent.getParam('MAIN_UI_FILTER__REMOVE_FIELD'),
			content: [
				{
					block: 'main-ui-control-string',
					name: fieldData.NAME,
					placeholder: fieldData.PLACEHOLDER || '',
					value: (Type.isString(fieldData.VALUE)
							|| Type.isNumber(fieldData.VALUE) ? fieldData.VALUE : ''),
					tabindex: fieldData.TABINDEX,
				},
			],
		};

		const renderedField = BX.decl(field);

		this.parent.getEmitter().emit(
			'init',
			{
				field: new Field({
					parent: this.parent,
					options: {...fieldData},
					node: renderedField,
				}),
			},
		);

		return renderedField;
	}

	createTextarea(fieldData)
	{
		const field = BX.decl({
			block: 'main-ui-control-field',
			mix: this.parent.getParam('ENABLE_LABEL') ? [this.parent.settings.classFieldWithLabel] : null,
			deleteButton: true,
			valueDelete: true,
			name: fieldData.NAME,
			type: fieldData.TYPE,
			label: this.parent.getParam('ENABLE_LABEL') ? fieldData.LABEL : '',
			icon: (this.parent.getParam('ENABLE_LABEL') && fieldData.ICON) ? fieldData.ICON : null,
			dragTitle: this.parent.getParam('MAIN_UI_FILTER__DRAG_FIELD_TITLE'),
			deleteTitle: this.parent.getParam('MAIN_UI_FILTER__REMOVE_FIELD'),
			content: [
				{
					block: 'main-ui-control-textarea',
					name: fieldData.NAME,
					placeholder: fieldData.PLACEHOLDER || '',
					value: (Type.isString(fieldData.VALUE)
					|| Type.isNumber(fieldData.VALUE) ? fieldData.VALUE : ''),
					tabindex: fieldData.TABINDEX,
				},
			],
		});

		const textarea = field.querySelector('textarea');
		const onChange = () => {
			Dom.style(textarea, 'height', '1px');
			Dom.style(textarea, 'height', `${textarea.scrollHeight}px`);
		};

		Event.bind(textarea, 'input', onChange);
		Event.bind(textarea, 'change', onChange);
		Event.bind(textarea, 'keyup', onChange);
		Event.bind(textarea, 'cut', onChange);
		Event.bind(textarea, 'paste', onChange);

		this.parent.getEmitter().emit(
			'init',
			{
				field: new Field({
					parent: this.parent,
					options: {...fieldData},
					node: field,
				}),
			},
		);

		return field;
	}

	createCustomEntityFieldLayout(fieldData)
	{
		let field = {
			block: 'main-ui-control-field',
			mix: this.parent.getParam('ENABLE_LABEL') ? [this.parent.settings.classFieldWithLabel] : null,
			deleteButton: true,
			valueDelete: true,
			name: fieldData.NAME,
			type: fieldData.TYPE,
			label: this.parent.getParam('ENABLE_LABEL') ? fieldData.LABEL : '',
			icon: (this.parent.getParam('ENABLE_LABEL') && fieldData.ICON) ? fieldData.ICON : null,
			dragTitle: this.parent.getParam('MAIN_UI_FILTER__DRAG_FIELD_TITLE'),
			deleteTitle: this.parent.getParam('MAIN_UI_FILTER__REMOVE_FIELD'),
			content: {
				block: 'main-ui-control-entity',
				mix: 'main-ui-control',
				attrs: {
					'data-multiple': JSON.stringify(fieldData.MULTIPLE),
				},
				content: [],
			},
		};

		if ('_label' in fieldData.VALUES && !!fieldData.VALUES._label)
		{
			if (fieldData.MULTIPLE)
			{
				let label = fieldData.VALUES._label ? fieldData.VALUES._label : [];

				if (Type.isPlainObject(label))
				{
					label = Object.keys(label).map((key) => {
						return label[key];
					});
				}

				if (!Type.isArray(label))
				{
					label = [label];
				}

				let value = fieldData.VALUES._value ? fieldData.VALUES._value : [];
				if (Type.isPlainObject(value))
				{
					value = Object.keys(value).map((key) => {
						return value[key];
					});
				}

				if (!Type.isArray(value))
				{
					value = [value];
				}

				label.forEach((currentLabel, index) => {
					field.content.content.push({
						block: 'main-ui-square',
						tag: 'span',
						name: currentLabel,
						item: {_label: currentLabel, _value: value[index]},
					});
				});
			}
			else
			{
				field.content.content.push({
					block: 'main-ui-square',
					tag: 'span',
					name: '_label' in fieldData.VALUES ? fieldData.VALUES._label : '',
					item: fieldData.VALUES,
				});
			}
		}

		field.content.content.push(
			{
				block: 'main-ui-square-search',
				tag: 'span',
				content: {
					block: 'main-ui-control-string',
					name: `${fieldData.NAME}_label`,
					tabindex: fieldData.TABINDEX,
					type: 'text',
					placeholder: fieldData.PLACEHOLDER || '',
				},
			},
			{
				block: 'main-ui-control-string',
				name: fieldData.NAME,
				type: 'hidden',
				placeholder: fieldData.PLACEHOLDER || '',
				value: '_value' in fieldData.VALUES ? fieldData.VALUES._value : '',
				tabindex: fieldData.TABINDEX,
			},
		);

		field = BX.decl(field);

		const input = BX.Filter.Utils.getBySelector(field, '.main-ui-control-string[type="text"]');
		BX.addClass(input, 'main-ui-square-search-item');
		input.autocomplete = 'off';

		Event.bind(input, 'focus', BX.proxy(this._onCustomEntityInputFocus, this));
		Event.bind(input, 'click', BX.proxy(this._onCustomEntityInputClick, this));

		if (!this.bindDocument)
		{
			Event.bind(document, 'click', BX.proxy(this._onCustomEntityBlur, this));
			document.addEventListener('focus', BX.proxy(this._onDocumentFocus, this), true);
			this.bindDocument = true;
		}

		Event.bind(input, 'keydown', BX.proxy(this._onCustomEntityKeydown, this));
		Event.bind(field, 'click', BX.proxy(this._onCustomEntityFieldClick, this));

		return field;
	}

	createDestSelector(fieldData)
	{
		const field = this.createCustomEntityFieldLayout(fieldData);

		BX.ready(BX.proxy(function() {
			BX.Filter.DestinationSelector.create(
				fieldData.NAME,
				{
					filterId: this.parent.getParam('FILTER_ID'),
					fieldId: fieldData.NAME,
				},
			);
		}, this));

		this.parent.getEmitter().emit(
			'init',
			{
				field: new Field({
					parent: this.parent,
					options: {...fieldData},
					node: field,
				}),
			},
		);

		return field;
	}

	createEntitySelector(fieldData)
	{
		const field = this.createCustomEntityFieldLayout(fieldData);

		BX.Filter.EntitySelector.create(
			fieldData.NAME,
			{
				filter: this.parent,
				isMultiple: fieldData.MULTIPLE,
				addEntityIdToResult: fieldData.ADD_ENTITY_ID_TO_RESULT,
				showDialogOnEmptyInput: fieldData.SHOW_DIALOG_ON_EMPTY_INPUT,
				dialogOptions: fieldData.DIALOG_OPTIONS
			},
		);

		this.parent.getEmitter().emit(
			'init',
			{
				field: new Field({
					parent: this.parent,
					options: {...fieldData},
					node: field,
				}),
			},
		);

		return field;
	}

	createCustomEntity(fieldData)
	{
		const field = this.createCustomEntityFieldLayout(fieldData);

		this.parent.getEmitter().emit(
			'init',
			{
				field: new Field({
					parent: this.parent,
					options: {...fieldData},
					node: field,
				}),
			},
		);

		return field;
	}

	_onCustomEntityInputFocus(event)
	{
		BX.fireEvent(event.currentTarget, 'click');
	}

	_onCustomEntityInputClick(event)
	{
		event.preventDefault();
		event.stopPropagation();

		if (event.isTrusted)
		{
			this.trustTimestamp = event.timeStamp;
			this.notTrustTimestamp = this.notTrustTimestamp || event.timeStamp;
		}
		else
		{
			this.notTrustTimestamp = event.timeStamp;
		}

		const trustDate = new Date(this.trustTimestamp);
		const notTrustDate = new Date(this.notTrustTimestamp);
		const trustTime = `${trustDate.getMinutes()}:${trustDate.getSeconds()}`;
		const notTrustTime = `${notTrustDate.getMinutes()}:${notTrustDate.getSeconds()}`;

		if (trustTime !== notTrustTime)
		{
			this._onCustomEntityFocus(event);
		}
	}

	_onDocumentFocus(event)
	{
		const CustomEntity = this.getCustomEntityInstance();
		const popupContainer = CustomEntity.getPopupContainer();
		const isOnInputField = CustomEntity.getLabelNode() === event.target;
		const isInsidePopup = !!popupContainer && popupContainer.contains(event.target);

		if (!isOnInputField && !isInsidePopup)
		{
			this._onCustomEntityBlur(event);
		}
	}

	_onCustomEntityKeydown(event)
	{
		const {target, currentTarget} = event;
		const {parentNode} = target.parentNode;

		const squares = parentNode.querySelectorAll('.main-ui-square');
		const square = squares[squares.length - 1];

		if (!Type.isDomNode(square))
		{
			return;
		}

		if (
			BX.Filter.Utils.isKey(event, 'backspace')
			&& currentTarget.selectionStart === 0
		)
		{
			if (Dom.hasClass(square, 'main-ui-square-selected'))
			{
				const input = parentNode.querySelector('input[type="hidden"]');

				if (Type.isDomNode(input))
				{
					input.value = '';
					BX.fireEvent(input, 'input');
				}

				Dom.remove(square);
				return;
			}

			Dom.addClass(square, 'main-ui-square-selected');
			return;
		}

		Dom.removeClass(square, 'main-ui-square-selected');
	}

	_onCustomEntityFieldClick({target})
	{
		if (Dom.hasClass(target, 'main-ui-square-delete'))
		{
			const square = target.closest('.main-ui-square');

			if (Type.isDomNode(square))
			{
				const CustomEntity = this.getCustomEntityInstance();
				BX.onCustomEvent(window, 'BX.Main.Filter:customEntityRemove', [CustomEntity]);
				Dom.remove(square);
			}

			return;
		}

		const input = target.querySelector('input[type="text"]');

		if (Type.isDomNode(input))
		{
			BX.fireEvent(input, 'focus');
		}
	}

	_onCustomEntityBlur(event)
	{
		const eventData = {
			stopBlur: false,
		};

		BX.onCustomEvent(window, 'BX.Main.Filter:onGetStopBlur', [event, eventData]);

		if (
			typeof eventData.stopBlur === 'undefined'
			|| !eventData.stopBlur
		)
		{
			const CustomEntity = this.getCustomEntityInstance();
			BX.onCustomEvent(window, 'BX.Main.Filter:customEntityBlur', [CustomEntity]);

			Event.unbind(CustomEntity.getPopupContainer(), 'click', this._stopPropagation);
			Dom.removeClass(CustomEntity.getField(), 'main-ui-focus');
		}
	}

	_stopPropagation(event)
	{
		event.stopPropagation();
	}

	getCustomEntityInstance()
	{
		if (!(this.customEntityInstance instanceof BX.Main.ui.CustomEntity))
		{
			this.customEntityInstance = new BX.Main.ui.CustomEntity();
		}

		return this.customEntityInstance;
	}

	_onCustomEntityFocus(event)
	{
		event.stopPropagation();

		const {currentTarget} = event;
		const field = currentTarget.closest('.main-ui-control-entity');

		const CustomEntity = this.getCustomEntityInstance();
		CustomEntity.setField(field);
		BX.onCustomEvent('BX.Main.Filter:customEntityFocus', [CustomEntity]);

		const popupContainer = CustomEntity.getPopupContainer();
		if (Type.isElementNode(popupContainer))
		{
			Event.bind(popupContainer, 'click', this._stopPropagation);
		}

		Dom.addClass(field, 'main-ui-focus');
	}

	createCustom(fieldData)
	{
		const field = BX.decl({
			block: 'main-ui-control-field',
			mix: this.parent.getParam('ENABLE_LABEL') ? [this.parent.settings.classFieldWithLabel] : null,
			name: fieldData.NAME,
			type: fieldData.TYPE,
			deleteButton: true,
			label: this.parent.getParam('ENABLE_LABEL') ? fieldData.LABEL : '',
			icon: (this.parent.getParam('ENABLE_LABEL') && fieldData.ICON) ? fieldData.ICON : null,
			dragTitle: this.parent.getParam('MAIN_UI_FILTER__DRAG_FIELD_TITLE'),
			deleteTitle: this.parent.getParam('MAIN_UI_FILTER__REMOVE_FIELD'),
			content: {
				block: 'main-ui-custom',
				mix: [
					'main-ui-control',
					'main-ui-custom-style',
				],
				attrs: {
					'data-name': fieldData.NAME,
				},
				content: '',
			},
		});

		if (Type.isString(fieldData.VALUE))
		{
			const fieldValue = (() => {
				if (Reflect.has(fieldData, '_VALUE'))
				{
					return fieldData._VALUE;
				}

				return '';
			})();

			const html = Text
				.decode(fieldData.VALUE)
				.replace(
					`name="${fieldData.NAME}"`,
					`name="${fieldData.NAME}" value="${fieldValue}"`,
				);

			const control = field.querySelector('.main-ui-custom');
			Runtime.html(control, html);
		}

		this.parent.getEmitter().emit(
			'init',
			{
				field: new Field({
					parent: this.parent,
					options: {...fieldData},
					node: field,
				}),
			},
		);

		return field;
	}

	createSelect(fieldData)
	{
		const field = BX.decl({
			block: 'main-ui-control-field',
			mix: this.parent.getParam('ENABLE_LABEL') ? [this.parent.settings.classFieldWithLabel] : null,
			name: fieldData.NAME,
			type: fieldData.TYPE,
			deleteButton: true,
			label: this.parent.getParam('ENABLE_LABEL') ? fieldData.LABEL : '',
			icon: (this.parent.getParam('ENABLE_LABEL') && fieldData.ICON) ? fieldData.ICON : null,
			dragTitle: this.parent.getParam('MAIN_UI_FILTER__DRAG_FIELD_TITLE'),
			deleteTitle: this.parent.getParam('MAIN_UI_FILTER__REMOVE_FIELD'),
			content: {
				block: this.parent.settings.classSelect,
				name: fieldData.NAME,
				items: fieldData.ITEMS,
				value: 'VALUE' in fieldData ? fieldData.VALUE : fieldData.ITEMS[0],
				params: fieldData.PARAMS,
				tabindex: fieldData.TABINDEX,
				valueDelete: false,
			},
		});

		this.parent.getEmitter().emit(
			'init',
			{
				field: new Field({
					parent: this.parent,
					options: {...fieldData},
					node: field,
				}),
			},
		);

		return field;
	}

	createMultiSelect(fieldData)
	{
		const field = BX.decl({
			block: 'main-ui-control-field',
			mix: this.parent.getParam('ENABLE_LABEL') ? [this.parent.settings.classFieldWithLabel] : null,
			name: fieldData.NAME,
			type: fieldData.TYPE,
			deleteButton: true,
			label: this.parent.getParam('ENABLE_LABEL') ? fieldData.LABEL : '',
			icon: (this.parent.getParam('ENABLE_LABEL') && fieldData.ICON) ? fieldData.ICON : null,
			dragTitle: this.parent.getParam('MAIN_UI_FILTER__DRAG_FIELD_TITLE'),
			deleteTitle: this.parent.getParam('MAIN_UI_FILTER__REMOVE_FIELD'),
			content: {
				block: 'main-ui-multi-select',
				name: fieldData.NAME,
				tabindex: 'TABINDEX' in fieldData ? fieldData.TABINDEX : '',
				placeholder: !this.parent.getParam('ENABLE_LABEL') && 'PLACEHOLDER' in fieldData ? fieldData.PLACEHOLDER : '',
				items: 'ITEMS' in fieldData ? fieldData.ITEMS : [],
				value: 'VALUE' in fieldData ? fieldData.VALUE : [],
				params: 'PARAMS' in fieldData ? fieldData.PARAMS : {isMulti: true},
				valueDelete: true,
			},
		});

		this.parent.getEmitter().emit(
			'init',
			{
				field: new Field({
					parent: this.parent,
					options: {...fieldData},
					node: field,
				}),
			},
		);

		return field;
	}


	createCustomDate(fieldData)
	{
		const group = {
			block: 'main-ui-control-field-group',
			type: fieldData.TYPE,
			mix: this.parent.getParam('ENABLE_LABEL') ? [this.parent.settings.classFieldWithLabel, 'main-ui-filter-date-group'] : ['main-ui-filter-date-group'],
			label: this.parent.getParam('ENABLE_LABEL') ? fieldData.LABEL : '',
			icon: (this.parent.getParam('ENABLE_LABEL') && fieldData.ICON) ? fieldData.ICON : null,
			dragTitle: this.parent.getParam('MAIN_UI_FILTER__DRAG_FIELD_TITLE'),
			deleteTitle: this.parent.getParam('MAIN_UI_FILTER__REMOVE_FIELD'),
			tabindex: 'TABINDEX' in fieldData ? fieldData.TABINDEX : '',
			name: 'NAME' in fieldData ? fieldData.NAME : '',
			deleteButton: true,
			content: [],
		};

		if (Type.isPlainObject(fieldData.VALUE.days))
		{
			fieldData.VALUE.days = Object.keys(fieldData.VALUE.days).map((index) => {
				return fieldData.VALUE.days[index];
			});
		}

		const daysValue = fieldData.DAYS.filter((item) => {
			return fieldData.VALUE.days.some((value) => {
				return value === item.VALUE;
			});
		});

		const days = {
			block: 'main-ui-control-field',
			mix: ['main-ui-control-custom-date'],
			placeholder: fieldData.DAYS_PLACEHOLDER,
			dragButton: false,
			content: {
				block: 'main-ui-multi-select',
				name: `${fieldData.NAME}_days`,
				tabindex: 'TABINDEX' in fieldData ? fieldData.TABINDEX : '',
				items: fieldData.DAYS,
				value: daysValue,
				params: 'PARAMS' in fieldData ? fieldData.PARAMS : {isMulti: true},
				valueDelete: true,
				attrs: {'data-placeholder': fieldData.DAYS_PLACEHOLDER},
			},
		};


		if (Type.isPlainObject(fieldData.VALUE.months))
		{
			fieldData.VALUE.months = Object.keys(fieldData.VALUE.months).map((index) => {
				return fieldData.VALUE.months[index];
			});
		}

		const monthsValue = fieldData.MONTHS.filter((item) => {
			return fieldData.VALUE.months.some((value) => {
				return value === item.VALUE;
			});
		});

		const months = {
			block: 'main-ui-control-field',
			mix: ['main-ui-control-custom-date'],
			dragButton: false,
			content: {
				block: 'main-ui-multi-select',
				name: `${fieldData.NAME}_months`,
				tabindex: 'TABINDEX' in fieldData ? fieldData.TABINDEX : '',
				items: fieldData.MONTHS,
				value: monthsValue,
				params: 'PARAMS' in fieldData ? fieldData.PARAMS : {isMulti: true},
				valueDelete: true,
				attrs: {'data-placeholder': fieldData.MONTHS_PLACEHOLDER},
			},
		};


		if (Type.isPlainObject(fieldData.VALUE.years))
		{
			fieldData.VALUE.years = Object.keys(fieldData.VALUE.years).map((index) => {
				return fieldData.VALUE.years[index];
			});
		}

		const yearsValue = fieldData.YEARS.filter((item) => {
			return fieldData.VALUE.years.some((value) => {
				return value === item.VALUE;
			});
		});

		const years = {
			block: 'main-ui-control-field',
			mix: ['main-ui-control-custom-date'],
			dragButton: false,
			content: {
				block: 'main-ui-multi-select',
				name: `${fieldData.NAME}_years`,
				tabindex: 'TABINDEX' in fieldData ? fieldData.TABINDEX : '',
				items: fieldData.YEARS,
				value: yearsValue,
				params: 'PARAMS' in fieldData ? fieldData.PARAMS : {isMulti: true},
				valueDelete: true,
				attrs: {'data-placeholder': fieldData.YEARS_PLACEHOLDER},
			},
		};

		group.content.push(days);
		group.content.push(months);
		group.content.push(years);

		const field = BX.decl(group);

		this.parent.getEmitter().emit(
			'init',
			{
				field: new Field({
					parent: this.parent,
					options: {...fieldData},
					node: field,
				}),
			},
		);

		return field;
	}


	_onDateTypeChange(instance, data)
	{
		if (this.parent.getPopup().contentContainer.contains(instance.node))
		{
			const fieldData = {};
			let dateGroup = null;
			let label;
			let controls;
			let index;

			if (Type.isPlainObject(data) && Reflect.has(data, 'VALUE'))
			{
				const fieldNode = instance.getNode();
				const params = instance.getParams();
				const {name} = fieldNode.dataset;

				if (
					!Type.isPlainObject(params)
					&& (name.endsWith('_datesel') || name.endsWith('_numsel')))
				{
					const group = fieldNode.parentNode.parentNode;
					fieldData.TABINDEX = instance.getInput().getAttribute('tabindex');
					fieldData.SUB_TYPES = instance.getItems();
					fieldData.SUB_TYPE = data;
					fieldData.NAME = group.dataset.name;
					fieldData.TYPE = group.dataset.type;
					fieldData.VALUE_REQUIRED = group.dataset.valueRequired === 'true';

					const presetData = this.parent.getPreset().getCurrentPresetData();

					if (Type.isArray(presetData.FIELDS))
					{
						let presetField = presetData.FIELDS.find((current) => {
							return current.NAME === fieldData.NAME;
						});

						if (Type.isNil(presetField))
						{
							presetField = this.parent.params.FIELDS_STUBS.find((current) => {
								return current.TYPE === fieldData.TYPE;
							});
						}

						if (!Type.isNil(presetField))
						{
							if (name.endsWith('_datesel'))
							{
								fieldData.MONTHS = presetField.MONTHS;
								fieldData.MONTH = presetField.MONTH;
								fieldData.YEARS = presetField.YEARS;
								fieldData.YEAR = presetField.YEAR;
								fieldData.QUARTERS = presetField.QUARTERS;
								fieldData.QUARTER = presetField.QUARTER;
								fieldData.ENABLE_TIME = presetField.ENABLE_TIME;
								fieldData.YEARS_SWITCHER = presetField.YEARS_SWITCHER;
							}

							fieldData.VALUES = presetField.VALUES;
							fieldData.REQUIRED = presetField.REQUIRED;
						}
					}

					if (this.parent.getParam('ENABLE_LABEL'))
					{
						label = group.querySelector('.main-ui-control-field-label');
						fieldData.LABEL = label.innerText;
					}

					if (name.endsWith('_datesel'))
					{
						dateGroup = this.createDate(fieldData);
					}
					else
					{
						dateGroup = this.createNumber(fieldData);
					}

					if (Type.isArray(this.parent.fieldsList))
					{
						index = this.parent.fieldsList.indexOf(group);

						if (index !== -1)
						{
							this.parent.fieldsList[index] = dateGroup;
							this.parent.registerDragItem(dateGroup);
						}
					}

					this.parent.unregisterDragItem(group);

					controls = [...dateGroup.querySelectorAll('.main-ui-control-field')];

					if (Type.isArray(controls) && controls.length)
					{
						controls.forEach((control) => {
							control.FieldController = new BX.Filter.FieldController(control, this.parent);
						});
					}

					if (this.parent.getParam('ENABLE_ADDITIONAL_FILTERS'))
					{
						const button = AdditionalFilter.getInstance().getAdditionalFilterButton({
							fieldId: fieldData.NAME,
							enabled: fieldData.ADDITIONAL_FILTER_ALLOWED,
						});
						Dom.append(button, dateGroup);
					}

					Dom.insertAfter(dateGroup, group);
					Dom.remove(group);
				}
			}
		}
	}

	createNumber(options)
	{
		const {
			numberTypes,
			additionalNumberTypes,
		} = this.parent;
		const {ENABLE_LABEL} = this.parent.params;
		const {
			SUB_TYPE = {},
			SUB_TYPES = [],
			TABINDEX = '',
			VALUES = {_from: '', _to: ''},
			LABEL = '',
			ICON = null,
			TYPE,
		} = options;

		const subType = SUB_TYPE.VALUE || numberTypes.SINGLE;
		const placeholder = SUB_TYPE.PLACEHOLDER || '';
		const fieldName = options.NAME.replace('_numsel', '');
		const classes = (() => {
			if (ENABLE_LABEL)
			{
				return [
					'main-ui-filter-wield-with-label',
					'main-ui-filter-number-group',
				];
			}

			return ['main-ui-filter-number-group'];
		})();

		const fieldGroup = {
			block: 'number-group',
			type: TYPE,
			mix: classes,
			label: ENABLE_LABEL ? LABEL : '',
			icon: ENABLE_LABEL ? ICON : null,
			dragTitle: this.parent.getParam('MAIN_UI_FILTER__DRAG_FIELD_TITLE'),
			deleteTitle: this.parent.getParam('MAIN_UI_FILTER__REMOVE_FIELD'),
			tabindex: TABINDEX,
			value: SUB_TYPE,
			items: SUB_TYPES,
			name: fieldName,
			deleteButton: true,
			content: [],
		};

		if (
			subType !== numberTypes.LESS
			&& subType !== additionalNumberTypes.BEFORE_N
		)
		{
			const from = {
				block: 'main-ui-control-field',
				type: TYPE,
				dragButton: false,
				content: {
					block: 'main-ui-number',
					mix: ['filter-type-single'],
					calendarButton: true,
					valueDelete: true,
					placeholder,
					name: `${fieldName}_from`,
					tabindex: TABINDEX,
					value: VALUES._from || '',
				},
			};

			fieldGroup.content.push(from);
		}

		if (subType === numberTypes.RANGE)
		{
			const line = {
				block: 'main-ui-filter-field-line',
				content: {
					block: 'main-ui-filter-field-line-item',
					tag: 'span',
				},
			};

			fieldGroup.content.push(line);
		}

		if (
			subType === numberTypes.RANGE
			|| subType === numberTypes.LESS
			|| subType === additionalNumberTypes.BEFORE_N
		)
		{
			const to = {
				block: 'main-ui-control-field',
				type: TYPE,
				dragButton: false,
				content: {
					block: 'main-ui-number',
					calendarButton: true,
					valueDelete: true,
					name: `${fieldName}_to`,
					tabindex: TABINDEX,
					value: VALUES._to || '',
				},
			};

			fieldGroup.content.push(to);
		}

		const field = BX.decl(fieldGroup);

		this.parent.getEmitter().emit(
			'init',
			{
				field: new Field({
					parent: this.parent,
					options: {...options},
					node: field,
				}),
			},
		);

		return field;
	}

	createDate(options)
	{
		const {
			dateTypes,
			additionalDateTypes,
		} = this.parent;
		const {
			SUB_TYPE = {},
			SUB_TYPES = [],
			PLACEHOLDER = '',
			VALUES = {
				_from: '',
				_to: '',
				_quarter: '',
				_days: '',
				_month: '',
				_year: '',
				_allow_year: '',
			},
			TABINDEX = '',
			ENABLE_TIME = false,
			LABEL = '',
			ICON = null,
			TYPE,
			VALUE_REQUIRED = false,
			REQUIRED = false,
		} = options;
		const {ENABLE_LABEL} = this.parent.params;

		const subType = SUB_TYPE.VALUE || dateTypes.NONE;
		const fieldName = options.NAME.replace('_datesel', '');
		const classes = (() => {
			if (ENABLE_LABEL)
			{
				return [
					'main-ui-filter-wield-with-label',
					'main-ui-filter-date-group',
				];
			}

			return ['main-ui-filter-date-group'];
		})();

		const fieldGroup = {
			block: 'date-group',
			type: TYPE,
			mix: classes,
			label: ENABLE_LABEL ? LABEL : '',
			icon: ENABLE_LABEL ? ICON : null,
			dragTitle: this.parent.getParam('MAIN_UI_FILTER__DRAG_FIELD_TITLE'),
			deleteTitle: this.parent.getParam('MAIN_UI_FILTER__REMOVE_FIELD'),
			tabindex: TABINDEX,
			value: SUB_TYPE,
			items: SUB_TYPES,
			name: fieldName,
			enableTime: ENABLE_TIME,
			deleteButton: true,
			content: [],
		};

		if (subType === dateTypes.EXACT)
		{
			const fieldDecl = createDateInputDecl({
				type: TYPE,
				name: `${fieldName.NAME}_from`,
				placeholder: PLACEHOLDER,
				tabindex: TABINDEX,
				value: VALUES._from || '',
				enableTime: ENABLE_TIME,
			});

			fieldGroup.content.push(fieldDecl);
		}

		if (
			subType === dateTypes.NEXT_DAYS
			|| subType === dateTypes.PREV_DAYS
			|| subType === additionalDateTypes.PREV_DAY
			|| subType === additionalDateTypes.NEXT_DAY
			|| subType === additionalDateTypes.MORE_THAN_DAYS_AGO
			|| subType === additionalDateTypes.AFTER_DAYS
		)
		{
			const fieldDecl = createNumberInputDecl({
				type: TYPE,
				name: `${fieldName}_days`,
				tabindex: TABINDEX,
				value: VALUES._days || '',
				placeholder: PLACEHOLDER,
			});

			fieldGroup.content.push(fieldDecl);
		}

		if (subType === dateTypes.RANGE)
		{
			const rangeGroup = {
				block: 'main-ui-filter-range-group',
				content: [
					createDateInputDecl({
						type: TYPE,
						name: `${fieldName}_from`,
						placeholder: PLACEHOLDER,
						tabindex: TABINDEX,
						value: VALUES._from || '',
						enableTime: ENABLE_TIME,
					}),
					createLineDecl(),
					createDateInputDecl({
						type: TYPE,
						name: `${fieldName}_to`,
						placeholder: PLACEHOLDER,
						tabindex: TABINDEX,
						value: VALUES._to || '',
						enableTime: ENABLE_TIME,
					}),
				],
			};

			fieldGroup.content.push(rangeGroup);
		}

		if (subType === dateTypes.MONTH)
		{
			const {MONTHS, MONTH, YEARS, YEAR} = options;

			const monthValue = (
				MONTHS.find((item) => {
					return item.VALUE === VALUES._month;
				})
				|| MONTH
				|| MONTHS[0]
			);

			const yearValue = (
				YEARS.find((item) => {
					return item.VALUE === VALUES._year;
				})
				|| YEAR
				|| YEARS[0]
			);

			fieldGroup.content.push(
				createSelectDecl({
					name: `${fieldName}_month`,
					value: monthValue,
					items: MONTHS,
					tabindex: TABINDEX,
				}),
				createSelectDecl({
					name: `${fieldName}_year`,
					value: yearValue,
					items: YEARS,
					tabindex: TABINDEX,
				}),
			);
		}

		if (subType === dateTypes.QUARTER)
		{
			const {YEARS, YEAR, QUARTERS, QUARTER, PARAMS} = options;

			const yearValue = (
				YEARS.find((item) => {
					return item.VALUE === VALUES._year;
				})
				|| YEAR
				|| YEARS[0]
			);

			const quarterValue = (
				QUARTERS.find((item) => {
					return item.VALUE === VALUES._quarter;
				})
				|| QUARTER
				|| QUARTERS[0]
			);

			fieldGroup.content.push(
				createSelectDecl({
					name: `${fieldName}_year`,
					value: yearValue,
					items: YEARS,
					tabindex: TABINDEX,
				}),
				createSelectDecl({
					name: `${fieldName}_quarter`,
					value: quarterValue,
					items: QUARTERS,
					tabindex: TABINDEX,
					params: PARAMS,
				}),
			);
		}

		if (subType === dateTypes.YEAR)
		{
			const {YEARS, YEAR} = options;

			const yearValue = (
				YEARS.find((item) => {
					return item.VALUE === VALUES._year;
				})
				|| YEAR
				|| YEARS[0]
			);

			fieldGroup.content.push(
				createSelectDecl({
					name: `${fieldName}_year`,
					value: yearValue,
					items: YEARS,
					tabindex: TABINDEX,
				}),
			);
		}

		if (subType === 'CUSTOM_DATE')
		{
			const customDateSubType = SUB_TYPES.find((item) => {
				return item.VALUE === 'CUSTOM_DATE';
			});

			if (customDateSubType)
			{
				const customDateDecl = Runtime.clone(customDateSubType.DECL);

				if (Type.isArray(VALUES._days))
				{
					customDateDecl.VALUE.days = VALUES._days;
				}

				if (Type.isArray(VALUES._month))
				{
					customDateDecl.VALUE.months = VALUES._month;
				}

				if (Type.isArray(VALUES._year))
				{
					customDateDecl.VALUE.years = VALUES._year;
				}

				const renderedField = this.createCustomDate(customDateDecl);
				Dom.removeClass(renderedField, 'main-ui-filter-wield-with-label');

				const buttons = [
					...renderedField
						.querySelectorAll('.main-ui-item-icon-container, .main-ui-filter-icon-grab'),
				];

				buttons.forEach((button) => Dom.remove(button));

				fieldGroup.content.push(renderedField);
				fieldGroup.mix.push('main-ui-filter-custom-date-group');
			}
		}

		if (
			subType !== dateTypes.NONE
			&& subType !== additionalDateTypes.CUSTOM_DATE
			&& options.YEARS_SWITCHER
		)
		{
			const YEARS_SWITCHER = Runtime.clone(options.YEARS_SWITCHER);
			const {ITEMS} = YEARS_SWITCHER;

			YEARS_SWITCHER.VALUE = ITEMS.reduce((acc, item) => {
				return item.VALUE === VALUES._allow_year ? item : acc;
			});

			const renderedField = this.createSelect(YEARS_SWITCHER);

			Dom.addClass(renderedField, ['main-ui-filter-year-switcher', 'main-ui-filter-with-padding']);
			Dom.removeClass(renderedField, 'main-ui-filter-wield-with-label');

			const buttons = [
				...renderedField
					.querySelectorAll('.main-ui-item-icon-container, .main-ui-filter-icon-grab'),
			];

			buttons.forEach((button) => Dom.remove(button));

			const lastIndex = fieldGroup.content.length - 1;
			const lastContentItem = fieldGroup.content[lastIndex];

			if (Type.isPlainObject(lastContentItem))
			{
				if (!Type.isArray(lastContentItem.mix))
				{
					lastContentItem.mix = [];
				}

				lastContentItem.mix.push('main-ui-filter-remove-margin-right');
			}

			if (Type.isDomNode(lastContentItem))
			{
				Dom.addClass(lastContentItem, 'main-ui-filter-remove-margin-right');
			}

			requestAnimationFrame(() => {
				Dom.addClass(renderedField.previousElementSibling, 'main-ui-filter-remove-margin-right');
			});

			fieldGroup.content.push(renderedField);
			fieldGroup.mix.push('main-ui-filter-date-with-years-switcher');
		}

		const renderedFieldGroup = BX.decl(fieldGroup);
		const onDateChange = Runtime.debounce(this.onDateChange, 500, this);

		const inputs = [
			...renderedFieldGroup
				.querySelectorAll('.main-ui-date-input'),
		];

		inputs
			.forEach((input) => {
				input.addEventListener('change', onDateChange);
				input.addEventListener('input', onDateChange);

				const {parentNode} = input;
				const clearButton = parentNode.querySelector('.main-ui-control-value-delete');

				if (clearButton)
				{
					clearButton.addEventListener('click', () => {
						setTimeout(() => {
							this.onDateChange({target: input});
						});
					});
				}
			});

		if (VALUE_REQUIRED)
		{
			renderedFieldGroup.dataset.valueRequired = true;

			const allInputs = [
				...inputs,
				...renderedFieldGroup
					.querySelectorAll('.main-ui-number-input'),
			];

			allInputs
				.forEach((input) => {
					input.addEventListener('change', this.checkRequiredDateValue.bind(this));
					input.addEventListener('input', this.checkRequiredDateValue.bind(this));

					const {parentNode} = input;
					const clearButton = parentNode.querySelector('.main-ui-control-value-delete');

					if (clearButton)
					{
						clearButton.addEventListener('click', () => {
							setTimeout(() => {
								this.checkRequiredDateValue({target: input});
							});
						});
					}

					Event.bindOnce(input, 'mouseout', () => {
						this.checkRequiredDateValue({target: input});
					});
				});
		}

		if (REQUIRED)
		{
			const removeButton = renderedFieldGroup
				.querySelector('.main-ui-filter-field-delete');

			if (removeButton)
			{
				BX.remove(removeButton);
			}
		}

		const currentValues = {};
		this.parent.prepareControlDateValue(currentValues, fieldName, renderedFieldGroup);

		Object.entries(currentValues).forEach(([key, value]) => {
			currentValues[key.replace(fieldName, '')] = value;
			delete currentValues[key];
		});

		this.parent.getEmitter().emit(
			'init',
			{
				field: new Field({
					parent: this.parent,
					options: {...options, VALUES: currentValues},
					node: renderedFieldGroup,
				}),
			},
		);

		return renderedFieldGroup;
	}

	checkRequiredDateValue(event)
	{
		if (event.target.value === '')
		{
			this.showError({
				id: 'valueError',
				target: event.target,
				text: this.parent.params.MAIN_UI_FILTER__VALUE_REQUIRED,
			});
			return;
		}

		this.hideError({
			id: 'valueError',
			target: event.target,
		});
	}

	onDateChange(event)
	{
		if (values.get(event.target) === event.target.value)
		{
			return;
		}

		values.set(event.target, event.target.value);

		if (event.target.value === '')
		{
			this.hideError({
				id: 'formatError',
				target: event.target,
			});
			return;
		}

		BX.ajax
			.runComponentAction(
				'bitrix:main.ui.filter',
				'checkDateFormat',
				{
					mode: 'ajax',
					data: {
						value: event.target.value,
						format: BX.message('FORMAT_DATETIME'),
					},
				},
			)
			.then((result) => {
				if (!result.data.result)
				{
					this.showError({
						id: 'formatError',
						target: event.target,
					});
					return;
				}

				this.hideError({
					id: 'formatError',
					target: event.target,
				});
			});
	}

	showError({id, target, text = null})
	{
		Dom.style(target, 'border-color', '#FF5752');

		if (
			errorMessages.has(target)
			&& errorMessagesTypes.get(target) === id
		)
		{
			Dom.remove(errorMessages.get(target));
		}

		const {
			MAIN_UI_FILTER__DATE_ERROR_TITLE,
			MAIN_UI_FILTER__DATE_ERROR_LABEL,
		} = this.parent.params;

		const errorText = text || `${MAIN_UI_FILTER__DATE_ERROR_LABEL} ${Loc.getMessage('FORMAT_DATE')}`;

		const dateErrorMessage = Tag.render`
			<div 
				class="main-ui-filter-error-message" 
				title="${MAIN_UI_FILTER__DATE_ERROR_TITLE}">
				${errorText}
			</div>
		`;

		errorMessages.set(target, dateErrorMessage);
		errorMessagesTypes.set(target, id);

		Dom.insertAfter(dateErrorMessage, target);
		Dom.attr(target, 'is-valid', 'false');
	}

	hideError({id, target})
	{
		Dom.style(target, 'border-color', null);

		if (
			errorMessages.has(target)
			&& errorMessagesTypes.get(target) === id
		)
		{
			Dom.remove(errorMessages.get(target));
		}

		Dom.attr(target, 'is-valid', 'true');
	}
}