@eclipse-scout/core
Version:
Eclipse Scout runtime
701 lines (608 loc) • 25.3 kB
text/typescript
/*
* Copyright (c) 2010, 2025 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
import {
AggregateTableControl, arrays, Column, Event, EventHandler, Filter, Group, InitModelOf, ObjectOrChildModel, ObjectOrModel, objects, Predicate, PropertyChangeEvent, scout, ScrollToOptions, Table, TableAllRowsDeletedEvent,
TableFilterAddedEvent, TableFilterRemovedEvent, TableGroupEvent, TableRow, TableRowOrderChangedEvent, TableRowsDeletedEvent, TableRowsInsertedEvent, TableRowsSelectedEvent, TableRowTileMapping, TableTileGridMediatorEventMap,
TableTileGridMediatorModel, TableUserFilter, Tile, TileAccordion, TileActionEvent, TileClickEvent, TileGrid, TileGridLayoutConfig, TileTableHierarchyFilter, Widget
} from '../index';
import $ from 'jquery';
/**
* Delegates events between the {@link Table} and it's internal {@link TileGrid}.
*/
export class TableTileGridMediator extends Widget implements TableTileGridMediatorModel {
declare model: TableTileGridMediatorModel;
declare eventMap: TableTileGridMediatorEventMap;
declare self: TableTileGridMediator;
declare parent: Table;
table: Table;
tileAccordion: TileAccordion;
tiles: Tile[];
tileMappings: TableRowTileMapping[];
/** tiles by rowId */
tilesMap: Record<string, Tile>;
/** groupId by tile */
groupForTileMap: Record<string, string>;
/** always stores the last table state before tileMode activation */
tableState: Record<string, any>;
/** properties for internal tileAccordion */
exclusiveExpand: boolean;
gridColumnCount: number;
tileGridLayoutConfig: TileGridLayoutConfig;
withPlaceholders: boolean;
protected _isUpdatingTiles: boolean;
protected _tableHierarchyFilter: TileTableHierarchyFilter;
protected _tileFilters: MediatedTableTileFilter[];
protected _destroyHandler: () => void;
protected _tileAccordionPropertyChangeHandler: EventHandler<PropertyChangeEvent<any, TileAccordion>>;
protected _tileAccordionActionHandler: EventHandler<TileActionEvent<TileAccordion>>;
protected _tileAccordionClickHandler: EventHandler<TileClickEvent<TileAccordion>>;
protected _tableFilterAddedHandler: EventHandler<TableFilterAddedEvent>;
protected _tableFilterRemovedHandler: EventHandler<TableFilterRemovedEvent>;
protected _tableFilterHandler: EventHandler<Event<Table>>;
protected _tableGroupHandler: EventHandler<TableGroupEvent>;
protected _tableRowsSelectedHandler: EventHandler<TableRowsSelectedEvent>;
protected _tableRowsInsertedHandler: EventHandler<TableRowsInsertedEvent>;
protected _tableRowsDeletedHandler: EventHandler<TableRowsDeletedEvent>;
protected _tableAllRowsDeletedHandler: EventHandler<TableAllRowsDeletedEvent>;
protected _tableRowOrderChangedHandler: EventHandler<TableRowOrderChangedEvent>;
protected _tablePropertyChangeHandler: EventHandler<PropertyChangeEvent>;
constructor() {
super();
this.table = null;
this.tileAccordion = null;
this.tiles = [];
this.tileMappings = [];
this.tilesMap = {};
this.groupForTileMap = {};
this.tableState = {};
this.exclusiveExpand = false;
this.gridColumnCount = null;
this.tileGridLayoutConfig = null;
this.withPlaceholders = null;
this._isUpdatingTiles = false;
this._tableHierarchyFilter = null;
this._tileFilters = [];
this._destroyHandler = this._uninstallListeners.bind(this);
this._tileAccordionPropertyChangeHandler = this._onTileAccordionPropertyChange.bind(this);
this._tileAccordionActionHandler = this._onTileAccordionAction.bind(this);
this._tileAccordionClickHandler = this._onTileAccordionClick.bind(this);
this._tableFilterAddedHandler = this._onTableFilterAdded.bind(this);
this._tableFilterRemovedHandler = this._onTableFilterRemoved.bind(this);
this._tableFilterHandler = this._onTableFilter.bind(this);
this._tableGroupHandler = this._onTableGroup.bind(this);
this._tableRowsSelectedHandler = this._onTableRowsSelected.bind(this);
this._tableRowsInsertedHandler = this._onTableRowsInserted.bind(this);
this._tableRowsDeletedHandler = this._onTableRowsDeleted.bind(this);
this._tableAllRowsDeletedHandler = this._onTableAllRowsDeleted.bind(this);
this._tableRowOrderChangedHandler = this._onTableRowOrderChangedHandler.bind(this);
this._tablePropertyChangeHandler = this._onTablePropertyChange.bind(this);
this._addWidgetProperties(['tileAccordion', 'tiles', 'tileMappings']);
}
override init(model: InitModelOf<this>) {
super._init(model);
this.table = this.parent;
this._setTileGridLayoutConfig(this.tileGridLayoutConfig);
if (!this.tileAccordion) {
this.tileAccordion = this._createTileAccordion();
this._installListeners();
}
this.tableState.headerVisible = this.table.headerVisible;
this._setTiles(this.tiles);
this._setTileMappings(this.tileMappings);
}
protected _installListeners() {
this.tileAccordion.on('propertyChange', this._tileAccordionPropertyChangeHandler);
this.tileAccordion.on('tileAction', this._tileAccordionActionHandler);
this.tileAccordion.on('tileClick', this._tileAccordionClickHandler);
this.table.on('filterAdded', this._tableFilterAddedHandler);
this.table.on('filterRemoved', this._tableFilterRemovedHandler);
this.table.on('filter', this._tableFilterHandler);
this.table.on('group', this._tableGroupHandler);
this.table.on('rowsSelected', this._tableRowsSelectedHandler);
this.table.on('rowsInserted', this._tableRowsInsertedHandler);
this.table.on('rowsDeleted', this._tableRowsDeletedHandler);
this.table.on('allRowsDeleted', this._tableAllRowsDeletedHandler);
this.table.on('rowOrderChanged', this._tableRowOrderChangedHandler);
this.table.on('propertyChange', this._tablePropertyChangeHandler);
this.tileAccordion.on('destroy', this._destroyHandler);
this.table.on('destroy', this._destroyHandler);
}
protected _uninstallListeners() {
this.tileAccordion.off('propertyChange', this._tileAccordionPropertyChangeHandler);
this.tileAccordion.off('tileAction', this._tileAccordionActionHandler);
this.tileAccordion.off('tileClick', this._tileAccordionClickHandler);
this.table.off('filterAdded', this._tableFilterAddedHandler);
this.table.off('filterRemoved', this._tableFilterRemovedHandler);
this.table.off('filter', this._tableFilterHandler);
this.table.off('group', this._tableGroupHandler);
this.table.off('rowsSelected', this._tableRowsSelectedHandler);
this.table.off('rowsInserted', this._tableRowsInsertedHandler);
this.table.off('rowsDeleted', this._tableRowsDeletedHandler);
this.table.off('allRowsDeleted', this._tableAllRowsDeletedHandler);
this.table.off('rowOrderChanged', this._tableRowOrderChangedHandler);
this.table.off('propertyChange', this._tablePropertyChangeHandler);
this.tileAccordion.off('destroy', this._destroyHandler);
this.table.off('destroy', this._destroyHandler);
}
setGridColumnCount(gridColumnCount: number) {
this.setProperty('gridColumnCount', gridColumnCount);
if (this.tileAccordion) {
this.tileAccordion.setGridColumnCount(gridColumnCount);
}
}
setTileGridLayoutConfig(tileGridLayoutConfig: ObjectOrModel<TileGridLayoutConfig>) {
this.setProperty('tileGridLayoutConfig', tileGridLayoutConfig);
}
protected _setTileGridLayoutConfig(tileGridLayoutConfig: ObjectOrModel<TileGridLayoutConfig>) {
tileGridLayoutConfig = TileGridLayoutConfig.ensure(tileGridLayoutConfig);
this._setProperty('tileGridLayoutConfig', tileGridLayoutConfig);
if (this.tileAccordion) {
this.tileAccordion.setTileGridLayoutConfig(tileGridLayoutConfig);
}
}
setWithPlaceholders(withPlaceholders: boolean) {
this.setProperty('withPlaceholders', withPlaceholders);
if (this.tileAccordion) {
this.tileAccordion.setWithPlaceholders(withPlaceholders);
}
}
protected _setTileMappings(tableRowTileMappings: TableRowTileMapping[]) {
this._setProperty('tileMappings', tableRowTileMappings);
if (!tableRowTileMappings) {
return;
}
let tiles = tableRowTileMappings.map(this.resolveMapping, this);
this._setTiles(tiles);
}
setTiles(tiles: ObjectOrChildModel<Tile>[]) {
this.setProperty('tiles', tiles);
}
protected _setTiles(tiles: Tile[]) {
this._isUpdatingTiles = true;
// remove all new tiles from this.tiles to prevent reused tiles from being destroyed in reset()
arrays.removeAll(this.tiles, tiles);
this.reset();
this._setTilesInternal(tiles);
this._isUpdatingTiles = false;
}
protected _setTilesInternal(tiles: Tile[]) {
// check if all tiles are already available in the table
let tableRowMissing = tiles.some(tile => this.table.rowsMap[tile.rowId] === undefined);
if (tableRowMissing) {
if (this.table.initialized) {
// wait for next insertRows event on the table to execute this function again
this.table.one('rowsInserted', this._setTilesInternal.bind(this, tiles));
} else {
// if table is not initialized already wait for the init event
this.table.one('init', this._setTilesInternal.bind(this, tiles));
}
return;
}
this._refreshTilesMap(tiles);
// create simplified grouping for tile accordion, grouping on the table can be left as is.
this._initGroups(tiles);
this._setProperty('tiles', tiles);
this.tileAccordion.setTiles(this.tiles);
this._updateGroupVisibility();
this._syncSelectionFromTableToTile();
}
// only used in ScoutJS, see TableAdapter.modifyTablePrototype()
loadTiles() {
// hierarchy is not supported in tile mode. There is no way to visualize a parent-child hierarchy in the tileGrid. Therefore, only top level rows are displayed.
let rows = this.table.rows.filter(row => !row.parentRow);
let tiles = this.table.createTiles(rows);
if (tiles) {
this.setTiles(tiles);
}
}
resolveMapping(tableRowTileMapping: TableRowTileMapping): Tile {
let tile = tableRowTileMapping.tile;
tile.rowId = tableRowTileMapping.tableRow;
tile.setParent(this);
tile.setOwner(this);
return tile;
}
// update tilesMap with the given tiles or recreate tilesMap completely in case of null given
protected _refreshTilesMap(tiles: Tile[]) {
if (!tiles) {
tiles = this.tiles;
this.tilesMap = {};
}
tiles.forEach(tile => {
this.tilesMap[tile.rowId] = tile;
});
}
getTilesForRows(rows: TableRow[]): Tile[] {
return rows
.map(row => this.tilesMap[row.id])
.filter(t => !!t);
}
protected _initGroups(tiles: Tile[]) {
let primaryGroupingColumn = arrays.find(this.table.columns, column => column.grouped && column.sortIndex === 0);
tiles.forEach(tile => {
let row = this.table.rowsMap[tile.rowId];
let groupId = primaryGroupingColumn ? primaryGroupingColumn.cellTextForGrouping(row) : 'default';
groupId = scout.nvl(groupId, ''); // use empty group to avoid NPE
this.groupForTileMap[row.id] = groupId;
// check if group already exists, otherwise create it
let group = this.tileAccordion.getGroupById(groupId);
if (!group) {
group = this._createTileGroup(groupId, primaryGroupingColumn, row);
this._adaptTileGrid(group.body);
this.tileAccordion.insertGroup(group);
}
tile.parent = group;
});
}
protected _adaptTileGrid(tileGrid: TileGrid) {
// The table contains the menu items -> pass them to the showContextMenu function of the tileGrid.
objects.mandatoryFunction(tileGrid, '_showContextMenu');
let origShowContextMenu = tileGrid._showContextMenu;
tileGrid._showContextMenu = options => {
objects.mandatoryFunction(this.table, '_filterMenusForContextMenu');
options.menuItems = this.table._filterMenusForContextMenu();
scout.assertProperty(this.table, '_filterMenusHandler');
options.menuFilter = this.table._filterMenusHandler;
origShowContextMenu.call(tileGrid, options);
};
// Use the table's keyStrokeContext $bindTarget for each tileGrid as well, to ensure that in tileMode the tileGrids
// keyStrokes work when the focus is in the tableHeader/Footer (or just outside the actual TileGrid).
// Adapt all keyStrokes to stop propagation. This is necessary since several tileGrids now use the same $bindTarget and the keyStroke
// should only be executed once. The TileGridSelectionHandler takes care of the correct selection behaviour.
tileGrid.keyStrokeContext.$bindTarget = this.table.keyStrokeContext.$bindTarget;
tileGrid.keyStrokeContext.keyStrokes.forEach(ks => {
ks.stopPropagation = true;
ks.stopImmediatePropagation = true;
});
}
protected _createTileAccordion(): TileAccordion {
return scout.create(TileAccordion, {
parent: this.table,
virtual: true,
selectable: true,
multiSelect: this.table.multiSelect,
exclusiveExpand: this.exclusiveExpand,
gridColumnCount: this.gridColumnCount,
tileGridLayoutConfig: this.tileGridLayoutConfig,
withPlaceholders: this.withPlaceholders
});
}
protected _createTileGroup(groupId: string, primaryGroupingColumn: Column<any>, row: TableRow): Group<TileGrid> {
let htmlEnabled: boolean, title: string, iconId: string;
if (primaryGroupingColumn) {
htmlEnabled = primaryGroupingColumn.htmlEnabled;
let cell = primaryGroupingColumn.createAggrGroupCell(row);
title = cell.text;
iconId = cell.iconId;
}
return scout.create((Group<TileGrid>), {
parent: this.tileAccordion,
id: groupId,
headerVisible: groupId !== 'default',
title: title,
titleHtmlEnabled: htmlEnabled,
iconId: iconId,
body: {
objectType: TileGrid,
scrollable: false
}
});
}
activate() {
this.tableState.headerVisible = this.table.headerVisible;
this.table.setHeaderVisible(false);
if (this.table.tileTableHeader) {
this.table.tileTableHeader.setVisible(true);
}
// hide aggregation table control
this.table.tableControls.forEach(control => {
if (control instanceof AggregateTableControl) {
this.tableState.aggregateTableControlSelected = control.selected;
control.setSelected(false, {closeWhenUnselected: true, animate: false});
control.setVisible(false);
}
});
// check if there exists a hierarchy within the tableRows
let hasHierarchy = arrays.find(this.table.rows, row => !!row.parentRow) !== null;
if (hasHierarchy) {
// add the hierarchyFilter since the tileMode doesn't support hierarchy
this._tableHierarchyFilter = scout.create(TileTableHierarchyFilter, {
table: this.table
});
this.table.addFilter(this._tableHierarchyFilter);
}
// doesn't depend upon any tile data, therefore execute on activation
this._syncFiltersFromTableToTile();
this._syncScrollTopFromTableToTile();
}
deactivate() {
// show aggregation table control
this.table.tableControls.forEach(control => {
if (control instanceof AggregateTableControl) {
control.setVisible(true);
}
});
// use _setProperty to avoid instant rendering, render manually later on (this is necessary since TableHeader depends upon table.$data)
this.table._setProperty('headerVisible', this.tableState.headerVisible);
if (this.table.tileTableHeader) {
this.table.tileTableHeader.setVisible(false);
}
if (this._tableHierarchyFilter) {
this.table.removeFilter(this._tableHierarchyFilter);
this._tableHierarchyFilter = null;
}
this._syncScrollTopFromTileGridToTable();
// complete reset
this.reset();
}
reset() {
this.tilesMap = {};
this.groupForTileMap = {};
this.tileAccordion.deleteAllTiles();
this.tileAccordion.deleteAllGroups();
// destroy tiles manually since owner is the mediator thus the tileGrid can't destroy them
this.tiles.forEach(tile => tile.destroy());
}
renderTileMode() {
if (this.table.tileMode) {
// if the table was previously in tileMode this is not necessary...
if (this.table.$data) {
this.table._removeData();
}
this._renderTileTableHeader();
this._renderTileAccordion();
} else {
this._removeTileTableHeader();
this._removeTileAccordion();
this.table._renderData();
this.table._renderTableHeader();
// restore selected state of the aggregationTableControl here since it depends on table.$data
if (this.tableState.aggregateTableControlSelected) {
arrays.find(this.table.tableControls, control => control instanceof AggregateTableControl).setSelected(true);
}
}
this.table._refreshMenuBarPosition();
this.table.loadingSupport.remove();
this.table.loadingSupport.renderLoading(true);
}
override destroy() {
// destroy tiles manually since owner is the mediator thus the tileGrid can't destroy them
this.tiles.forEach(tile => tile.destroy());
this.tileAccordion.destroy();
this.tileAccordion = null;
}
insertTiles(tiles: Tile[] | Tile) {
tiles = arrays.ensure(tiles);
if (tiles.length === 0) {
return;
}
this.setTiles(this.tiles.concat(tiles));
}
deleteTiles(tiles?: Tile[]) {
if (!tiles) {
tiles = this.tiles.slice();
}
arrays.removeAll(this.tiles, tiles);
tiles.forEach(tile => {
delete this.tilesMap[tile.rowId];
delete this.groupForTileMap[tile.rowId];
let group = this.tileAccordion.getGroupByTile(tile);
if (group) {
// if there's only one left remove the group (tile is removed later)
if (group.body.tiles.length === 1) {
this.tileAccordion.deleteGroup(group);
}
}
tile.destroy();
});
this.tileAccordion.deleteTiles(tiles);
}
protected _onTileAccordionPropertyChange(event: PropertyChangeEvent<any, TileAccordion>) {
if (!this.table.tileMode) {
return;
}
if (event.propertyName === 'selectedTiles') {
this._syncSelectionFromTileGridToTable(event.source.getSelectedTiles());
if (this.tileAccordion.rendered) {
// Depending on the tiles content, selecting tiles with shift can lead to a mix of selecting the tiles content
// and the tiles itself, which doesn't look nice. Remove the text selection when selection tiles to avoid this.
this.tileAccordion.$container.document(true).getSelection().removeAllRanges();
}
} else if (event.propertyName === 'filteredTiles') {
this._updateGroupVisibility();
}
}
protected _onTileAccordionAction(event: TileActionEvent<TileAccordion>) {
if (!this.table.tileMode) {
return;
}
this.table.doRowAction(this.table.rowsMap[event.tile.rowId]);
}
protected _onTileAccordionClick(event: TileClickEvent<TileAccordion>) {
if (!this.table.tileMode) {
return;
}
this.table._triggerRowClick(event.originalEvent, this.table.rowsMap[event.tile.rowId], event.mouseButton);
}
protected _onTableRowsSelected(event: TableRowsSelectedEvent) {
if (!this.table.tileMode) {
return;
}
this._syncSelectionFromTableToTile();
}
protected _onTableRowsInserted(event: TableRowsInsertedEvent) {
if (!this.table.tileMode) {
return;
}
this.insertTiles(this.table.createTiles(event.rows));
}
protected _onTableRowsDeleted(event: TableRowsDeletedEvent) {
if (!this.table.tileMode) {
return;
}
this.deleteTiles(this.getTilesForRows(event.rows));
}
protected _onTableAllRowsDeleted(event: TableAllRowsDeletedEvent) {
if (!this.table.tileMode) {
return;
}
this.deleteTiles();
}
protected _onTableRowOrderChangedHandler(event: TableRowOrderChangedEvent) {
// ignore event when not in tileMode or when this.tilesMap is not (yet) initialized correctly
if (!this.table.tileMode || $.isEmptyObject(this.tilesMap)) {
return;
}
this.tiles = this.table.rows.map(row => this.tilesMap[row.id]);
this.tileAccordion.setTiles(this.tiles);
}
/** @internal */
_onTableGroup(event?: TableGroupEvent) {
if (!this.table.tileMode) {
return;
}
this.tileAccordion.deleteAllGroups();
this._initGroups(this.tiles);
this.tileAccordion.setTiles(this.tiles);
}
protected _onTableFilterAdded(event: TableFilterAddedEvent) {
if (!this.table.tileMode) {
return;
}
this._addFilter(event.filter);
}
protected _onTableFilterRemoved(event: TableFilterRemovedEvent) {
if (!this.table.tileMode) {
return;
}
this._removeFilterByPredicate(f => f.tableFilter === event.filter);
}
protected _addFilter(tableFilter: Filter<TableRow>) {
let tileFilter = {
tableFilter: tableFilter,
table: this.table,
accept: (tile: Tile) => {
let rowForTile = this.table.rowsMap[tile.rowId];
if (rowForTile) {
return tableFilter.accept(rowForTile);
}
return false;
}
};
this._removeFilterByPredicate(f => f.tableFilter === tableFilter, false);
if (tableFilter instanceof TableUserFilter) {
let key = tableFilter.createKey();
this._removeFilterByPredicate(f => f.tableFilter instanceof TableUserFilter && objects.equals(f.tableFilter.createKey(), key), false);
}
this._tileFilters.push(tileFilter);
this.tileAccordion.addFilter(tileFilter);
}
protected _removeFilterByPredicate(predicate: Predicate<MediatedTableTileFilter>, applyFilter = true) {
let filter = this._tileFilters.find(predicate);
if (filter) {
this.tileAccordion.removeFilter(filter, applyFilter);
arrays.remove(this._tileFilters, filter);
}
}
protected _onTableFilter(event: Event<Table>) {
if (!this.table.tileMode) {
return;
}
this.tileAccordion.filter();
}
protected _syncSelectionFromTableToTile() {
if (this.tileAccordion) {
this.tileAccordion.selectTiles(this.getTilesForRows(this.table.selectedRows));
}
}
protected _syncSelectionFromTileGridToTable(selectedTiles: Tile[]) {
if (!this._isUpdatingTiles) {
let selectedRows = selectedTiles
.map(tile => this.table.rowsMap[tile.rowId])
.filter(t => Boolean(t));
this.table.selectRows(selectedRows);
}
}
protected _updateGroupVisibility() {
this.tileAccordion.groups.forEach(group => {
// Make groups invisible if a tile filter is active and no tiles match (= no tiles are visible)
let body = group.body;
let groupEmpty = body.filters.length > 0 && body.filteredTiles.length === 0;
group.setVisible(!groupEmpty);
group.setTitleSuffix(body.filteredTiles.length + '');
});
}
protected _syncScrollTopFromTableToTile() {
let rowIndex = this.table._rowIndexAtScrollTop(this.table.scrollTop);
if (rowIndex <= 0) {
return;
}
let tile = this.tilesMap[this.table.rows[rowIndex].id];
if (!tile) {
return;
}
// reset scrollTop on tileAccordion, otherwise it would overwrite the synced scrollTop
this.tileAccordion.scrollTop = null;
let options: ScrollToOptions = {
align: 'top'
};
if (!tile.rendered) {
// Execute delayed because table may be not layouted yet
this.table.session.layoutValidator.schedulePostValidateFunction(tile.reveal.bind(tile, options));
return;
}
tile.reveal(options);
}
protected _syncScrollTopFromTileGridToTable() {
let tile = this.tileAccordion.tileAtScrollTop(this.tileAccordion.scrollTop);
if (tile) {
let options: ScrollToOptions = {
align: 'top'
};
if (!this.table._isDataRendered()) {
this.table.session.layoutValidator.schedulePostValidateFunction(this.table.scrollTo.bind(this.table, this.table.rowsMap[tile.rowId], options));
} else {
this.table.scrollTo(this.table.rowsMap[tile.rowId], options);
}
}
}
protected _syncFiltersFromTableToTile() {
if (this.tileAccordion) {
this.tileAccordion.setFilters([]);
this.table.filters.forEach(tableFilter => this._addFilter(tableFilter));
}
}
protected _onTablePropertyChange(event: PropertyChangeEvent) {
if (event.propertyName === 'multiSelect') {
this.tileAccordion.setMultiSelect(event.newValue as boolean);
}
}
protected _renderTileTableHeader() {
if (this.table.tileTableHeader) {
this.table.tileTableHeader.render();
}
}
protected _removeTileTableHeader() {
if (this.table.tileTableHeader) {
this.table.tileTableHeader.remove();
}
}
protected _renderTileAccordion() {
if (!this.tileAccordion.rendered) {
this.tileAccordion.render();
}
}
protected _removeTileAccordion() {
if (this.tileAccordion.rendered) {
this.tileAccordion.remove();
}
}
}
export type MediatedTableTileFilter = Filter<Tile> & { tableFilter: Filter<TableRow> };