UNPKG

handsontable

Version:

Handsontable is a JavaScript Data Grid available for React, Angular and Vue.

343 lines (322 loc) • 13.1 kB
import "core-js/modules/es.error.cause.js"; import "core-js/modules/esnext.iterator.constructor.js"; import "core-js/modules/esnext.iterator.filter.js"; import "core-js/modules/esnext.iterator.for-each.js"; import "core-js/modules/esnext.iterator.map.js"; function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); } function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); } function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); } function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); } function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; } function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } import { html, toSingleLine } from "../../helpers/templateLiteralTag.mjs"; import { mixin } from "../../helpers/object.mjs"; import localHooks from "../../mixins/localHooks.mjs"; import { addClass, removeClass, hasClass, fastInnerHTML, setAttribute, removeAttribute } from "../../helpers/dom/element.mjs"; import { A11Y_DIALOG, A11Y_MODAL, A11Y_TABINDEX, A11Y_LABEL, A11Y_LABELED_BY, A11Y_DESCRIBED_BY, A11Y_ALERTDIALOG } from "../../helpers/a11y.mjs"; import { TEMPLATES } from "./templates/index.mjs"; import { DIALOG_CLASS_NAME } from "./constants.mjs"; const CONTAINER_TEMPLATE = ` <div data-ref="dialogElement" class="${DIALOG_CLASS_NAME}"> <div data-ref="dialogWrapperElement" class="${DIALOG_CLASS_NAME}__content-wrapper"> </div> `; /** * DialogUI is a UI component that renders and manages dialog elements. * It handles dialog creation, content updates, visibility toggling, and styling. * * @private * @class DialogUI */ var _rootElement = /*#__PURE__*/new WeakMap(); var _refs = /*#__PURE__*/new WeakMap(); var _isRtl = /*#__PURE__*/new WeakMap(); var _animationStarted = /*#__PURE__*/new WeakMap(); var _template = /*#__PURE__*/new WeakMap(); var _templateButtonCallbacks = /*#__PURE__*/new WeakMap(); var _DialogUI_brand = /*#__PURE__*/new WeakSet(); export class DialogUI { constructor(_ref) { let { rootElement, isRtl } = _ref; /** * Handles the transition end event. */ _classPrivateMethodInitSpec(this, _DialogUI_brand); /** * The root element where the dialog UI will be installed. * * @type {HTMLElement} */ _classPrivateFieldInitSpec(this, _rootElement, void 0); /** * The references to the UI elements. * * @type {object} */ _classPrivateFieldInitSpec(this, _refs, void 0); /** * Indicates if the UI is in RTL mode. * * @type {boolean} */ _classPrivateFieldInitSpec(this, _isRtl, false); /** * Indicates if the animation has started. * * @type {boolean} */ _classPrivateFieldInitSpec(this, _animationStarted, false); /** * The template to use for the dialog. * * @type {function(): string} */ _classPrivateFieldInitSpec(this, _template, TEMPLATES.get('base')); /** * The callbacks of the template buttons to trigger when the button is clicked. * * @type {Array<function(MouseEvent)>} */ _classPrivateFieldInitSpec(this, _templateButtonCallbacks, []); _classPrivateFieldSet(_rootElement, this, rootElement); _classPrivateFieldSet(_isRtl, this, isRtl); this.install(); } /** * Uses the specified template for the dialog. * * @param {string} templateName The name of the template to use. * @param {object} templateVars The variables to use for the template. */ useTemplate(templateName) { var _templateVars$buttons; let templateVars = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (!TEMPLATES.has(templateName) || templateName === 'base') { const validTemplates = Array.from(TEMPLATES.keys()).filter(template => template !== 'base').join(', '); throw new Error(toSingleLine`Invalid template: ${templateName}.\x20 Valid templates are: ${validTemplates}.`); } _classPrivateFieldSet(_template, this, TEMPLATES.get(templateName)(templateVars)); _classPrivateFieldSet(_templateButtonCallbacks, this, ((_templateVars$buttons = templateVars.buttons) !== null && _templateVars$buttons !== void 0 ? _templateVars$buttons : []).map(button => button.callback)); } /** * Uses the default template for the dialog for the `content` option. */ useDefaultTemplate() { _classPrivateFieldSet(_template, this, TEMPLATES.get('base')()); _classPrivateFieldSet(_templateButtonCallbacks, this, []); } /** * Creates the dialog UI elements and sets up the structure. */ install() { var _classPrivateFieldGet2; if ((_classPrivateFieldGet2 = _classPrivateFieldGet(_refs, this)) !== null && _classPrivateFieldGet2 !== void 0 && _classPrivateFieldGet2.dialogElement) { return; } const elements = html`${CONTAINER_TEMPLATE}`; _classPrivateFieldSet(_refs, this, elements.refs); const { dialogElement } = _classPrivateFieldGet(_refs, this); // Set ARIA attributes setAttribute(dialogElement, [A11Y_MODAL(), ['dir', _classPrivateFieldGet(_isRtl, this) ? 'rtl' : 'ltr']]); dialogElement.addEventListener('transitionstart', () => _assertClassBrand(_DialogUI_brand, this, _onTransitionStart).call(this)); dialogElement.addEventListener('transitionend', () => _assertClassBrand(_DialogUI_brand, this, _onTransitionEnd).call(this)); // Append to Handsontable after table grid element _classPrivateFieldGet(_rootElement, this).after(elements.fragment); } /** * Returns the dialog element. * * @returns {HTMLElement} The dialog element. */ getContainer() { return _classPrivateFieldGet(_refs, this).dialogElement; } /** * Gets the focusable elements. * * @returns {HTMLElement[]} The focusable elements. */ getFocusableElements() { return _classPrivateFieldGet(_template, this).focusableElements(); } /** * Updates the dialog content and class name. * * @param {object} options - Class name update options. * @param {boolean} options.isVisible - Whether the dialog is visible. * @param {string|HTMLElement} options.content - The content to render in the dialog. * @param {string} options.customClassName - The custom class name to add to the dialog. * @param {string} options.background - The background to add to the dialog. * @param {boolean} options.contentBackground - Whether to show content background. * @param {boolean} options.animation - Whether to add the animation class to the dialog. * @param {object} options.a11y - The accessibility options for the dialog. * * @returns {DialogUI} The instance of the DialogUI. */ updateDialog(_ref2) { let { isVisible, content, customClassName, background, contentBackground, animation, a11y } = _ref2; const elements = _classPrivateFieldGet(_template, this).compile(); const { dialogElement, dialogWrapperElement } = _classPrivateFieldGet(_refs, this); dialogWrapperElement.innerHTML = ''; dialogWrapperElement.appendChild(elements.fragment); Object.assign(_classPrivateFieldGet(_refs, this), elements.refs); const { contentElement, buttonsContainer } = _classPrivateFieldGet(_refs, this); if (_classPrivateFieldGet(_template, this).TEMPLATE_NAME !== 'base') { Object.assign(a11y, _classPrivateFieldGet(_template, this).dialogA11YOptions()); } // Dialog class name const customClass = customClassName ? ` ${customClassName}` : ''; const backgroundClass = background ? ` ${DIALOG_CLASS_NAME}--background-${background}` : ''; const animationClass = animation ? ` ${DIALOG_CLASS_NAME}--animation` : ''; const showClass = isVisible ? ` ${DIALOG_CLASS_NAME}--show` : ''; // Update dialog class name dialogElement.className = [DIALOG_CLASS_NAME, `${DIALOG_CLASS_NAME}--${_classPrivateFieldGet(_template, this).TEMPLATE_NAME}`, 'handsontable', customClass, backgroundClass, animationClass, showClass].join(' '); setAttribute(dialogElement, [_classPrivateFieldGet(_template, this).TEMPLATE_NAME === 'base' ? A11Y_TABINDEX(-1) : undefined]); // Dialog aria attributes setAttribute(dialogElement, [a11y.role === 'alertdialog' ? A11Y_ALERTDIALOG() : A11Y_DIALOG()]); if (a11y.ariaLabel && !a11y.ariaLabelledby) { setAttribute(dialogElement, [a11y.ariaLabel ? A11Y_LABEL(a11y.ariaLabel) : undefined]); } else { removeAttribute(dialogElement, 'aria-label'); } if (a11y.ariaLabelledby) { setAttribute(dialogElement, [A11Y_LABELED_BY(a11y.ariaLabelledby)]); } else { removeAttribute(dialogElement, 'aria-labelledby'); } if (a11y.ariaDescribedby) { setAttribute(dialogElement, [A11Y_DESCRIBED_BY(a11y.ariaDescribedby)]); } else { removeAttribute(dialogElement, 'aria-describedby'); } // Dialog content class name const contentBackgroundClass = contentBackground ? ` ${DIALOG_CLASS_NAME}__content--background` : ''; // Update content class name addClass(contentElement, `${DIALOG_CLASS_NAME}__content${contentBackgroundClass}`); if (_classPrivateFieldGet(_template, this).TEMPLATE_NAME === 'base') { // Clear existing dialog content contentElement.innerHTML = ''; // Render new dialog content if (typeof content === 'string') { fastInnerHTML(contentElement, content); } else if (content instanceof HTMLElement || content instanceof DocumentFragment) { contentElement.appendChild(content); } } else if (buttonsContainer) { Array.from(buttonsContainer.children).forEach((button, index) => { const callback = _classPrivateFieldGet(_templateButtonCallbacks, this)[index]; if (callback) { button.addEventListener('click', callback); } }); } return this; } /** * Shows the dialog with optional animation. * * @param {boolean} animation - Whether to add the animation class to the dialog. * @returns {DialogUI} The instance of the DialogUI. */ showDialog(animation) { const { dialogElement } = _classPrivateFieldGet(_refs, this); dialogElement.style.display = 'block'; if (animation) { // Triggers style and layout recalculation, so the display: block is fully committed before adding // the class ht-dialog--show. // eslint-disable-next-line no-unused-expressions dialogElement.offsetHeight; } addClass(dialogElement, `${DIALOG_CLASS_NAME}--show`); _classPrivateFieldSet(_animationStarted, this, false); return this; } /** * Hides the dialog with optional animation. * * @param {boolean} animation - Whether to add the animation class to the dialog. * @returns {DialogUI} The instance of the DialogUI. */ hideDialog(animation) { const { dialogElement } = _classPrivateFieldGet(_refs, this); removeClass(dialogElement, `${DIALOG_CLASS_NAME}--show`); if (animation && !_classPrivateFieldGet(_animationStarted, this) || !animation) { dialogElement.style.display = 'none'; } _classPrivateFieldSet(_animationStarted, this, false); return this; } /** * Focuses the dialog element. */ focusDialog() { _classPrivateFieldGet(_refs, this).dialogElement.focus(); } /** * Updates the width of the dialog container to the same size as the table. * * @param {number} width - The width of the table. * @returns {DialogUI} The instance of the DialogUI. */ updateWidth(width) { _classPrivateFieldGet(_refs, this).dialogElement.style.width = `${width}px`; return this; } /** * Updates the height of the dialog container. * * @param {number} licenseInfoHeight - The height of the license info. * @returns {DialogUI} The instance of the DialogUI. */ updateHeight(licenseInfoHeight) { _classPrivateFieldGet(_refs, this).dialogElement.style.height = `calc(100% - ${licenseInfoHeight}px)`; return this; } /** * Removes the dialog UI elements from the DOM and clears the refs. */ destroyDialog() { var _classPrivateFieldGet3; (_classPrivateFieldGet3 = _classPrivateFieldGet(_refs, this)) === null || _classPrivateFieldGet3 === void 0 || _classPrivateFieldGet3.dialogElement.remove(); _classPrivateFieldSet(_refs, this, null); } } function _onTransitionEnd() { const { dialogElement } = _classPrivateFieldGet(_refs, this); if (!hasClass(dialogElement, `${DIALOG_CLASS_NAME}--show`)) { dialogElement.style.display = 'none'; } } /** * Handles the transition start event. This is used to track if the animation has started. */ function _onTransitionStart() { _classPrivateFieldSet(_animationStarted, this, true); } mixin(DialogUI, localHooks);