@hashicorp/design-system-components
Version:
Helios Design System Components
157 lines (144 loc) • 7.74 kB
JavaScript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { assert } from '@ember/debug';
import { HdsPaginationDirectionValues } from '../types.js';
import { precompileTemplate } from '@ember/template-compilation';
import { g, i, n } from 'decorator-transforms/runtime';
import { setComponentTemplate } from '@ember/component';
var TEMPLATE = precompileTemplate("{{!\n Copyright (c) HashiCorp, Inc.\n SPDX-License-Identifier: MPL-2.0\n}}\n<div class=\"hds-pagination\" ...attributes>\n <nav class=\"hds-pagination-nav\" aria-label={{this.ariaLabel}}>\n <Hds::Pagination::Nav::Arrow\n @direction=\"prev\"\n @showLabel={{this.showLabels}}\n @route={{this.routing.route}}\n @query={{this.routing.queryPrev}}\n @model={{this.routing.model}}\n @models={{this.routing.models}}\n @replace={{this.routing.replace}}\n @onClick={{this.onPageChange}}\n @disabled={{@isDisabledPrev}}\n />\n <Hds::Pagination::Nav::Arrow\n @direction=\"next\"\n @showLabel={{this.showLabels}}\n @route={{this.routing.route}}\n @query={{this.routing.queryNext}}\n @model={{this.routing.model}}\n @models={{this.routing.models}}\n @replace={{this.routing.replace}}\n @onClick={{this.onPageChange}}\n @disabled={{@isDisabledNext}}\n />\n </nav>\n\n {{#if this.showSizeSelector}}\n <Hds::Pagination::SizeSelector\n @pageSizes={{this.pageSizes}}\n @label={{@sizeSelectorLabel}}\n @selectedSize={{this.currentPageSize}}\n @onChange={{this.onPageSizeChange}}\n />\n {{/if}}\n</div>");
/**
* Copyright (c) HashiCorp, Inc.
* 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, "_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; // if the labels for the "prev/next" controls are visible
showSizeSelector = this.args.showSizeSelector ?? false; // if the "size selector" block is visible
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 ?? '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);
}
}
static {
n(this.prototype, "onPageChange", [action]);
}
onPageSizeChange(newPageSize) {
const {
onPageSizeChange
} = this.args;
// invoke the callback function
if (typeof onPageSizeChange === 'function') {
onPageSizeChange(newPageSize);
}
}
static {
n(this.prototype, "onPageSizeChange", [action]);
}
}
setComponentTemplate(TEMPLATE, HdsPaginationCompact);
export { DEFAULT_PAGE_SIZES, HdsPaginationCompact as default };
//# sourceMappingURL=index.js.map