@nova-ui/dashboards
Version:
Nova Dashboards is a framework designed to provide feature developers with a common solution for presenting data coming from various sources within a single view, as well as a set of predefined widget visualizations that are 100% configuration-driven and
650 lines • 29 kB
JavaScript
"use strict";
// © 2022 SolarWinds Worldwide, LLC. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
Object.defineProperty(exports, "__esModule", { value: true });
exports.TableWidgetComponent = void 0;
const tslib_1 = require("tslib");
const scrolling_1 = require("@angular/cdk/scrolling");
const core_1 = require("@angular/core");
const get_1 = tslib_1.__importDefault(require("lodash/get"));
const isEqual_1 = tslib_1.__importDefault(require("lodash/isEqual"));
const omit_1 = tslib_1.__importDefault(require("lodash/omit"));
// eslint-disable-next-line import/no-deprecated
const rxjs_1 = require("rxjs");
// eslint-disable-next-line import/no-deprecated
const operators_1 = require("rxjs/operators");
const bits_1 = require("@nova-ui/bits");
const pizzagna_service_1 = require("../../pizzagna/services/pizzagna.service");
const table_formatter_registry_service_1 = require("../../services/table-formatter-registry.service");
const types_1 = require("../../services/types");
const widget_configuration_service_1 = require("../../services/widget-configuration.service");
const types_2 = require("../../types");
const paginator_feature_addon_service_1 = require("./addons/paginator-feature-addon.service");
const search_feature_addon_service_1 = require("./addons/search-feature-addon.service");
const virtual_scroll_feature_addon_service_1 = require("./addons/virtual-scroll-feature-addon.service");
const types_3 = require("./types");
/**
* @ignore
*/
let TableWidgetComponent = class TableWidgetComponent {
static { this.lateLoadKey = "TableWidgetComponent"; }
constructor(eventBus, dataSource, widgetConfigurationService, changeDetector, pizzagnaService, viewportManager, zone, el, logger, searchAddon, paginatorAddon, virtualScrollAddon, formattersRegistryService, selectorService) {
this.eventBus = eventBus;
this.dataSource = dataSource;
this.widgetConfigurationService = widgetConfigurationService;
this.changeDetector = changeDetector;
this.pizzagnaService = pizzagnaService;
this.viewportManager = viewportManager;
this.zone = zone;
this.el = el;
this.logger = logger;
this.searchAddon = searchAddon;
this.paginatorAddon = paginatorAddon;
this.virtualScrollAddon = virtualScrollAddon;
this.formattersRegistryService = formattersRegistryService;
this.selectorService = selectorService;
// 70 px stands for widget header and margins
this.indentFromTop = 70;
this.sortable = true;
this.delayedMousePresenceDetectionEnabled = true;
this.tableData = [];
this.columns = [];
this.columnsWidthMap = new Map();
this.scrollType = types_3.ScrollType.virtual;
this.isSearchEnabled = false;
this.searchTerm$ = new rxjs_1.Subject();
this.onDestroy$ = new rxjs_1.Subject();
this.tableUpdate$ = new rxjs_1.Subject();
this.mousePresent$ = new rxjs_1.BehaviorSubject(false);
this.rowHeight = 24;
this.selection = {
isAllPages: false,
include: [],
exclude: [],
};
this.totalPages = 0;
this.lastPageFetched = 0;
this.isBusy = this.lastPageFetched !== this.totalPages;
this.sortableSet = {};
this.defaultColumnAlignment = "left";
this.idle = false;
this.isSearchLimitWarningDisplayed = (0, core_1.signal)(false);
this.defaultMaxSearchLength = 2000;
this._scrollBuffer = 0;
}
get headerTooltipsEnabled() {
// Note: If tooltip state is not provided treat is as true;
return this.configuration.headerTooltipsEnabled ?? true;
}
set scrollBuffer(value) {
if (value > 100 || value < 0) {
throw new Error("Invalid scroll buffer provided. Required range 1..100");
}
this._scrollBuffer = value;
}
get range() {
return this.getTableScrollRange();
}
set range(value) {
this._range = value;
}
get interactive() {
return (this.configuration?.interactive ??
this.dataSource?.features?.getFeatureConfig(types_2.WellKnownDataSourceFeatures.Interactivity)?.enabled ??
false);
}
get clickableRow() {
const configuration = this.configuration?.selectionConfiguration;
return (!!configuration?.enabled &&
(configuration.clickableRow ||
configuration.selectionMode === bits_1.TableSelectionMode.Single));
}
get hasVirtualScroll() {
return this.scrollType === types_3.ScrollType.virtual;
}
get hasPaginator() {
return this.scrollType === types_3.ScrollType.paginator;
}
get searchLimitMaxLength() {
return this.configuration?.searchConfiguration?.maxSearchLength ?? this.defaultMaxSearchLength;
}
ngOnChanges(changes) {
if (changes.dataFields) {
if (this.dataFields?.length) {
this.setSortableSet();
}
}
if (changes.widgetData || changes.configuration || changes.dataFields) {
this.updateTable();
// Note: CDK Viewport does not have a proper mechanism to identify the changes and trigger checkViewportSize
// we have to do it manually. Using optional chaining because vscrollViewport can be unavailable at initialization.
// Related issue: https://github.com/angular/components/issues/10117
this.vscrollViewport?.checkViewportSize();
}
if (changes.configuration) {
this.resolveSortBy(changes);
this.resolveScrollType(changes);
this.resolveSearch(changes);
}
if (changes.totalItems) {
this.totalPages = Math.floor((this.totalItems ?? 0) / this.range);
}
}
resolveScrollType(changes) {
// Since removing "hasVirtualScroll" from configuration would cause breaking changes, it is used as primary source of truth
const newHasVirtualScroll = (0, get_1.default)(changes, "configuration.currentValue.hasVirtualScroll", true);
const scrollType = (0, get_1.default)(changes, "configuration.currentValue.scrollType", types_3.ScrollType.virtual);
const newScrollType = newHasVirtualScroll ? types_3.ScrollType.virtual : scrollType;
const scrollTypeActuallyChanged = newScrollType !== this.scrollType;
this.scrollType = newScrollType;
if (scrollTypeActuallyChanged) {
this.initPrefetchAddon();
}
if (this.scrollTypeChanged(changes.configuration)) {
this.dataSource.applyFilters();
}
}
ngOnInit() {
if (this.dataSource) {
// Since the sortFilter is not initialized, we have to retrieve and set the correct filter value from the configuration before registering it
this.setSortFilter();
this.registerSorter();
}
}
ngAfterViewInit() {
if (!this.dataSource) {
return;
}
this.dataSource.busy
?.pipe(
// eslint-disable-next-line import/no-deprecated
(0, operators_1.tap)((isBusy) => (this.isBusy = isBusy)), (0, operators_1.takeUntil)(this.onDestroy$))
.subscribe();
this.initPrefetchAddon();
this.searchAddon.initWidget(this);
const tableHeightChanged$ = this.eventBus
.getStream(types_1.WIDGET_RESIZE)
.pipe((0, operators_1.filter)((event) => event.payload?.widgetId?.toString() ===
this.widgetConfigurationService.getWidget().id),
// eslint-disable-next-line import/no-deprecated
(0, operators_1.map)((event) => event.payload?.height ?? 0));
// subscribing to widget resize event from dashboard and update virtual scroll viewport size
tableHeightChanged$
.pipe((0, operators_1.takeUntil)(this.onDestroy$))
.subscribe((height) => {
this.tableContainerHeight = height - this.indentFromTop;
this.vscrollViewport?.checkViewportSize();
this.changeDetector.detectChanges();
});
// Note: Secondary stream used to trigger widget ready event when table is properly displayed and we can start
// loading data for our virtual scroll
// setTimeout is needed because this.el.nativeElement.getBoundingClientRect().height is 0 when it switches components from
// fallback component to table widget and it needs to have height
setTimeout(() => {
// eslint-disable-next-line import/no-deprecated
(0, rxjs_1.merge)((0, rxjs_1.of)(this.range * this.rowHeight), tableHeightChanged$, (0, rxjs_1.of)(this.el.nativeElement.getBoundingClientRect().height))
.pipe((0, operators_1.filter)((value) => !!value), (0, operators_1.take)(1),
// eslint-disable-next-line import/no-deprecated
(0, operators_1.tap)((value) => {
this.tableWidgetHeight = value;
if (this.hasVirtualScroll) {
this.virtualScrollAddon.subscribeToVirtualScroll();
}
this.eventBus.getStream(types_1.WIDGET_READY).next({});
}))
.subscribe();
});
this.eventBus
.getStream(types_1.CHANGE_SELECTION)
.pipe((0, operators_1.takeUntil)(this.onDestroy$))
.subscribe(({ payload: selection }) => {
if (selection) {
this.selection = selection;
}
else {
this.logger.warn(`widget CHANGE_SELECTION, ${this.componentId} has received the incorrect selection object`);
}
});
}
ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
this.tableUpdate$.complete();
this.searchTerm$?.complete();
// erase the error status when this component gets destroyed to prevent an error status leak when changing data sources
this.pizzagnaService.setProperty({
componentId: "bodyContent",
propertyPath: ["fallbackKey"],
pizzagnaKey: types_2.PizzagnaLayer.Data,
}, undefined);
}
/** Checks if table should be displayed */
shouldDisplayTable() {
const columnsCondition = this.columns.length > 0;
const dataCondition = this.tableData?.length > 0;
return this.isSearchEnabled ||
(this.hasVirtualScroll && (this.isBusy || this.idle))
? columnsCondition
: columnsCondition && dataCondition;
}
dataTrackBy() {
const configuration = this?.configuration?.selectionConfiguration;
const trackByProperty = (configuration?.enabled && configuration.trackByProperty) || "id";
// we are returning a function here because the scope of "this" changes once it's passed
// to the table component and are therefore unable to access the configuration in its body
return (index, item) => {
// selection uses a set of the columns in prior of selection correct data id we need to use record
if (item["__record"]) {
item = item["__record"];
}
return item ? item[trackByProperty] : index;
};
}
columnTrackBy(index, item) {
return item.id;
}
/**
* Handles updating of columns.
* @param configuration
*/
updateColumns(configuration) {
this.headers = configuration.columns
.filter((item) => item.isActive)
.map((item) => item.id);
const allColumnsHaveWidthSpecified = configuration.columns.every((column) => Boolean(column.width));
const columns = configuration.columns.map((column, index, array) => {
this.columnsWidthMap.set(column.id, column.width);
if (allColumnsHaveWidthSpecified) {
const lastColumn = array[array.length - 1];
this.columnsWidthMap.set(lastColumn.id, undefined);
}
return {
...column,
sortable: this.sortableSet[column?.formatter?.properties?.dataFieldIds?.value] ?? true,
};
});
if (columns.length > 0 && allColumnsHaveWidthSpecified) {
this.logger.warn(`Cannot set width for all columns. Resetting last column width.`);
}
if (this.columns.length !== columns.length) {
this.columns = [...columns];
}
else {
// This Object.assign is important. It helps to track changes in column header names
this.columns.forEach((c, i) => {
Object.assign(c, columns[i], {});
});
}
}
/**
* Takes widgetData from back-end, columns which are provided in table widget config and dataFields and maps them to data-format
* which is acceptable by table component.
* Also it can merge multiple data fields into one column. If you provide more than one dataFieldId in your
* widget config, they will be merged to an object with needed data. This data can be passed to formatter and displayed
* in one column.
* @param widgetData
* @param columns
* @param dataFields
* @returns any[]
*/
mapTableData(widgetData, columns, dataFields) {
if (!dataFields || dataFields.length === 0) {
this.logger.warn("There are no data fields defined, so table data cannot be displayed.");
return [];
}
return widgetData.map((record) => {
const row = columns.reduce((result, column) => {
const dataFieldIds = column.formatter?.properties?.dataFieldIds;
if (!dataFieldIds) {
return result;
}
const data = Object.keys(dataFieldIds).reduce((mapping, next) => {
mapping[next] = record[dataFieldIds[next]];
return mapping;
}, {});
result[column.id] = {
data,
...(0, omit_1.default)(column.formatter?.properties, "dataFieldIds"),
};
return result;
}, {});
// we want to include original record for row interaction
row.__record = record;
return row;
});
}
/**
* Handles change of sorting. Gets sorted column from columns array.
* @param event
*/
onSortOrderChanged(event) {
this.sortedColumn = event;
const columnToSort = this.columns.find((column) => column.id === event.sortBy);
if (!columnToSort) {
return;
}
const dataFieldIds = columnToSort.formatter?.properties?.dataFieldIds;
const sortField = dataFieldIds?.value;
if (!sortField) {
return;
}
const newSortFilter = {
sortBy: sortField,
direction: event.direction,
};
if ((0, isEqual_1.default)(newSortFilter, this.sortFilter)) {
return;
}
this.sortFilter = newSortFilter;
// Note: Since we're relying on the CDK Viewport internal logic to perform data load
// we have to move to the start of the range for dataSource to take correct range,
// if we will keep old data user can think (NUI-5333) that data is already reloaded.
// So we're flushing the old data and waiting for new one to avoid confusion.
this.flushTableData();
this.eventBus.getStream(types_1.REFRESH).next({});
}
onSelectionChange(event) {
this.selection = event;
this.eventBus.getStream(types_1.SELECTED_ITEMS).next({
payload: this.selectorService.getSelectedItems(this.selection, this.widgetData, (a, b) => this.dataTrackBy()(0, a) === b),
id: this.componentId,
});
this.eventBus.getStream(types_1.SELECTION).next({ payload: this.selection });
}
onInteraction(row, event) {
if (!this.interactive ||
this.configuration.selectionConfiguration?.enabled) {
// allowing interactive and selection at the same time would trigger both
// events when clicking on a row (not checkbox/radio)
return;
}
const ignoredSelectors = this.configuration.interactionIgnoredSelectors ||
bits_1.DEFAULT_INTERACTIVE_ELEMENTS;
// avoid emitting events when an ignored element was clicked
if (event.target.closest(ignoredSelectors.join(","))) {
return;
}
this.eventBus
.getStream(types_1.INTERACTION)
.next({ payload: { data: row.__record } });
}
onSearchInputChanged(searchTerm) {
if (searchTerm?.length > this.searchLimitMaxLength) {
this.isSearchLimitWarningDisplayed.set(true);
return;
}
this.isSearchLimitWarningDisplayed.set(false);
this.searchValue = searchTerm;
this.searchTerm$.next("");
this.selection = {
include: [],
exclude: [],
isAllPages: false,
};
}
getColumnAlignment(column) {
// Note: In case we have provided formatters by old manner (via config)
// we don't have to proceed with calculations
if (!column?.formatter?.componentType ||
this.formattersRegistryService.isEmpty) {
return this.defaultColumnAlignment;
}
// Note: We don't want to invoke getFormattersMap() on every change detection cycle
// but only when it was changed
if (this.formatters?.version !==
this.formattersRegistryService.stateVersion) {
this.formatters = {
// Transforming array into map
items: this.formattersRegistryService.getItems().reduce((prev, next) => ({
...prev,
[next.componentType]: next,
}), {}),
version: this.formattersRegistryService.stateVersion,
};
}
return (this.formatters.items[column.formatter.componentType]?.alignment ||
this.defaultColumnAlignment);
}
setSortFilter() {
if (this.configuration) {
const sortBy = this.configuration.sorterConfiguration?.sortBy;
const columnValue = this.configuration.columns?.find((column) => column.id === sortBy)?.formatter?.properties?.dataFieldIds?.value;
this.sortFilter = {
direction: this.configuration.sorterConfiguration
?.descendantSorting
? bits_1.SorterDirection.descending
: bits_1.SorterDirection.ascending,
sortBy: columnValue,
};
}
}
/**
* Checks if column id has changed or if sorting order has changed
* @param configuration
*/
isSortByUpdated(configuration) {
const oldSorterConfiguration = configuration.previousValue?.sorterConfiguration;
const newSorterConfiguration = configuration.currentValue.sorterConfiguration;
const oldSortById = oldSorterConfiguration?.sortBy;
const newSortById = newSorterConfiguration?.sortBy;
if (!newSortById) {
return Boolean(oldSortById);
}
const equalSortingState = oldSortById === newSortById &&
newSorterConfiguration.descendantSorting ===
oldSorterConfiguration.descendantSorting;
return !equalSortingState;
}
/**
* Updates table columns and maps table data.
*/
updateTable() {
if (this.widgetData && this.dataFields && this.configuration.columns) {
this.updateColumns(this.configuration);
this.idle = false;
this.tableData = this.mapTableData(this.widgetData, this.configuration.columns, this.dataFields);
this.tableUpdate$.next();
this.changeDetector.detectChanges();
}
}
/**
* Registers sorter filter.
*/
registerSorter() {
this.dataSource.registerComponent({
sorter: {
componentInstance: {
getFilters: () => ({
type: "sorter",
value: this.sortFilter,
}),
},
},
});
}
setSortableSet() {
this.sortableSet = {};
this.dataFields.forEach((dataField) => {
this.sortableSet[dataField.id] = dataField.sortable ?? true;
});
}
flushTableData() {
this.idle = true;
this.tableData = [];
}
scrollTypeChanged(configurationChange) {
return (!!configurationChange.previousValue &&
configurationChange.previousValue.scrollType !==
configurationChange.currentValue.scrollType);
}
getTableScrollRange() {
// Note: To work properly virtual viewport should be scrollable
// to ensure that container will be scrollable we're adding 50% more items
// Ex: Viewport height: 100px, itemSize: 20px => 5 (range) in current configuration the viewport
// will not be scrollable/work properly because all the items fits the screen.
// We're adding 50% more items and in this case, we're covered even the user provides 0% buffer.
const internalBuffer = 2;
const scrollBuffer = (this._scrollBuffer ?? 0) / 100 + 1;
if (this._range) {
// Note: In case user provided a range we don't need
// to add our internal buffer. Totally relying on user calculus
return Math.floor(this._range * scrollBuffer);
}
return Math.floor((this.tableWidgetHeight / this.rowHeight) *
internalBuffer *
scrollBuffer);
}
onPagerAction(page) {
this.eventBus
.getStream(types_1.SET_NEXT_PAGE)
.next({ payload: page, id: this.componentId });
}
initPrefetchAddon() {
if (this.hasVirtualScroll) {
this.virtualScrollAddon.initVirtualScroll(this);
}
else if (this.hasPaginator) {
this.paginatorAddon.initPaginator(this);
}
else {
this.virtualScrollAddon.initVirtualScroll(this);
}
}
resolveSortBy(changes) {
// Note: We don't have to trigger sorting in case sortable flag is false.
// Note: Using true as a default sortable value to maintain backward compatibility
if (this.isSortByUpdated(changes.configuration) &&
(changes.configuration.currentValue.sortable ?? true)) {
const sortedColumn = {
direction: changes.configuration.currentValue
.sorterConfiguration.descendantSorting
? bits_1.SorterDirection.descending
: bits_1.SorterDirection.ascending,
sortBy: changes.configuration.currentValue.sorterConfiguration
.sortBy,
};
this.onSortOrderChanged(sortedColumn);
}
}
resolveSearch(changes) {
const prev = (0, omit_1.default)(changes.configuration.previousValue
?.searchConfiguration, "searchTerm");
const curr = (0, omit_1.default)(changes.configuration.currentValue
?.searchConfiguration, "searchTerm");
if (!(0, isEqual_1.default)(curr, prev)) {
this.searchAddon.initWidget(this);
}
}
};
exports.TableWidgetComponent = TableWidgetComponent;
tslib_1.__decorate([
(0, core_1.Input)(),
tslib_1.__metadata("design:type", Array)
], TableWidgetComponent.prototype, "widgetData", void 0);
tslib_1.__decorate([
(0, core_1.Input)(),
tslib_1.__metadata("design:type", String)
], TableWidgetComponent.prototype, "componentId", void 0);
tslib_1.__decorate([
(0, core_1.Input)(),
tslib_1.__metadata("design:type", Object)
], TableWidgetComponent.prototype, "configuration", void 0);
tslib_1.__decorate([
(0, core_1.Input)(),
tslib_1.__metadata("design:type", Array)
], TableWidgetComponent.prototype, "dataFields", void 0);
tslib_1.__decorate([
(0, core_1.Input)(),
tslib_1.__metadata("design:type", Number)
], TableWidgetComponent.prototype, "totalItems", void 0);
tslib_1.__decorate([
(0, core_1.Input)(),
tslib_1.__metadata("design:type", Number)
], TableWidgetComponent.prototype, "indentFromTop", void 0);
tslib_1.__decorate([
(0, core_1.Input)(),
tslib_1.__metadata("design:type", Boolean)
], TableWidgetComponent.prototype, "sortable", void 0);
tslib_1.__decorate([
(0, core_1.Input)(),
tslib_1.__metadata("design:type", Boolean)
], TableWidgetComponent.prototype, "delayedMousePresenceDetectionEnabled", void 0);
tslib_1.__decorate([
(0, core_1.Input)(),
(0, core_1.HostBinding)("class"),
tslib_1.__metadata("design:type", String)
], TableWidgetComponent.prototype, "elementClass", void 0);
tslib_1.__decorate([
(0, core_1.ViewChild)("widgetTable"),
tslib_1.__metadata("design:type", bits_1.TableComponent)
], TableWidgetComponent.prototype, "table", void 0);
tslib_1.__decorate([
(0, core_1.ViewChild)("paginator"),
tslib_1.__metadata("design:type", bits_1.PaginatorComponent)
], TableWidgetComponent.prototype, "paginator", void 0);
tslib_1.__decorate([
(0, core_1.ViewChild)(scrolling_1.CdkVirtualScrollViewport),
tslib_1.__metadata("design:type", scrolling_1.CdkVirtualScrollViewport)
], TableWidgetComponent.prototype, "vscrollViewport", void 0);
tslib_1.__decorate([
(0, core_1.ViewChildren)(bits_1.TableRowComponent, { read: core_1.ElementRef }),
tslib_1.__metadata("design:type", core_1.QueryList)
], TableWidgetComponent.prototype, "tableRows", void 0);
tslib_1.__decorate([
(0, core_1.Input)(),
tslib_1.__metadata("design:type", Number),
tslib_1.__metadata("design:paramtypes", [Number])
], TableWidgetComponent.prototype, "range", null);
exports.TableWidgetComponent = TableWidgetComponent = tslib_1.__decorate([
(0, core_1.Component)({
selector: "nui-table-widget",
templateUrl: "./table-widget.component.html",
styleUrls: ["./table-widget.component.less"],
changeDetection: core_1.ChangeDetectionStrategy.OnPush,
providers: [
search_feature_addon_service_1.SearchFeatureAddonService,
virtual_scroll_feature_addon_service_1.VirtualScrollFeatureAddonService,
paginator_feature_addon_service_1.PaginatorFeatureAddonService,
],
host: {
// Note: Moved here from configuration to ensure that consumers will not override it.
// Used to prevent table overflowing preview container in the edit/configuration mode.
"[class.table-widget-fullwidth]": "true",
},
standalone: false,
}),
tslib_1.__param(0, (0, core_1.Inject)(types_2.PIZZAGNA_EVENT_BUS)),
tslib_1.__param(1, (0, core_1.Optional)()),
tslib_1.__param(1, (0, core_1.Inject)(types_2.DATA_SOURCE)),
tslib_1.__param(2, (0, core_1.Optional)()),
tslib_1.__metadata("design:paramtypes", [bits_1.EventBus, Object, widget_configuration_service_1.WidgetConfigurationService,
core_1.ChangeDetectorRef,
pizzagna_service_1.PizzagnaService,
bits_1.VirtualViewportManager,
core_1.NgZone,
core_1.ElementRef,
bits_1.LoggerService,
search_feature_addon_service_1.SearchFeatureAddonService,
paginator_feature_addon_service_1.PaginatorFeatureAddonService,
virtual_scroll_feature_addon_service_1.VirtualScrollFeatureAddonService,
table_formatter_registry_service_1.TableFormatterRegistryService,
bits_1.SelectorService])
], TableWidgetComponent);
//# sourceMappingURL=table-widget.component.js.map