@3mo/data-grid
Version:
A data grid web component
207 lines (198 loc) • 6.98 kB
JavaScript
import { __decorate } from "tslib";
import { component, Component, html, property, css, state } from '@a11d/lit';
import { NotificationComponent } from '@a11d/lit-application';
import { Localizer } from '@3mo/localization';
import { DataGridEditability } from './index.js';
Localizer.dictionaries.add('de', {
'Copied to clipboard': 'In die Zwischenablage kopiert',
});
/**
* @element mo-data-grid-cell
*
* @attr value
* @attr column
* @attr row
*/
let DataGridCell = class DataGridCell extends Component {
constructor() {
super(...arguments);
this.editing = false;
}
get dataGrid() { return this.row.dataGrid; }
get data() { return this.row.data; }
get dataSelector() { return this.column.dataSelector; }
get cellIndex() { return this.row.cells.indexOf(this); }
get rowIndex() { return this.dataGrid.rows.indexOf(this.row); }
get valueTextContent() { return this.renderRoot.textContent?.trim() || ''; }
get isEditable() {
return this.dataGrid.editability !== DataGridEditability.Never
&& [undefined, null].includes(this.editContentTemplate) === false
&& (this.column.editable === true || (typeof this.column.editable === 'function' && this.column.editable(this.data)));
}
get isEditing() {
return this.isEditable
&& (this.editing || this.dataGrid.editability === DataGridEditability.Always);
}
handlePointerDown(event) {
if (this.isEditing && event.composedPath().includes(this) === false) {
this.setEditing(false);
}
}
handleDoubleClick(event) {
if (this.dataGrid.editability === DataGridEditability.Cell) {
event.preventDefault();
this.setEditing(true);
}
}
async handleKeyDown(event) {
switch (event.key) {
case 'Enter':
event.preventDefault();
event.stopPropagation();
if (this.isEditable) {
this.setEditing(true);
}
else {
this.click();
}
break;
case 'Escape':
event.preventDefault();
event.stopPropagation();
this.setEditing(false);
await this.updateComplete;
this.focusCell(event, this);
break;
case 'c':
if (this.isEditing === false && (event.ctrlKey || event.metaKey)) {
event.preventDefault();
await navigator.clipboard.writeText(this.valueTextContent);
NotificationComponent.notifySuccess(t('Copied to clipboard'));
}
break;
case 'Tab':
case 'ArrowRight':
this.focusCell(event, this.row.cells.at(this.cellIndex === this.dataGrid.visibleColumns.length - 1 ? 0 : this.cellIndex + 1));
break;
case 'ArrowLeft':
this.focusCell(event, this.row.cells.at(this.cellIndex === 0 ? this.dataGrid.visibleColumns.length - 1 : this.cellIndex - 1));
break;
case 'ArrowUp':
this.focusCell(event, this.dataGrid.rows.at(this.rowIndex === 0 ? this.dataGrid.rows.length - 1 : this.rowIndex - 1)?.cells.at(this.cellIndex));
break;
case 'ArrowDown':
this.focusCell(event, this.dataGrid.rows.at(this.rowIndex === this.dataGrid.rows.length - 1 ? 0 : this.rowIndex + 1)?.cells.at(this.cellIndex));
break;
default:
break;
}
}
async setEditing(value) {
if (this.editing === value) {
return;
}
this.editing = value;
await this.updateComplete;
if (value) {
this.renderRoot.querySelector('[autofocus]')?.focus();
}
}
focusCell(event, cell) {
if (cell && this.isEditing === false) {
event.preventDefault();
cell.focus();
this.setEditing(false);
if (this.dataGrid.selectOnClick) {
this.dataGrid.selectionController.setSelection(cell.row.data, true, event.shiftKey);
}
}
}
static get styles() {
return css `
:host {
display: inline-grid;
align-items: center;
position: relative;
padding-inline: var(--mo-data-grid-cell-padding);
font-size: var(--mo-data-grid-cell-font-size);
outline: none;
min-height: var(--mo-data-grid-row-height);
}
:host(:not([isEditing])) div {
user-select: none;
white-space: nowrap;
overflow: hidden !important;
text-overflow: ellipsis;
}
:host(:not([isEditing]):focus) {
outline: 2px solid var(--mo-color-accent);
}
:host([isEditing]) {
display: grid;
}
:host([alignment=start]) {
text-align: start;
}
:host([alignment=center]) {
text-align: center;
}
:host([alignment=end]) {
text-align: end;
}
:host([sticky]) {
position: sticky;
}
:host([sticky]) /*[sticking]*/ {
z-index: 2;
background: var(--mo-data-grid-sticky-part-color);
}
`;
}
get tooltip() { return this.valueTextContent; }
get template() {
this.title = this.tooltip;
this.toggleAttribute('isEditing', this.isEditing);
this.setAttribute('alignment', this.column.alignment || 'start');
if (this.isEditing) {
this.removeAttribute('tabindex');
}
else {
this.setAttribute('tabindex', '-1');
}
this.toggleAttribute('sticky', this.column.sticky !== undefined);
this.toggleAttribute('sticking', this.column.intersecting === false);
this.style.insetInline = this.column.stickyColumnInsetInline;
return this.isEditing ? this.editContentTemplate : this.contentTemplate;
}
get contentTemplate() {
return html `
<div>
${this.column.getContentTemplate?.(this.value, this.data) ?? html `${this.value}`}
</div>
`;
}
// Having focus-controller on every cell can lead to performance issues
// in larger data-grids. Therefore defaulting to CSS native outline for now.
// protected get focusRingTemplate() {
// return !this.focusController.focused ? html.nothing : html`<mo-focus-ring inward visible></mo-focus-ring>`
// }
get editContentTemplate() {
return this.column.getEditContentTemplate?.(this.value, this.data);
}
};
__decorate([
property({ type: Object })
], DataGridCell.prototype, "value", void 0);
__decorate([
property({ type: Object })
], DataGridCell.prototype, "column", void 0);
__decorate([
property({ type: Object })
], DataGridCell.prototype, "row", void 0);
__decorate([
state()
], DataGridCell.prototype, "editing", void 0);
DataGridCell = __decorate([
component('mo-data-grid-cell')
], DataGridCell);
export { DataGridCell };