@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
277 lines (276 loc) • 15.4 kB
JavaScript
/*! All material copyright ESRI, All Rights Reserved, unless otherwise specified.
See https://github.com/Esri/calcite-design-system/blob/dev/LICENSE.md for details.
v3.2.1 */
import { c as customElement } from "../../chunks/runtime.js";
import { isServer, nothing, html } from "lit";
import { render } from "lit-html";
import { createRef, ref } from "lit-html/directives/ref.js";
import { LitElement, createEvent, safeClassMap } from "@arcgis/lumina";
import { n as numberStringFormatter } from "../../chunks/locale.js";
import { u as useT9n } from "../../chunks/useT9n.js";
import { i as isHidden } from "../../chunks/component.js";
import { css } from "@lit/reactive-element/css-tag.js";
function getUserAgentData() {
return navigator.userAgentData;
}
function getUserAgentString() {
if (isServer) {
return "";
}
const uaData = getUserAgentData();
return uaData?.brands ? uaData.brands.map(({ brand, version }) => `${brand}/${version}`).join(" ") : navigator.userAgent;
}
const CSS = {
bordered: "bordered",
striped: "striped",
selectionArea: "selection-area",
paginationArea: "pagination-area",
container: "container",
tableContainer: "table-container",
tableFixed: "table--fixed",
assistiveText: "assistive-text",
selectionActions: "selection-actions"
};
const SLOTS = {
selectionActions: "selection-actions",
tableHeader: "table-header",
tableFooter: "table-footer"
};
const styles = css` "UTF-8";:host([scale=s]){--calcite-internal-table-cell-padding: .25rem;--calcite-internal-table-cell-font-size: var(--calcite-font-size--2);--calcite-internal-table-cell-font-size-secondary: var(--calcite-font-size--3)}:host([scale=m]){--calcite-internal-table-cell-padding: .5rem;--calcite-internal-table-cell-font-size: var(--calcite-font-size--1);--calcite-internal-table-cell-font-size-secondary: var(--calcite-font-size--2)}:host([scale=l]){--calcite-internal-table-cell-padding: 1rem;--calcite-internal-table-cell-font-size: var(--calcite-font-size-0);--calcite-internal-table-cell-font-size-secondary: var(--calcite-font-size--1)}:host{display:flex}.container{display:flex;block-size:100%;inline-size:100%;flex-direction:column}.table-container{overflow:auto;white-space:nowrap;border:1px solid var(--calcite-color-border-3)}.assistive-text{position:absolute;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}table{inline-size:100%;border-collapse:collapse;overflow-x:scroll}@-moz-document url-prefix(){table{border-collapse:separate;border-spacing:0}}.table--fixed{table-layout:fixed}.bordered ::slotted(calcite-table-row){--calcite-table-row-border-color: var(--calcite-color-border-3)}.striped ::slotted(calcite-table-row:nth-child(2n+1)){--calcite-table-row-background: var(--calcite-color-foreground-2)}.selection-actions{display:flex;flex-direction:row;margin-inline-start:auto}.selection-area{display:flex;flex-direction:row;align-items:center;padding-block:var(--calcite-internal-table-cell-padding)}.selection-area calcite-chip:last-of-type{margin-inline-end:.5rem}.selection-area calcite-chip:last-of-type:not(:first-of-type){margin-inline-start:.5rem}.selection-area calcite-button{margin-inline-end:1rem}.pagination-area{display:flex;inline-size:100%;flex-direction:row;justify-content:center;padding-block:var(--calcite-internal-table-cell-padding)}calcite-pagination{flex:1;justify-content:center}:host([hidden]){display:none}[hidden]{display:none}`;
class Table extends LitElement {
constructor() {
super();
this.paginationEl = createRef();
this.tableBodySlotEl = createRef();
this.tableFootSlotEl = createRef();
this.tableHeadSlotEl = createRef();
this.messages = useT9n({ blocking: true });
this.colCount = 0;
this.pageStartRow = 1;
this.selectedCount = 0;
this._selectedItems = [];
this.bordered = false;
this.groupSeparator = false;
this.interactionMode = "interactive";
this.layout = "auto";
this.numbered = false;
this.pageSize = 0;
this.scale = "m";
this.selectionDisplay = "top";
this.selectionMode = "none";
this.striped = false;
this.calciteInternalTableRowFocusChange = createEvent({ cancelable: false });
this.calciteTablePageChange = createEvent({ cancelable: false });
this.calciteTableSelect = createEvent({ cancelable: false });
this.listen("calciteTableRowSelect", this.calciteTableRowSelectListener);
this.listen("calciteInternalTableRowSelect", this.calciteInternalTableRowSelectListener);
this.listen("calciteInternalTableRowFocusRequest", this.calciteInternalTableRowFocusEvent);
}
static {
this.properties = { colCount: [16, {}, { state: true }], pageStartRow: [16, {}, { state: true }], readCellContentsToAT: [16, {}, { state: true }], selectedCount: [16, {}, { state: true }], _selectedItems: [16, {}, { state: true }], bordered: [7, {}, { reflect: true, type: Boolean }], caption: 1, groupSeparator: [7, {}, { reflect: true, type: Boolean }], interactionMode: [3, {}, { reflect: true }], layout: [3, {}, { reflect: true }], messageOverrides: [0, {}, { attribute: false }], numbered: [7, {}, { reflect: true, type: Boolean }], numberingSystem: [3, {}, { reflect: true }], pageSize: [11, {}, { reflect: true, type: Number }], scale: [3, {}, { reflect: true }], selectedItems: [32, {}, { attribute: false, readOnly: true }], selectionDisplay: [3, {}, { reflect: true }], selectionMode: [3, {}, { reflect: true }], striped: [7, {}, { reflect: true, type: Boolean }] };
}
static {
this.styles = styles;
}
get selectedItems() {
return this._selectedItems;
}
async load() {
this.readCellContentsToAT = /safari/i.test(getUserAgentString());
this.listenOn(this.el.shadowRoot, "slotchange", this.handleSlotChange);
}
willUpdate(changes) {
if (changes.has("groupSeparator") && (this.hasUpdated || this.groupSeparator !== false) || changes.has("interactionMode") && (this.hasUpdated || this.interactionMode !== "interactive") || changes.has("numbered") && (this.hasUpdated || this.numbered !== false) || changes.has("numberingSystem") || changes.has("pageSize") && (this.hasUpdated || this.pageSize !== 0) || changes.has("scale") && (this.hasUpdated || this.scale !== "m") || changes.has("selectionMode") && (this.hasUpdated || this.selectionMode !== "none")) {
this.updateRows();
}
}
handleSlotChange() {
this.updateRows();
}
calciteTableRowSelectListener(event) {
if (event.composedPath().includes(this.el)) {
this.setSelectedItems(event.target);
}
}
calciteInternalTableRowSelectListener(event) {
if (!event.composedPath().includes(this.el)) {
return;
}
this.updateSelectedItems(false);
event.stopPropagation();
}
calciteInternalTableRowFocusEvent(event) {
const cellPosition = event.detail.cellPosition;
const rowPos = event.detail.rowPosition;
const destination = event.detail.destination;
const lastCell = event.detail.lastCell;
const visibleBody = this.bodyRows?.filter((row) => !isHidden(row));
const visibleAll = this.allRows?.filter((row) => !isHidden(row));
const lastHeadRow = this.headRows[this.headRows.length - 1]?.positionAll;
const firstBodyRow = visibleBody[0]?.positionAll;
const lastBodyRow = visibleBody[visibleBody.length - 1]?.positionAll;
const firstFootRow = this.footRows[0]?.positionAll;
const lastTableRow = visibleAll[visibleAll.length - 1]?.positionAll;
const leavingHeader = destination === "next" && rowPos === lastHeadRow;
const leavingFooter = destination === "previous" && rowPos === firstFootRow;
const enteringHeader = destination === "previous" && rowPos === firstBodyRow;
const enteringFooter = destination === "next" && rowPos === lastBodyRow;
let rowPosition;
switch (destination) {
case "first":
rowPosition = 0;
break;
case "last":
rowPosition = lastTableRow;
break;
case "next":
rowPosition = leavingHeader ? firstBodyRow : enteringFooter ? firstFootRow : rowPos + 1;
break;
case "previous":
rowPosition = leavingFooter ? lastBodyRow : enteringHeader ? lastHeadRow : rowPos - 1;
break;
}
const destinationCount = this.allRows?.find((row) => row.positionAll === rowPosition)?.cellCount;
const adjustedPos = cellPosition > destinationCount ? destinationCount : cellPosition;
if (rowPosition !== void 0) {
this.calciteInternalTableRowFocusChange.emit({
cellPosition: adjustedPos,
rowPosition,
destination,
lastCell
});
}
}
getSlottedRows(el) {
return el?.assignedElements({ flatten: true })?.filter((el2) => el2?.matches("calcite-table-row"));
}
updateRows() {
const headRows = this.getSlottedRows(this.tableHeadSlotEl.value) || [];
const bodyRows = this.getSlottedRows(this.tableBodySlotEl.value) || [];
const footRows = this.getSlottedRows(this.tableFootSlotEl.value) || [];
const allRows = [...headRows, ...bodyRows, ...footRows];
headRows?.forEach((row) => {
const position = headRows?.indexOf(row);
row.rowType = "head";
row.positionSection = position;
row.positionSectionLocalized = this.localizeNumber((position + 1).toString());
});
bodyRows?.forEach((row) => {
const position = bodyRows?.indexOf(row);
row.rowType = "body";
row.positionSection = position;
row.positionSectionLocalized = this.localizeNumber((position + 1).toString());
});
footRows?.forEach((row) => {
const position = footRows?.indexOf(row);
row.rowType = "foot";
row.positionSection = position;
row.positionSectionLocalized = this.localizeNumber((position + 1).toString());
});
allRows?.forEach((row) => {
row.interactionMode = this.interactionMode;
row.selectionMode = this.selectionMode;
row.bodyRowCount = bodyRows?.length;
row.positionAll = allRows?.indexOf(row);
row.numbered = this.numbered;
row.scale = this.scale;
row.readCellContentsToAT = this.readCellContentsToAT;
row.lastVisibleRow = allRows?.indexOf(row) === allRows.length - 1;
});
const colCount = headRows[0]?.cellCount || headRows[0]?.querySelectorAll("calcite-table-header")?.length;
this.colCount = colCount;
this.headRows = headRows;
this.bodyRows = bodyRows;
this.footRows = footRows;
this.allRows = allRows;
this.updateSelectedItems();
this.paginateRows();
}
handlePaginationChange() {
const requestedItem = this.paginationEl.value?.startItem;
this.pageStartRow = requestedItem || 1;
this.calciteTablePageChange.emit();
this.updateRows();
}
paginateRows() {
this.bodyRows?.forEach((row) => {
const rowPos = row.positionSection + 1;
const inView = rowPos >= this.pageStartRow && rowPos < this.pageStartRow + this.pageSize;
row.itemHidden = this.pageSize > 0 && !inView && !this.footRows.includes(row);
row.lastVisibleRow = rowPos === this.pageStartRow + this.pageSize - 1 || rowPos === this.bodyRows.length;
});
}
async updateSelectedItems(emit) {
const selectedItems = this.bodyRows?.filter((el) => el.selected);
this._selectedItems = selectedItems;
this.selectedCount = selectedItems?.length;
this.allRows?.forEach((row) => {
row.selectedRowCount = this.selectedCount;
row.selectedRowCountLocalized = this.localizeNumber(this.selectedCount);
});
if (emit) {
this.calciteTableSelect.emit();
}
}
handleDeselectAllRows() {
this.bodyRows?.forEach((row) => {
row.selected = false;
});
this.updateSelectedItems(true);
}
setSelectedItems(elToMatch) {
this.bodyRows?.forEach((el) => {
if (elToMatch?.rowType === "head") {
el.selected = this.selectedCount !== this.bodyRows?.length;
} else {
el.selected = this.selectionMode === "multiple" || elToMatch === el ? el.selected : false;
}
});
this.updateSelectedItems(true);
}
localizeNumber(value) {
numberStringFormatter.numberFormatOptions = {
locale: this.messages._lang,
numberingSystem: this.numberingSystem,
useGrouping: this.groupSeparator
};
return numberStringFormatter.localize(value.toString());
}
renderSelectionArea() {
const outOfViewCount = this._selectedItems?.filter((el) => isHidden(el))?.length;
const localizedOutOfView = this.localizeNumber(outOfViewCount?.toString());
const localizedSelectedCount = this.localizeNumber(this.selectedCount?.toString());
const selectionText = `${localizedSelectedCount} ${this.messages.selected}`;
const outOfView = `${localizedOutOfView} ${this.messages.hiddenSelected}`;
return html`<div class=${safeClassMap(CSS.selectionArea)}><calcite-chip .kind=${this.selectedCount > 0 ? "brand" : "neutral"} .label=${selectionText} .scale=${this.scale} .value=${selectionText}>${selectionText}</calcite-chip>${outOfViewCount > 0 && html`<calcite-chip icon=hide-empty .label=${outOfView} .scale=${this.scale} title=${outOfView ?? nothing} .value=${outOfView}>${localizedOutOfView}</calcite-chip>` || ""}${this.selectedCount > 0 && html`<calcite-button icon-start=x kind=neutral =${this.handleDeselectAllRows} round .scale=${this.scale} .title=${`${this.messages.clear} ${selectionText} ${this.messages.row}`}>${this.messages.clear}</calcite-button>` || ""}<div class=${safeClassMap(CSS.selectionActions)}><slot name=${SLOTS.selectionActions}></slot></div></div>`;
}
renderPaginationArea() {
return html`<div class=${safeClassMap(CSS.paginationArea)}><calcite-pagination .groupSeparator=${this.groupSeparator} .numberingSystem=${this.numberingSystem} =${this.handlePaginationChange} .pageSize=${this.pageSize} .scale=${this.scale} start-item=1 .totalItems=${this.bodyRows?.length} ${ref(this.paginationEl)}></calcite-pagination></div>`;
}
renderTHead() {
return html`<thead><slot name=${SLOTS.tableHeader} ${ref(this.tableHeadSlotEl)}></slot></thead>`;
}
renderTBody() {
return html`<tbody><slot ${ref(this.tableBodySlotEl)}></slot></tbody>`;
}
renderTFoot() {
return html`<tfoot><slot name=${SLOTS.tableFooter} ${ref(this.tableFootSlotEl)}></slot></tfoot>`;
}
render() {
return html`<div class=${safeClassMap(CSS.container)}>${this.selectionMode !== "none" && this.selectionDisplay !== "none" && this.renderSelectionArea() || ""}<div class=${safeClassMap({
[CSS.bordered]: this.bordered,
[CSS.striped]: this.striped,
[CSS.tableContainer]: true
})}><table .ariaColCount=${this.colCount} .ariaMultiSelectable=${/* workaround to ensure the attr gets removed; we should be able to avoid the ternary when fixed */
this.selectionMode === "multiple" ? "true" : null} .ariaRowCount=${this.allRows?.length} class=${safeClassMap({ [CSS.tableFixed]: this.layout === "fixed" })} .role=${this.interactionMode === "interactive" ? "grid" : "table"} ${ref((el) => {
if (!el) {
return;
}
render(html`<caption class=${safeClassMap(CSS.assistiveText)}>${this.caption}</caption>${this.renderTHead()}${this.renderTBody()}${this.renderTFoot()}`, el);
})}></table></div>${this.pageSize > 0 && this.renderPaginationArea() || ""}</div>`;
}
}
customElement("calcite-table", Table);
export {
Table
};