UNPKG

@siberiaweb/components

Version:
269 lines (268 loc) 9.85 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import CSS from "./CSS"; import "./ModalForm.css"; import HTMLElementUtils from "../utils/HTMLElementUtils"; /** * Модальная форма. */ export default class ModalForm { /** * Конструктор. * * @param template Шаблон содержания. * @param properties Свойства. */ constructor(template, properties = {}) { /** * Функция модального разрешения. */ this.modalResolve = null; /** * Модальный результат. */ this.modalResult = "undefined"; /** * Элемент для установки фокуса. */ this._focusElement = null; /** * Предотвращение получения событий от клавиатуры элементами модальной формы. */ this.preventKeyboardEvents = false; this.host = this.createHost(template.dataset["modalFormId"]); this.overlay = this.createOverlay(); this.closeOnClickOutOfBounds = (properties.closeOnClickOutOfBounds === undefined) || properties.closeOnClickOutOfBounds; this.closeOnEscape = (properties.closeOnEscape === undefined) || properties.closeOnEscape; this.host.appendChild(template.content.cloneNode(true)); this.overlay.appendChild(this.host); this.initControls(); if (!ModalForm.globalEventListenersAssigned) { document.addEventListener("keydown", ModalForm.documentKeyDownEventListener); document.addEventListener("focusin", ModalForm.documentFocusInEventListener); } } /** * Создание хоста. */ createHost(id) { let container = document.createElement("div"); container.classList.add(CSS.MODAL_FORM); if (id) { container.id = id; } container.addEventListener("keydown", (event) => { if (this.preventKeyboardEvents) { event.preventDefault(); event.stopImmediatePropagation(); } }, { capture: true }); return container; } /** * Создание подложки. */ createOverlay() { let container = document.createElement("div"); container.classList.add(CSS.OVERLAY); container.addEventListener("click", (event) => { if ((event.target === container) && this.closeOnClickOutOfBounds) { this.close("cancel"); } }); return container; } /** * Инициализация элементов управления. */ initControls() { let modalOkElements = Array.from(this.host.querySelectorAll("[data-mr-ok]")); for (const element of modalOkElements) { element.addEventListener("click", () => { this.close("ok"); }); } let modalCancelElements = Array.from(this.host.querySelectorAll("[data-mr-cancel]")); for (const element of modalCancelElements) { element.addEventListener("click", () => { this.close("cancel"); }); } } /** * Вывод. */ show() { return new Promise((resolve => { this.modalResolve = resolve; if (ModalForm.shawnForms.length === 0) { document.body.classList.add(CSS.OVERFLOW_HIDDEN); } else { ModalForm.shawnForms[ModalForm.shawnForms.length - 1].getOverlay().classList.add(CSS.OVERFLOW_HIDDEN); } ModalForm.shawnForms.push(this); document.body.appendChild(this.overlay); window.requestAnimationFrame(() => { let enabledControls = this.getEnabledControls(); if (enabledControls.length > 0) { enabledControls[0].focus(); } }); })); } /** * Закрытие. */ close(result = "undefined") { (() => __awaiter(this, void 0, void 0, function* () { this.preventKeyboardEvents = true; this.overlay.classList.add(CSS.CLOSING); yield HTMLElementUtils.waitForAnimation(this.overlay); this.overlay.classList.remove(CSS.CLOSING); this.preventKeyboardEvents = false; this.modalResult = result; this.overlay.remove(); this.overlay.classList.remove(CSS.OVERFLOW_HIDDEN); ModalForm.shawnForms.splice(ModalForm.shawnForms.indexOf(this), 1); if (ModalForm.shawnForms.length === 0) { document.body.classList.remove(CSS.OVERFLOW_HIDDEN); } else { let topModalForm = ModalForm.shawnForms[ModalForm.shawnForms.length - 1]; if (topModalForm.focusElement !== null) { topModalForm.focusElement.focus(); } } if (this.modalResolve !== null) { this.modalResolve(); } }))(); } /** * Получение модального результата. */ getModalResult() { return this.modalResult; } /** * Получение элемента формы по идентификатору. * * @param id Идентификатор. */ getElementById(id) { return this.getHost().querySelector(`[data-mf-id="${id}"]`); } /** * Получение хоста. */ getHost() { return this.host; } /** * Получение подложки. */ getOverlay() { return this.overlay; } /** * Получение признака закрытия модальной формы при клике вне границ. */ isCloseOnClickOutOfBounds() { return this.closeOnClickOutOfBounds; } /** * Получение признака закрытия модальной формы по клавише Escape. */ isCloseOnEscape() { return this.closeOnEscape; } /** * Получение активных элементов управления. */ getEnabledControls() { let controls = Array.from(this.host.querySelectorAll('a, button, input, select, textarea, [ tabindex ]:not( [ tabindex="-1" ] )')); return controls.filter((control) => { return !control.hasAttribute("disabled"); }); } /** * Получение элемента для фокусировки. */ get focusElement() { return this._focusElement; } /** * Установка элемента для фокусировки. * * @param value Значение. */ set focusElement(value) { this._focusElement = value; } /** * Глобальный обработчик нажатия клавиш. * * @param event Событие. */ static documentKeyDownEventListener(event) { if ((event.code === "Escape") && (ModalForm.shawnForms.length > 0)) { let modalForm = ModalForm.shawnForms[ModalForm.shawnForms.length - 1]; if (modalForm.isCloseOnEscape()) { modalForm.close("cancel"); } } } /** * Глобальный обработчик установки фокуса. * * @param event Событие. */ static documentFocusInEventListener(event) { if (!(event.target instanceof HTMLElement) || (ModalForm.shawnForms.length === 0)) { return; } let modalForm = ModalForm.shawnForms[ModalForm.shawnForms.length - 1]; if (modalForm.getHost().contains(event.target)) { modalForm.focusElement = event.target; return; } let enabledControls = modalForm.getEnabledControls(); if (enabledControls.length === 0) { return; } if (modalForm.focusElement === null) { event.preventDefault(); enabledControls[0].focus(); return; } if (enabledControls[0] === modalForm.focusElement) { event.preventDefault(); enabledControls[enabledControls.length - 1].focus(); return; } if (enabledControls[enabledControls.length - 1] === modalForm.focusElement) { event.preventDefault(); enabledControls[0].focus(); return; } } } /** * Выведенные на экран формы. */ ModalForm.shawnForms = []; /** * Признак установки глобальных обработчиков. */ ModalForm.globalEventListenersAssigned = false;