UNPKG

handsontable

Version:

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

443 lines (430 loc) • 17.8 kB
import "core-js/modules/es.error.cause.js"; import "core-js/modules/esnext.iterator.constructor.js"; import "core-js/modules/esnext.iterator.for-each.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 } from "../../helpers/templateLiteralTag.mjs"; import { mixin } from "../../helpers/object.mjs"; import localHooks from "../../mixins/localHooks.mjs"; import * as C from "../../i18n/constants.mjs"; import { addClass, removeClass, setAttribute } from "../../helpers/dom/element.mjs"; import { A11Y_DISABLED, A11Y_LABEL, A11Y_TABINDEX } from "../../helpers/a11y.mjs"; const TEMPLATE = ` <div data-ref="container" class="ht-pagination handsontable"> <div class="ht-pagination__inner"> <div data-ref="pageSizeSection" class="ht-page-size-section"> <span data-ref="pageSizeLabel" class="ht-page-size-section__label"></span> <div class="ht-page-size-section__select-wrapper"> <select data-ref="pageSizeSelect" name="pageSize" data-hot-input></select> </div> </div> <div data-ref="pageCounterSection" class="ht-page-counter-section"></div> <nav data-ref="pageNavSection" class="ht-page-navigation-section"> <button data-ref="first" class="ht-page-navigation-section__button ht-page-first"></button> <button data-ref="prev" class="ht-page-navigation-section__button ht-page-prev"></button> <span data-ref="pageNavLabel" class="ht-page-navigation-section__label"></span> <button data-ref="next" class="ht-page-navigation-section__button ht-page-next"></button> <button data-ref="last" class="ht-page-navigation-section__button ht-page-last"></button> </nav> </div> </div> `; /** * PaginationUI is a UI component that renders and manages pagination controls. * It handles user interactions (navigation and page size changes), and exposes methods to * toggle visibility of pagination sections and update the state of the pagination controls. * * @private * @class PaginationUI */ var _rootElement = /*#__PURE__*/new WeakMap(); var _uiContainer = /*#__PURE__*/new WeakMap(); var _isRtl = /*#__PURE__*/new WeakMap(); var _refs = /*#__PURE__*/new WeakMap(); var _themeName = /*#__PURE__*/new WeakMap(); var _phraseTranslator = /*#__PURE__*/new WeakMap(); var _shouldHaveBorder = /*#__PURE__*/new WeakMap(); var _a11yAnnouncer = /*#__PURE__*/new WeakMap(); var _PaginationUI_brand = /*#__PURE__*/new WeakSet(); export class PaginationUI { constructor(_ref) { let { rootElement, uiContainer, isRtl, themeName, phraseTranslator, shouldHaveBorder, a11yAnnouncer } = _ref; /** * Updates the visibility of the pagination container based on the visibility of its sections. */ _classPrivateMethodInitSpec(this, _PaginationUI_brand); /** * The root element where the pagination UI will be installed. * * @type {HTMLElement} */ _classPrivateFieldInitSpec(this, _rootElement, void 0); /** * The container element where the pagination UI will be installed. * If not provided, the pagination container will be injected after the root element. * * @type {HTMLElement} */ _classPrivateFieldInitSpec(this, _uiContainer, void 0); /** * Indicates if the UI is in RTL mode. * * @type {boolean} */ _classPrivateFieldInitSpec(this, _isRtl, false); /** * The references to the UI elements. * * @type {object} */ _classPrivateFieldInitSpec(this, _refs, void 0); /** * The name of the current theme. * * @type {string | undefined} */ _classPrivateFieldInitSpec(this, _themeName, void 0); /** * A function to translate phrases used in the UI. * * @type {function(string): string} */ _classPrivateFieldInitSpec(this, _phraseTranslator, void 0); /** * A function that determines whether the pagination should have a border. * * @type {function(): void} */ _classPrivateFieldInitSpec(this, _shouldHaveBorder, void 0); /** * A function allowing to announce accessibility messages. * * @type {function(string): void} */ _classPrivateFieldInitSpec(this, _a11yAnnouncer, void 0); _classPrivateFieldSet(_rootElement, this, rootElement); _classPrivateFieldSet(_uiContainer, this, uiContainer); _classPrivateFieldSet(_isRtl, this, isRtl); _classPrivateFieldSet(_themeName, this, themeName); _classPrivateFieldSet(_phraseTranslator, this, phraseTranslator); _classPrivateFieldSet(_shouldHaveBorder, this, shouldHaveBorder); _classPrivateFieldSet(_a11yAnnouncer, this, a11yAnnouncer); this.install(); } /** * Creates the pagination UI elements and sets up event listeners. */ install() { var _classPrivateFieldGet2; if ((_classPrivateFieldGet2 = _classPrivateFieldGet(_refs, this)) !== null && _classPrivateFieldGet2 !== void 0 && _classPrivateFieldGet2.container) { return; } const elements = html`${TEMPLATE}`; const { container, first, prev, next, last, pageSizeSelect } = elements.refs; _classPrivateFieldSet(_refs, this, elements.refs); container.setAttribute('dir', _classPrivateFieldGet(_isRtl, this) ? 'rtl' : 'ltr'); const isDisabled = event => event.currentTarget.disabled; const addClickListener = (eventName, element, callback) => { element.addEventListener(eventName, event => { if (!isDisabled(event)) { callback(); } }); }; addClickListener('click', first, () => this.runLocalHooks('firstPageClick')); addClickListener('focus', first, () => this.runLocalHooks('focus', first)); addClickListener('click', prev, () => this.runLocalHooks('prevPageClick')); addClickListener('focus', prev, () => this.runLocalHooks('focus', prev)); addClickListener('click', next, () => this.runLocalHooks('nextPageClick')); addClickListener('focus', next, () => this.runLocalHooks('focus', next)); addClickListener('click', last, () => this.runLocalHooks('lastPageClick')); addClickListener('focus', last, () => this.runLocalHooks('focus', last)); addClickListener('focus', pageSizeSelect, () => this.runLocalHooks('focus', pageSizeSelect)); pageSizeSelect.addEventListener('change', () => { const value = pageSizeSelect.value === 'auto' ? 'auto' : Number.parseInt(pageSizeSelect.value, 10); this.runLocalHooks('pageSizeChange', value); }); this.setCounterSectionVisibility(false); this.setNavigationSectionVisibility(false); this.setPageSizeSectionVisibility(false); if (_classPrivateFieldGet(_uiContainer, this)) { _classPrivateFieldGet(_uiContainer, this).appendChild(elements.fragment); addClass(container, [_classPrivateFieldGet(_themeName, this), 'handsontable']); } else { _classPrivateFieldGet(_rootElement, this).after(elements.fragment); } } /** * Gets the pagination element. * * @returns {HTMLElement} The pagination element. */ getContainer() { return _classPrivateFieldGet(_refs, this).container; } /** * Gets the focusable elements. * * @returns {HTMLElement[]} The focusable elements. */ getFocusableElements() { const { first, prev, next, last, pageSizeSelect } = _classPrivateFieldGet(_refs, this); return [pageSizeSelect, first, prev, next, last].filter(element => !element.disabled); } /** * Updates the width of the pagination container. * * @param {number} width The new width of the pagination container. * @returns {PaginationUI} The instance of the PaginationUI for method chaining. */ updateWidth(width) { _classPrivateFieldGet(_refs, this).container.style.width = `${width}px`; return this; } /** * Updates the theme of the pagination container. * * @param {string | false | undefined} themeName The name of the theme to use. * @returns {PaginationUI} The instance of the PaginationUI for method chaining. */ updateTheme(themeName) { _classPrivateFieldSet(_themeName, this, themeName); if (_classPrivateFieldGet(_uiContainer, this)) { const { container } = _classPrivateFieldGet(_refs, this); removeClass(container, /ht-theme-.*/g); if (_classPrivateFieldGet(_themeName, this)) { addClass(container, _classPrivateFieldGet(_themeName, this)); } } return this; } /** * Gets the height of the pagination container element. * * @returns {number} */ getHeight() { return _classPrivateFieldGet(_refs, this).container.offsetHeight; } /** * Refreshes the border state of the pagination container based on the external condition. * * @returns {PaginationUI} The instance of the PaginationUI for method chaining. */ refreshBorderState() { const { container } = _classPrivateFieldGet(_refs, this); if (_classPrivateFieldGet(_uiContainer, this) || _classPrivateFieldGet(_shouldHaveBorder, this).call(this)) { addClass(container, 'ht-pagination--bordered'); } else { removeClass(container, 'ht-pagination--bordered'); } return this; } /** * Updates the state of the pagination UI. * * @param {object} state The pagination state. * @param {number} state.currentPage The current page number. * @param {number} state.totalPages The total number of pages. * @param {number} state.firstVisibleRowIndex The index of the first visible row on the current page. * @param {number} state.lastVisibleRowIndex The index of the last visible row on the current page. * @param {number} state.totalRenderedRows The total number of renderable rows. * @param {Array<number | 'auto'>} state.pageSizeList The list of available page sizes. * @param {number} state.pageSize The current page size. * @param {boolean} state.autoPageSize Indicates if the page size is set to 'auto'. * @returns {PaginationUI} The instance of the PaginationUI for method chaining. */ updateState(_ref2) { let { currentPage, totalPages, firstVisibleRowIndex, lastVisibleRowIndex, totalRenderedRows, pageSizeList, pageSize, autoPageSize } = _ref2; const { first, prev, next, last, pageCounterSection, pageNavSection, pageNavLabel, pageSizeSelect, pageSizeLabel } = _classPrivateFieldGet(_refs, this); const counterSectionText = _classPrivateFieldGet(_phraseTranslator, this).call(this, C.PAGINATION_COUNTER_SECTION, { start: firstVisibleRowIndex + 1, end: lastVisibleRowIndex + 1, total: totalRenderedRows }); const navLabelText = _classPrivateFieldGet(_phraseTranslator, this).call(this, C.PAGINATION_NAV_SECTION, { currentPage, totalPages }); const pageSizeLabelText = _classPrivateFieldGet(_phraseTranslator, this).call(this, C.PAGINATION_PAGE_SIZE_SECTION); pageCounterSection.textContent = counterSectionText; pageNavLabel.textContent = navLabelText; pageSizeSelect.textContent = ''; pageSizeLabel.textContent = `${pageSizeLabelText}:`; setAttribute(pageNavSection, [...[A11Y_LABEL(_classPrivateFieldGet(_phraseTranslator, this).call(this, C.PAGINATION_SECTION))]]); setAttribute(pageSizeSelect, [...[A11Y_LABEL(_classPrivateFieldGet(_phraseTranslator, this).call(this, C.PAGINATION_PAGE_SIZE_SECTION))], ...[A11Y_TABINDEX(-1)]]); _classPrivateFieldGet(_a11yAnnouncer, this).call(this, navLabelText); this.refreshBorderState(); pageSizeList.forEach(pageSizeItem => { const label = pageSizeItem === 'auto' ? _classPrivateFieldGet(_phraseTranslator, this).call(this, C.PAGINATION_PAGE_SIZE_AUTO) : pageSizeItem; const option = new Option(label, pageSizeItem); if (autoPageSize && pageSizeItem === 'auto' || !autoPageSize && pageSizeItem === pageSize) { option.selected = true; } pageSizeSelect.add(option); }); const isFirstPage = currentPage === 1; const isLastPage = currentPage === totalPages; if (pageNavSection.style.display !== 'none') { const activeElement = _classPrivateFieldGet(_rootElement, this).ownerDocument.activeElement; if (isFirstPage) { addClass(first, 'ht-page-navigation-section__button--disabled'); addClass(prev, 'ht-page-navigation-section__button--disabled'); first.disabled = true; prev.disabled = true; } else { removeClass(first, 'ht-page-navigation-section__button--disabled'); removeClass(prev, 'ht-page-navigation-section__button--disabled'); first.disabled = false; prev.disabled = false; } if (isLastPage) { addClass(next, 'ht-page-navigation-section__button--disabled'); addClass(last, 'ht-page-navigation-section__button--disabled'); next.disabled = true; last.disabled = true; } else { removeClass(next, 'ht-page-navigation-section__button--disabled'); removeClass(last, 'ht-page-navigation-section__button--disabled'); next.disabled = false; last.disabled = false; } if ([first, prev, next, last].includes(activeElement)) { if (prev.disabled) { next.focus(); } else if (next.disabled) { prev.focus(); } } } setAttribute(first, [...[A11Y_LABEL(_classPrivateFieldGet(_phraseTranslator, this).call(this, C.PAGINATION_FIRST_PAGE))], ...[A11Y_DISABLED(isFirstPage)], ...[A11Y_TABINDEX(-1)]]); setAttribute(prev, [...[A11Y_LABEL(_classPrivateFieldGet(_phraseTranslator, this).call(this, C.PAGINATION_PREV_PAGE))], ...[A11Y_DISABLED(isFirstPage)], ...[A11Y_TABINDEX(-1)]]); setAttribute(next, [...[A11Y_LABEL(_classPrivateFieldGet(_phraseTranslator, this).call(this, C.PAGINATION_NEXT_PAGE))], ...[A11Y_DISABLED(isLastPage)], ...[A11Y_TABINDEX(-1)]]); setAttribute(last, [...[A11Y_LABEL(_classPrivateFieldGet(_phraseTranslator, this).call(this, C.PAGINATION_LAST_PAGE))], ...[A11Y_DISABLED(isLastPage)], ...[A11Y_TABINDEX(-1)]]); return this; } /** * Sets the visibility of the page size section. * * @param {boolean} isVisible True to show the page size section, false to hide it. * @returns {PaginationUI} The instance of the PaginationUI for method chaining. */ setPageSizeSectionVisibility(isVisible) { const { pageSizeSection, pageSizeSelect } = _classPrivateFieldGet(_refs, this); pageSizeSection.style.display = isVisible ? '' : 'none'; pageSizeSelect.disabled = !isVisible; _assertClassBrand(_PaginationUI_brand, this, _updateContainerVisibility).call(this); return this; } /** * Sets the visibility of the page counter section. * * @param {boolean} isVisible True to show the page size section, false to hide it. * @returns {PaginationUI} The instance of the PaginationUI for method chaining. */ setCounterSectionVisibility(isVisible) { _classPrivateFieldGet(_refs, this).pageCounterSection.style.display = isVisible ? '' : 'none'; _assertClassBrand(_PaginationUI_brand, this, _updateContainerVisibility).call(this); return this; } /** * Sets the visibility of the page navigation section. * * @param {boolean} isVisible True to show the page size section, false to hide it. * @returns {PaginationUI} The instance of the PaginationUI for method chaining. */ setNavigationSectionVisibility(isVisible) { const { pageNavSection, first, prev, next, last } = _classPrivateFieldGet(_refs, this); pageNavSection.style.display = isVisible ? '' : 'none'; first.disabled = !isVisible; prev.disabled = !isVisible; next.disabled = !isVisible; last.disabled = !isVisible; _assertClassBrand(_PaginationUI_brand, this, _updateContainerVisibility).call(this); return this; } /** * Removes the pagination UI elements from the DOM and clears the refs. */ destroy() { var _classPrivateFieldGet3; (_classPrivateFieldGet3 = _classPrivateFieldGet(_refs, this)) === null || _classPrivateFieldGet3 === void 0 || _classPrivateFieldGet3.container.remove(); _classPrivateFieldSet(_refs, this, null); } } function _updateContainerVisibility() { const { container, pageSizeSection, pageCounterSection, pageNavSection } = _classPrivateFieldGet(_refs, this); const isSectionVisible = pageSizeSection.style.display !== 'none' || pageCounterSection.style.display !== 'none' || pageNavSection.style.display !== 'none'; // adds or removes the corner around the Handsontable root element if (!_classPrivateFieldGet(_uiContainer, this)) { if (isSectionVisible) { addClass(_classPrivateFieldGet(_rootElement, this).querySelector('.ht-wrapper'), 'htPagination'); } else { removeClass(_classPrivateFieldGet(_rootElement, this).querySelector('.ht-wrapper'), 'htPagination'); } } container.style.display = isSectionVisible ? '' : 'none'; } mixin(PaginationUI, localHooks);