handsontable
Version:
Handsontable is a JavaScript Data Grid available for React, Angular and Vue.
443 lines (430 loc) • 17.8 kB
JavaScript
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);