@hashicorp/design-system-components
Version:
Helios Design System Components
156 lines (153 loc) • 7.7 kB
JavaScript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { assert } from '@ember/debug';
import { service } from '@ember/service';
import { HdsPaginationDirectionValues } from '../types.js';
import HdsPaginationControlArrow from '../nav/arrow.js';
import HdsPaginationSizeSelector from '../size-selector/index.js';
import { precompileTemplate } from '@ember/template-compilation';
import { setComponentTemplate } from '@ember/component';
import { g, i } from 'decorator-transforms/runtime';
/**
* Copyright IBM Corp. 2021, 2025
* SPDX-License-Identifier: MPL-2.0
*/
// for context about the decision to use these values, see:
// https://hashicorp.slack.com/archives/C03A0N1QK8S/p1673546329082759
const DEFAULT_PAGE_SIZES = [10, 30, 50];
class HdsPaginationCompact extends Component {
static {
g(this.prototype, "hdsIntl", [service]);
}
#hdsIntl = (i(this, "hdsIntl"), void 0);
static {
g(this.prototype, "_currentPageSize", [tracked]);
}
#_currentPageSize = (i(this, "_currentPageSize"), void 0); // This private variable is used to differentiate between
// "uncontrolled" component (where the state is handled internally) and
// "controlled" component (where the state is handled externally, by the consumer's code).
// In the first case, the variable stores the internal state of the component at any moment,
// and its value is updated internally according to the user's interaction with the component.
// In the second case, the variable stores *only* the initial state of the component (coming from the arguments)
// at rendering time, but from that moment on it's not updated anymore, no matter what interaction the user
// has with the component (the state is controlled externally, eg. via query parameters)
static {
g(this.prototype, "_isControlled", [tracked]);
}
#_isControlled = (i(this, "_isControlled"), void 0);
showLabels = this.args.showLabels ?? true;
showSizeSelector = this.args.showSizeSelector ?? false;
constructor(owner, args) {
super(owner, args);
const {
queryFunction
} = this.args;
// This component works in two different ways, depending if we need to support
// routing through links (`LinkTo`) for the "navigation controls", or not.
// If there's no routing then the component behaves as "uncontrolled"
// (the state updates are handled by its internal logic).
// If instead the component needs to update the routing (and we infer this via the "query" arguments)
// then the component behaves as "controlled", where the state is
// initialized and updated using the arguments passed to it.
if (queryFunction === undefined) {
this._isControlled = false;
} else {
assert('@model, @models, or @route for "Hds::Pagination::Compact" must be provided when using the `@queryFunction` argument', this.args.model !== undefined || this.args.models !== undefined || this.args.route !== undefined);
assert('@queryFunction for "Hds::Pagination::Compact" must be a function', typeof queryFunction === 'function');
this._isControlled = true;
}
// we assert that `this.pageSizes` will always be an array with at least one item
this._currentPageSize = this.args.currentPageSize ?? this.pageSizes[0];
}
get ariaLabel() {
return this.args.ariaLabel ?? this.hdsIntl.t('hds.components.pagination.compact.aria-label', {
default: 'Pagination'
});
}
// This very specific `get/set` pattern is used to handle the two different use cases of the component
// being "controlled" (when it has routing, meaning it needs to support pagination controls as links/`LinkTo`)
// vs being "uncontrolled" (see comments above for details).
//
// If it has routing (and so it's "controlled"), than the value ("state") of the `currentPageSize` variable
// is *always* determined by the controller via arguments (most of the times, connected to query parameters in the URL).
// For this reason the "get" method always returns the value from the `args`,
// while the "set" method never updates the private internal state (_variable).
//
// If instead it doesn't have routing (and so it's "uncontrolled") than the value ("state") of the `currentPageSize` variables
// is *always* determined by the component's internal logic (and updated according to the user interaction with it).
// For this reason the "get" and "set" methods always read from or write to the private internal state (_variable).
get currentPageSize() {
if (this._isControlled) {
return this.args.currentPageSize;
} else {
return this._currentPageSize;
}
}
set currentPageSize(value) {
if (this._isControlled) ; else {
this._currentPageSize = value;
}
}
get pageSizes() {
const {
pageSizes = DEFAULT_PAGE_SIZES
} = this.args;
assert(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`pageSizes argument must be an array. Received: ${pageSizes}`, Array.isArray(pageSizes) === true && pageSizes.length > 0);
return pageSizes;
}
buildQueryParamsObject(page, pageSize) {
if (this._isControlled) {
// if the component is controlled, we can assert that the queryFunction is defined
return this.args.queryFunction(page, pageSize);
} else {
return {};
}
}
get routing() {
const routing = {
route: this.args.route ?? undefined,
model: this.args.model ?? undefined,
models: this.args.models ?? undefined,
replace: this.args.replace ?? undefined
};
// the "query" is dynamic and needs to be calculated
if (this._isControlled) {
routing.queryPrev = this.buildQueryParamsObject(HdsPaginationDirectionValues.Prev, this.currentPageSize);
routing.queryNext = this.buildQueryParamsObject(HdsPaginationDirectionValues.Next, this.currentPageSize);
} else {
routing.queryPrev = undefined;
routing.queryNext = undefined;
}
return routing;
}
onPageChange = newPage => {
const {
onPageChange
} = this.args;
if (typeof onPageChange === 'function') {
onPageChange(newPage);
}
};
onPageSizeChange = newPageSize => {
const {
onPageSizeChange
} = this.args;
// invoke the callback function
if (typeof onPageSizeChange === 'function') {
onPageSizeChange(newPageSize);
}
};
static {
setComponentTemplate(precompileTemplate("<div class=\"hds-pagination\" ...attributes>\n <nav class=\"hds-pagination-nav\" aria-label={{this.ariaLabel}}>\n <HdsPaginationNavArrow @direction=\"prev\" @showLabel={{this.showLabels}} @route={{this.routing.route}} @query={{this.routing.queryPrev}} @model={{this.routing.model}} @models={{this.routing.models}} @replace={{this.routing.replace}} @onClick={{this.onPageChange}} @disabled={{@isDisabledPrev}} />\n <HdsPaginationNavArrow @direction=\"next\" @showLabel={{this.showLabels}} @route={{this.routing.route}} @query={{this.routing.queryNext}} @model={{this.routing.model}} @models={{this.routing.models}} @replace={{this.routing.replace}} @onClick={{this.onPageChange}} @disabled={{@isDisabledNext}} />\n </nav>\n\n {{#if this.showSizeSelector}}\n <HdsPaginationSizeSelector @pageSizes={{this.pageSizes}} @label={{@sizeSelectorLabel}} @selectedSize={{this.currentPageSize}} @onChange={{this.onPageSizeChange}} />\n {{/if}}\n</div>", {
strictMode: true,
scope: () => ({
HdsPaginationNavArrow: HdsPaginationControlArrow,
HdsPaginationSizeSelector
})
}), this);
}
}
export { DEFAULT_PAGE_SIZES, HdsPaginationCompact as default };
//# sourceMappingURL=index.js.map