UNPKG

@3mo/data-grid

Version:
355 lines (333 loc) 11.7 kB
import { __decorate } from "tslib"; import { css, property, Component, html, query, queryAll, LitElement, live } from '@a11d/lit'; import { equals } from '@a11d/equals'; import { DirectionsByLanguage } from '@3mo/localization'; import { popover } from '@3mo/popover'; import { ContextMenu } from '@3mo/context-menu'; import { DataGridPrimaryContextMenuItem } from '../index.js'; export class DataGridRow extends Component { constructor() { super(...arguments); this.isIntersecting = true; this.delegateToCell = (method) => (e) => { const target = e.target; target?.[method]?.(e); }; } get dataGrid() { return this.dataRecord.dataGrid; } get data() { return this.dataRecord.data; } get index() { return this.dataRecord.index; } get level() { return this.dataRecord.level; } get selected() { return this.dataRecord.isSelected; } get detailsOpen() { return this.dataRecord.detailsOpen; } get detailsElement() { return this.renderRoot.querySelector('#detailsContainer')?.firstElementChild; } getCell(column) { return this.cells.find(cell => cell.column[equals](column)); } connected() { if ((this.index ?? 0) < 25) { this.isIntersecting = true; } } initialized() { this.toggleAttribute('mo-data-grid-row', true); this.dataGrid.rowIntersectionObserver?.observe(this); } disconnected() { this.dataGrid.rowIntersectionObserver?.unobserve?.(this); } updated(...parameters) { this.cells.forEach(cell => cell.requestUpdate()); this.subRows.forEach(subRow => subRow.requestUpdate()); if (this.detailsElement instanceof LitElement) { this.detailsElement.requestUpdate(); } super.updated(...parameters); } get hasDetails() { return this.dataGrid.detailsController.hasDetail(this.dataRecord); } static get styles() { return css ` :host { display: block; position: relative; height: auto; width: 100%; } #detailsExpanderContainer { position: sticky; z-index: 2; inset-inline-start: 0px; background: var(--mo-data-grid-sticky-expander-part-color, var(--mo-data-grid-sticky-part-color)); } #selectionContainer { width: var(--mo-data-grid-column-selection-width); position: sticky; z-index: 2; inset-inline-start: 0px; height: 100%; background: var(--mo-data-grid-sticky-part-color); &[data-has-details] { inset-inline-start: 20px; } } :host(:hover) { #contentContainer { --mo-data-grid-sticky-part-color: color-mix(in srgb, var(--mo-color-surface), var(--mo-color-accent) 25%) !important; color: inherit; background: var(--mo-color-accent-transparent) !important; } #contentContainer, #detailsContainer { &::before { content: ''; width: 2px; height: 100%; top: 0; inset-inline-start: 0; position: absolute; background-color: var(--mo-color-accent); z-index: 2; } } } :host([data-has-alternating-background]) { #contentContainer, #detailsContainer:not(:has([instanceof*=mo-data-grid])) { background: var(--mo-data-grid-alternating-background); --mo-data-grid-sticky-part-color: color-mix(in srgb, var(--mo-color-surface), black var(--mo-data-grid-alternating-background-transparency)); } } #contentContainer { grid-column: -1 / 1; cursor: pointer; } #contextMenuIconButtonContainer { height: 100%; place-self: end; position: sticky; inset-inline-end: 0px; z-index: 3; background: var(--mo-data-grid-sticky-part-color); } #contextMenuIconButton { opacity: 0.5; color: var(--mo-color-gray); } :host([selected]), :host([data-context-menu-open]) { #contentContainer { --mo-data-grid-sticky-part-color: color-mix(in srgb, var(--mo-color-surface), var(--mo-color-accent)) !important; background: var(--mo-data-grid-selection-background) !important; } #contextMenuIconButton { color: currentColor; opacity: 1; } } #contentContainer:hover #contextMenuIconButton { color: currentColor; opacity: 1; } #detailsExpanderIconButton { transition: transform 250ms; &[data-rtl] { transform: rotate(180deg); } } :host([detailsOpen]) #detailsExpanderIconButton { transform: rotate(90deg); } #detailsContainer { display: grid; grid-template-columns: subgrid; grid-column: -1 / 1; &:empty { display: none; } & > * { grid-column: data / -1; box-sizing: border-box; padding-inline: var(--mo-data-grid-cell-padding); padding-block: 1rem; &[mo-data-grid-row] { grid-column: -1 / 1; padding: 0; } &[instanceof*=mo-data-grid] { padding-inline: 0; --mo-data-grid-header-background: color-mix(in srgb, var(--mo-color-foreground), transparent 96%); --mo-data-grid-alternating-background: transparent; --mo-data-grid-alternating-background-transparency: 0; --_content-min-height-default: 0px; &::part(row) { border-block-end: var(--mo-data-grid-border); } } } } mo-data-grid-cell:first-of-type:not([alignment=end]), mo-data-grid-cell[alignment=end]:first-of-type + mo-data-grid-cell { margin-inline-start: calc(var(--_level, 0) * var(--mo-data-grid-column-sub-row-indentation, 20px)); } `; } get template() { this.style.setProperty('--_level', this.level.toString()); this.toggleAttribute('selected', this.dataRecord.isSelected); this.toggleAttribute('detailsOpen', this.dataRecord.detailsOpen); return !this.isIntersecting ? html.nothing : html ` <mo-grid id='contentContainer' columns='subgrid' @click=${() => this.handleContentClick()} @dblclick=${() => this.handleContentDoubleClick()} @auxclick=${(e) => e.button !== 1 ? void 0 : this.handleContentMiddleClick()} ${this.contextMenuTemplate === html.nothing ? html.nothing : popover(() => html ` <mo-context-menu @openChange=${(e) => this.handleContextMenuOpenChange(e.detail)}> ${this.contextMenuTemplate} </mo-context-menu> `)} > ${this.rowTemplate} </mo-grid> <slot id='detailsContainer'>${this.detailsOpen ? this.detailsTemplate : html.nothing}</slot> `; } get detailsExpanderTemplate() { return this.dataGrid.hasDetails === false ? html.nothing : html ` <mo-flex id='detailsExpanderContainer' justifyContent='center' alignItems='center' @click=${(e) => e.stopPropagation()} @dblclick=${(e) => e.stopPropagation()} > ${this.hasDetails === false ? html.nothing : html ` <mo-icon-button id='detailsExpanderIconButton' icon='keyboard_arrow_right' ?data-rtl=${DirectionsByLanguage.get() === 'rtl'} @click=${() => this.toggleDetails()} ></mo-icon-button> `} </mo-flex> `; } get selectionTemplate() { return !this.dataGrid.hasSelection ? html.nothing : html ` <mo-flex id='selectionContainer' justifyContent='center' alignItems='center' ?data-has-details=${this.dataGrid.hasDetails} @click=${(e) => e.stopPropagation()} @dblclick=${(e) => e.stopPropagation()} > <mo-checkbox tabindex='-1' ?disabled=${this.dataRecord.isSelectable === false} .selected=${live(this.selected)} @change=${(e) => this.dataGrid.selectionController.setSelection(this.data, e.detail, true)} ></mo-checkbox> </mo-flex> `; } getCellTemplate(column) { return column.hidden ? html.nothing : html ` <mo-data-grid-cell .row=${this} .column=${column} .value=${KeyPath.get(this.data, column.dataSelector)} @keydown=${this.delegateToCell('handleKeyDown')} @dblclick=${this.delegateToCell('handleDoubleClick')} ></mo-data-grid-cell> `; } get fillerTemplate() { return html `<span></span>`; } get contextMenuIconButtonTemplate() { return this.dataGrid.hasContextMenu === false ? html.nothing : html ` <mo-flex id='contextMenuIconButtonContainer' justifyContent='center'> <mo-icon-button id='contextMenuIconButton' icon='more_vert' dense @click=${this.openContextMenu} @dblclick=${(e) => e.stopPropagation()} ></mo-icon-button> </mo-flex> `; } get detailsTemplate() { if (!this.hasDetails) { return html.nothing; } if (this.dataGrid.getRowDetailsTemplate) { return this.dataGrid.getRowDetailsTemplate(this.data); } return !this.dataRecord.hasSubData ? html.nothing : html ` ${this.dataRecord.getSubDataByLevel(this.level + 1)?.map(data => this.dataGrid.getRowTemplate(data))} `; } handleContentClick() { if (this.dataGrid.selectOnClick) { this.dataGrid.selectionController.setSelection(this.data, true); } if (this.dataGrid.detailsOnClick && this.dataGrid.hasDetails) { this.toggleDetails(); } this.dataGrid.rowClick.dispatch(this); } async handleContentDoubleClick() { await this.clickOnPrimaryContextMenuItemIfApplicable(); this.dataGrid.rowDoubleClick.dispatch(this); } async handleContentMiddleClick() { await this.clickOnPrimaryContextMenuItemIfApplicable(); this.dataGrid.rowMiddleClick.dispatch(this); } async clickOnPrimaryContextMenuItemIfApplicable() { if (this.dataGrid.hasContextMenu === true && this.dataGrid.primaryContextMenuItemOnDoubleClick === true) { await this.openContextMenu(); ContextMenu.openInstance?.items.find(item => item instanceof DataGridPrimaryContextMenuItem && !item.disabled)?.click(); await this.closeContextMenu(); } } async openContextMenu(event) { if (this.dataGrid.hasContextMenu) { event?.stopPropagation(); this.content?.dispatchEvent(new MouseEvent('contextmenu', event)); // We need this only for testing environments, but should not be necessary. this.handleContextMenuOpenChange(true); await this.updateComplete; } } handleContextMenuOpenChange(open) { this.toggleAttribute('data-context-menu-open', open); if (this.dataRecord.isSelected === false) { this.dataGrid.select([this.data]); } } get contextMenuTemplate() { return this.dataGrid.contextMenuController.getMenuContentTemplate(!this.dataGrid.selectability || !this.dataGrid.selectedData.length ? [this.data] : this.dataGrid.selectedData); } async closeContextMenu() { ContextMenu.openInstance?.close(); await this.updateComplete; } toggleDetails() { this.dataGrid.detailsController.toggle(this.dataRecord); if (this.dataRecord.detailsOpen) { this.dataGrid.rowDetailsOpen.dispatch(this); } else { this.dataGrid.rowDetailsClose.dispatch(this); } } } __decorate([ queryAll('mo-data-grid-cell') ], DataGridRow.prototype, "cells", void 0); __decorate([ queryAll('[mo-data-grid-row]') ], DataGridRow.prototype, "subRows", void 0); __decorate([ query('#contentContainer') ], DataGridRow.prototype, "content", void 0); __decorate([ property({ type: Boolean }) ], DataGridRow.prototype, "isIntersecting", void 0); __decorate([ property({ type: Object }) ], DataGridRow.prototype, "dataRecord", void 0);