diff --git a/docs/grid-functionalities/row-detail.md b/docs/grid-functionalities/row-detail.md index ef53da9cb..d2f57c7c4 100644 --- a/docs/grid-functionalities/row-detail.md +++ b/docs/grid-functionalities/row-detail.md @@ -384,4 +384,148 @@ addNewColumn() { // you could also use SlickGrid setColumns() method // this.angularGrid.slickGrid.setColumns(cols); } +``` + +## Row Detail with Inner Grid + +You can also add an inner grid inside a Row Detail, however there are a few things to know off and remember. Any time a Row Detail is falling outside the main grid viewport, it will be unmounted and until it comes back into the viewport which is then remounted. The process of unmounting and remounting means that Row Detail previous states aren't preserved, however you could use Grid State & Presets to overcome this problem. + +##### Component + +Main Grid Component + +```ts +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { AngularGridInstance, Column, GridOption, GridState } from 'angular-slickgrid'; + +@Component({ + styleUrls: ['main-grid.component.scss'], + templateUrl: './main-grid.component.html', + encapsulation: ViewEncapsulation.None, +}) +export class MainGridComponent implements OnInit { + columnDefinitions: Column[] = []; + gridOptions!: GridOption; + angularGrid!: AngularGridInstance; + dataset: Distributor[] = []; + showSubTitle = true; + + get rowDetailInstance(): SlickRowDetailView { + return this.angularGrid.extensions.rowDetailView?.instance || {}; + } + + angularGridReady(angularGrid: AngularGridInstance) { + this.angularGrid = angularGrid; + } + + ngOnInit(): void { + this.defineGrid(); + this.dataset = this.getData(); + } + + defineGrid() { + this.columnDefinitions = [ /*...*/ ]; + this.gridOptions = { + enableRowDetailView: true, + rowSelectionOptions: { + selectActiveRow: true + }, + preRegisterExternalExtensions: (pubSubService) => { + // Row Detail View is a special case because of its requirement to create extra column definition dynamically + // so it must be pre-registered before SlickGrid is instantiated, we can do so via this option + const rowDetail = new SlickRowDetailView(pubSubService as EventPubSubService); + return [{ name: ExtensionName.rowDetailView, instance: rowDetail }]; + }, + rowDetailView: { + process: (item: any) => simulateServerAsyncCall(item), + loadOnce: false, // IMPORTANT, you can't use loadOnce with inner grid because only HTML template are re-rendered, not JS events + panelRows: 10, + preloadComponent: PreloadComponent, + viewComponent: InnerGridComponent, + }, + }; + } +} +``` + +Now, let's define our Inner Grid Component + +```html +
+

Order Details (id: {{ model.id }})

+
+ + +
+
+``` + +```ts +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { AngularGridInstance, Column, GridOption, GridState } from 'angular-slickgrid'; + +export interface Distributor { /* ... */ } +export interface OrderData { /* ... */ } + +@Component({ + templateUrl: './inner-grid.component.html', +}) +export class InnerGridComponent implements OnInit { + model!: Distributor; + innerColDefs: Column[] = []; + innerGridOptions!: GridOption; + angularGrid!: AngularGridInstance; + innerDataset: any[] = []; + innerGridId = ''; + innerGridClass = ''; + + ngOnInit(): void { + this.innerGridId = `innergrid-${this.model.id}`; + this.innerGridClass = `row-detail-${this.model.id}`; + this.defineGrid(); + this.innerDataset = [...this.model.orderData]; + } + + angularGridReady(angularGrid: AngularGridInstance) { + this.angularGrid = angularGrid; + } + + defineGrid() { + // OPTIONALLY reapply Grid State as Presets before unmounting the compoment + let gridState: GridState | undefined; + const gridStateStr = sessionStorage.getItem(`gridstate_${this.innerGridClass}`); + if (gridStateStr) { + gridState = JSON.parse(gridStateStr); + } + + this.innerColDefs = [ + { id: 'orderId', field: 'orderId', name: 'Order ID', filterable: true, sortable: true }, + { id: 'shipCity', field: 'shipCity', name: 'Ship City', filterable: true, sortable: true }, + { id: 'freight', field: 'freight', name: 'Freight', filterable: true, sortable: true, type: 'number' }, + { id: 'shipName', field: 'shipName', name: 'Ship Name', filterable: true, sortable: true }, + ]; + + this.innerGridOptions = { + autoResize: { + container: `.${this.innerGridClass}`, + }, + enableFiltering: true, + enableSorting: true, + datasetIdPropertyName: 'orderId', + presets: gridState, // reapply grid state presets + }; + } + + // OPTIONALLY save Grid State before unmounting the compoment + handleBeforeGridDestroy() { + const gridState = this.angularGrid.gridStateService.getCurrentGridState(); + sessionStorage.setItem(`gridstate_${this.innerGridClass}`, JSON.stringify(gridState)); + } +} ``` \ No newline at end of file diff --git a/package.json b/package.json index 5415c8682..6b11b71e6 100644 --- a/package.json +++ b/package.json @@ -53,13 +53,13 @@ }, "dependencies": { "@ngx-translate/core": "^15.0.0", - "@slickgrid-universal/common": "~5.12.2", - "@slickgrid-universal/custom-footer-component": "~5.12.2", - "@slickgrid-universal/empty-warning-component": "~5.12.2", - "@slickgrid-universal/event-pub-sub": "~5.12.2", - "@slickgrid-universal/pagination-component": "~5.12.2", - "@slickgrid-universal/row-detail-view-plugin": "~5.12.2", - "@slickgrid-universal/rxjs-observable": "~5.12.2", + "@slickgrid-universal/common": "~5.13.0", + "@slickgrid-universal/custom-footer-component": "~5.13.0", + "@slickgrid-universal/empty-warning-component": "~5.13.0", + "@slickgrid-universal/event-pub-sub": "~5.13.0", + "@slickgrid-universal/pagination-component": "~5.13.0", + "@slickgrid-universal/row-detail-view-plugin": "~5.13.0", + "@slickgrid-universal/rxjs-observable": "~5.13.0", "dequal": "^2.0.3", "rxjs": "^7.8.1" }, @@ -92,12 +92,12 @@ "@ngx-translate/http-loader": "^8.0.0", "@popperjs/core": "^2.11.8", "@release-it/conventional-changelog": "^10.0.0", - "@slickgrid-universal/composite-editor-component": "~5.12.2", - "@slickgrid-universal/custom-tooltip-plugin": "~5.12.2", - "@slickgrid-universal/excel-export": "~5.12.2", - "@slickgrid-universal/graphql": "~5.12.2", - "@slickgrid-universal/odata": "~5.12.2", - "@slickgrid-universal/text-export": "~5.12.2", + "@slickgrid-universal/composite-editor-component": "~5.13.0", + "@slickgrid-universal/custom-tooltip-plugin": "~5.13.0", + "@slickgrid-universal/excel-export": "~5.13.0", + "@slickgrid-universal/graphql": "~5.13.0", + "@slickgrid-universal/odata": "~5.13.0", + "@slickgrid-universal/text-export": "~5.13.0", "@types/fnando__sparkline": "^0.3.7", "@types/jest": "^29.5.14", "@types/node": "^22.13.1", diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 7f98e1963..cfb86961e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -42,6 +42,7 @@ import { GridTreeDataParentChildComponent } from './examples/grid-tree-data-pare import { Grid18Component } from './examples/grid18.component'; import { Grid43Component } from './examples/grid43.component'; import { Grid44Component } from './examples/grid44.component'; +import { Grid45Component } from './examples/grid45.component'; import { SwtCommonGridTestComponent } from './examples/swt-common-grid-test.component'; import { NgModule } from '@angular/core'; @@ -86,6 +87,7 @@ const routes: Routes = [ { path: 'range', component: GridRangeComponent }, { path: 'resize-by-content', component: GridResizeByContentComponent }, { path: 'rowdetail', component: GridRowDetailComponent }, + { path: 'rowdetail-innergrid', component: Grid45Component }, { path: 'rowmove', component: GridRowMoveComponent }, { path: 'rowspan-timesheets', component: Grid43Component }, { path: 'rowspan-large', component: Grid44Component }, diff --git a/src/app/app.component.html b/src/app/app.component.html index 7bb295a95..ce6f5b078 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -182,6 +182,9 @@ + diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 301b97b60..6cb2198e8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -58,10 +58,12 @@ import { GridTreeDataParentChildComponent } from './examples/grid-tree-data-pare import { Grid18Component } from './examples/grid18.component'; import { Grid43Component } from './examples/grid43.component'; import { Grid44Component } from './examples/grid44.component'; +import { Grid45Component } from './examples/grid45.component'; import { HomeComponent } from './examples/home.component'; import { CustomPagerComponent } from './examples/grid-custom-pager.component'; import { RowDetailPreloadComponent } from './examples/rowdetail-preload.component'; import { RowDetailViewComponent } from './examples/rowdetail-view.component'; +import { Grid45DetailComponent } from './examples/grid45-detail.component'; import { SwtCommonGridTestComponent } from './examples/swt-common-grid-test.component'; import { SwtCommonGridPaginationComponent } from './examples/swt-common-grid-pagination.component'; @@ -149,6 +151,8 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj Grid18Component, Grid43Component, Grid44Component, + Grid45Component, + Grid45DetailComponent, RowDetailPreloadComponent, RowDetailViewComponent, SwtCommonGridTestComponent, diff --git a/src/app/examples/grid45-detail.component.html b/src/app/examples/grid45-detail.component.html new file mode 100644 index 000000000..ea511cff9 --- /dev/null +++ b/src/app/examples/grid45-detail.component.html @@ -0,0 +1,14 @@ +
+

{{ model.companyName }} - Order Details (id: {{ model.id }})

+
+ + +
+
diff --git a/src/app/examples/grid45-detail.component.ts b/src/app/examples/grid45-detail.component.ts new file mode 100644 index 000000000..95c67e514 --- /dev/null +++ b/src/app/examples/grid45-detail.component.ts @@ -0,0 +1,91 @@ +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { AngularGridInstance, Column, GridOption, GridState } from '../modules/angular-slickgrid'; + +export interface Distributor { + id: number; + companyId: number; + companyName: string; + city: string; + streetAddress: string; + zipCode: string; + country: string; + orderData: OrderData[]; + isUsingInnerGridStatePresets: boolean; +} + +export interface OrderData { + orderId: string; + shipCity: string; + freight: number; + shipName: string; +} + +@Component({ + styles: ['.detail-label { display: inline-flex; align-items: center; gap: 4px; padding: 4px; }', 'label { font-weight: 600; }'], + templateUrl: './grid45-detail.component.html', + encapsulation: ViewEncapsulation.None, +}) +export class Grid45DetailComponent implements OnInit { + model!: Distributor; + innerColDefs: Column[] = []; + innerGridOptions!: GridOption; + angularGrid!: AngularGridInstance; + innerDataset: any[] = []; + innerGridId = ''; + innerGridClass = ''; + showGrid = false; + + ngOnInit(): void { + this.innerGridId = `innergrid-${this.model.id}`; + this.innerGridClass = `row-detail-${this.model.id}`; + // define the grid options & columns and then create the grid itself + this.defineGrid(); + + // mock some data (different in each dataset) + this.innerDataset = [...this.model.orderData]; + this.showGrid = true; + } + + angularGridReady(angularGrid: AngularGridInstance) { + this.angularGrid = angularGrid; + } + + defineGrid() { + // when Grid State found in Session Storage, reapply inner Grid State then reapply it as preset + let gridState: GridState | undefined; + if (this.model.isUsingInnerGridStatePresets) { + const gridStateStr = sessionStorage.getItem(`gridstate_${this.innerGridClass}`); + if (gridStateStr) { + gridState = JSON.parse(gridStateStr); + } + } + + this.innerColDefs = [ + { id: 'orderId', field: 'orderId', name: 'Order ID', filterable: true, sortable: true }, + { id: 'shipCity', field: 'shipCity', name: 'Ship City', filterable: true, sortable: true }, + { id: 'freight', field: 'freight', name: 'Freight', filterable: true, sortable: true, type: 'number' }, + { id: 'shipName', field: 'shipName', name: 'Ship Name', filterable: true, sortable: true }, + ]; + + this.innerGridOptions = { + autoResize: { + container: `.${this.innerGridClass}`, + rightPadding: 30, + minHeight: 200, + }, + enableFiltering: true, + enableSorting: true, + rowHeight: 33, + enableCellNavigation: true, + datasetIdPropertyName: 'orderId', + presets: gridState, + }; + } + + handleBeforeGridDestroy() { + if (this.model.isUsingInnerGridStatePresets) { + const gridState = this.angularGrid.gridStateService.getCurrentGridState(); + sessionStorage.setItem(`gridstate_${this.innerGridClass}`, JSON.stringify(gridState)); + } + } +} diff --git a/src/app/examples/grid45.component.html b/src/app/examples/grid45.component.html new file mode 100644 index 000000000..4ae7cd01f --- /dev/null +++ b/src/app/examples/grid45.component.html @@ -0,0 +1,97 @@ +
+

+ Example 45: Row Detail with inner Grid + + + code + + + + +

+ +
+ Add functionality to show extra information with a Row Detail View, (Wiki docs), we'll use an inner grid inside our Row Detail Component. Note that because SlickGrid uses Virtual Scroll, the rows and row + details are often be re-rendered (when row is out of viewport range) and this means unmounting Row Detail Component which + indirectly mean that all component states (dynamic elements, forms, ...) will be disposed as well, however you can use Grid + State/Presets to reapply previous state whenever the row detail gets re-rendered when back to viewport. +
+ +
+
+ +    + + + + + + + + + +
+
+ + + +
diff --git a/src/app/examples/grid45.component.scss b/src/app/examples/grid45.component.scss new file mode 100644 index 000000000..7a6fdc17a --- /dev/null +++ b/src/app/examples/grid45.component.scss @@ -0,0 +1,50 @@ +#grid45 { + --slick-cell-active-box-shadow: inset 0 0 0 1px #e35ddc; + + .slick-row.even .slick-cell.cell-very-high { + background-color: #f0ffe0; + } + .slick-row.odd .slick-cell.cell-var-span { + background-color: #87ceeb; + } + .slick-row .slick-cell.rowspan { + background-color: #95b7a2; + z-index: 10; + } + .slick-row[data-row='3'] .slick-cell.l3.rowspan { + background-color: #95b7a2; + } + .slick-row[data-row='2'] .slick-cell.l3.r5 { + background-color: #ddfffc; + } + .slick-row[data-row='0'] .slick-cell.rowspan, + .slick-row[data-row='8'] .slick-cell.rowspan { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAQ0lEQVQYV2N8/fr1fwY84M6dOwyM+BSBFKioqOBWBFMAsgSrScgKsCpCV4ChCJsCFEW4FMAV4VMAVnT8+PH/IG/iAwDA1DlezHn8bwAAAABJRU5ErkJggg==); + } + .slick-row[data-row='8'] .slick-cell.rowspan:nth-child(4) { + background: #f0ffe0; + } + .slick-row[data-row='12'] .slick-cell.rowspan { + background: #bd8b8b; + } + .slick-row[data-row='15'] .slick-cell.rowspan { + background: #edc12e; + } + .slick-row[data-row='85'] .slick-cell.rowspan { + background: #8baebd; + } + .slick-cell.active { + /* use a different active cell color to make it a bit more obvious */ + box-shadow: inset 0 0 0 1px #e35ddc; + } + .cellValue { + float: right; + font-size: 14px; + } + .valueComment { + color: #7c8983; + font-size: 12px; + font-style: italic; + width: fit-content; + } +} diff --git a/src/app/examples/grid45.component.ts b/src/app/examples/grid45.component.ts new file mode 100644 index 000000000..2eb8b4379 --- /dev/null +++ b/src/app/examples/grid45.component.ts @@ -0,0 +1,234 @@ +import { faker } from '@faker-js/faker'; +import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; +import { AngularGridInstance, Column, GridOption, SlickRowDetailView } from './../modules/angular-slickgrid'; + +import { type Distributor, Grid45DetailComponent, type OrderData } from './grid45-detail.component'; +import { RowDetailPreloadComponent } from './rowdetail-preload.component'; + +const FAKE_SERVER_DELAY = 250; +const NB_ITEMS = 995; + +@Component({ + styleUrls: ['grid45.component.scss'], + templateUrl: './grid45.component.html', + encapsulation: ViewEncapsulation.None, +}) +export class Grid45Component implements OnDestroy, OnInit { + private _darkMode = false; + columnDefinitions: Column[] = []; + gridOptions!: GridOption; + angularGrid!: AngularGridInstance; + dataset: Distributor[] = []; + detailViewRowCount = 8; + showSubTitle = true; + isUsingInnerGridStatePresets = false; + serverWaitDelay = FAKE_SERVER_DELAY; + + get rowDetailInstance(): SlickRowDetailView { + return this.angularGrid.extensions.rowDetailView?.instance || {}; + } + + angularGridReady(angularGrid: AngularGridInstance) { + this.angularGrid = angularGrid; + } + + ngOnDestroy(): void { + console.log('destroying row detail component'); + document.querySelector('.panel-wm-content')!.classList.remove('dark-mode'); + document.querySelector('#demo-container')!.dataset.bsTheme = 'light'; + } + + ngOnInit(): void { + // define the grid options & columns and then create the grid itself + this.defineGrid(); + + // mock a dataset + this.dataset = this.getData(NB_ITEMS); + } + + defineGrid() { + this.columnDefinitions = [ + { + id: 'companyId', + name: 'ID', + field: 'companyId', + cssClass: 'text-end', + minWidth: 50, + maxWidth: 50, + filterable: true, + sortable: true, + type: 'number', + }, + { + id: 'companyName', + name: 'Company Name', + field: 'companyName', + width: 90, + filterable: true, + sortable: true, + }, + { + id: 'streetAddress', + name: 'Street Address', + field: 'streetAddress', + minWidth: 120, + filterable: true, + }, + { + id: 'city', + name: 'City', + field: 'city', + minWidth: 120, + filterable: true, + }, + { + id: 'zipCode', + name: 'Zip Code', + field: 'zipCode', + minWidth: 120, + filterable: true, + }, + { + id: 'country', + name: 'Country', + field: 'country', + minWidth: 120, + filterable: true, + }, + ]; + + this.gridOptions = { + autoResize: { + container: '#demo-container', + bottomPadding: 20, + }, + autoHeight: false, + enableFiltering: true, + enableRowDetailView: true, + darkMode: this._darkMode, + rowHeight: 33, + rowDetailView: { + process: (item: any) => this.simulateServerAsyncCall(item), + loadOnce: false, // you can't use loadOnce with inner grid because only HTML template are re-rendered, not JS events + useRowClick: false, + // how many grid rows do we want to use for the row detail panel + panelRows: this.detailViewRowCount, + // optionally expose the functions that you want to use from within the row detail Child Component + parent: this, + // Preload View Template + preloadComponent: RowDetailPreloadComponent, + // ViewModel Template to load when row detail data is ready + viewComponent: Grid45DetailComponent, + }, + }; + } + + getData(count: number) { + // mock a dataset + const mockDataset: Distributor[] = []; + for (let i = 0; i < count; i++) { + mockDataset[i] = { + id: i, + companyId: i, + companyName: faker.company.name(), + city: faker.location.city(), + streetAddress: faker.location.streetAddress(), + zipCode: faker.location.zipCode('######'), + country: faker.location.country(), + orderData: [], + isUsingInnerGridStatePresets: false, + }; + } + + return mockDataset; + } + + changeDetailViewRowCount() { + if (this.angularGrid?.extensionService) { + const options = this.rowDetailInstance.getOptions(); + if (options?.panelRows) { + options.panelRows = this.detailViewRowCount; // change number of rows dynamically + this.rowDetailInstance.setOptions(options); + } + } + } + + changeUsingInnerGridStatePresets() { + this.isUsingInnerGridStatePresets = !this.isUsingInnerGridStatePresets; + this.closeAllRowDetail(); + return true; + } + + closeAllRowDetail() { + if (this.angularGrid?.extensionService) { + this.rowDetailInstance.collapseAll(); + } + } + + /** Just for demo purposes, we will simulate an async server call and return more details on the selected row item */ + simulateServerAsyncCall(item: Distributor) { + let orderData: OrderData[] = []; + // let's mock some data but make it predictable for easier Cypress E2E testing + if (item.id % 3) { + orderData = [ + { orderId: '10261', shipCity: 'Rio de Janeiro', freight: 3.05, shipName: 'Que Delícia' }, + { orderId: '10267', shipCity: 'München', freight: 208.58, shipName: 'Frankenversand' }, + { orderId: '10281', shipCity: 'Madrid', freight: 2.94, shipName: 'Romero y tomillo' }, + ]; + } else if (item.id % 4) { + orderData = [ + { orderId: '10251', shipCity: 'Lyon', freight: 41.34, shipName: 'Victuailles en stock' }, + { orderId: '10253', shipCity: 'Rio de Janeiro', freight: 58.17, shipName: 'Hanari Carnes' }, + { orderId: '10256', shipCity: 'Resende', freight: 13.97, shipName: 'Wellington Importadora' }, + ]; + } else if (item.id % 5) { + orderData = [ + { orderId: '10265', shipCity: 'Strasbourg', freight: 55.28, shipName: 'Blondel père et fils' }, + { orderId: '10277', shipCity: 'Leipzig', freight: 125.77, shipName: 'Morgenstern Gesundkost' }, + { orderId: '10280', shipCity: 'Luleå', freight: 8.98, shipName: 'Berglunds snabbköp' }, + { orderId: '10295', shipCity: 'Reims', freight: 1.15, shipName: 'Vins et alcools Chevalier' }, + ]; + } else if (item.id % 2) { + orderData = [ + { orderId: '10258', shipCity: 'Graz', freight: 140.51, shipName: 'Ernst Handel' }, + { orderId: '10270', shipCity: 'Oulu', freight: 136.54, shipName: 'Wartian Herkku' }, + ]; + } else { + orderData = [{ orderId: '10255', shipCity: 'Genève', freight: 148.33, shipName: 'Richter Supermarkt' }]; + } + + // fill the template on async delay + return new Promise((resolve) => { + window.setTimeout(() => { + const itemDetail = item; + itemDetail.orderData = orderData; + itemDetail.isUsingInnerGridStatePresets = this.isUsingInnerGridStatePresets; + // resolve the data after delay specified + resolve(itemDetail); + }, this.serverWaitDelay); + }); + } + + toggleDarkMode() { + this._darkMode = !this._darkMode; + this.toggleBodyBackground(); + this.angularGrid.slickGrid?.setOptions({ darkMode: this._darkMode }); + this.closeAllRowDetail(); + } + + toggleBodyBackground() { + if (this._darkMode) { + document.querySelector('.panel-wm-content')!.classList.add('dark-mode'); + document.querySelector('#demo-container')!.dataset.bsTheme = 'dark'; + } else { + document.querySelector('.panel-wm-content')!.classList.remove('dark-mode'); + document.querySelector('#demo-container')!.dataset.bsTheme = 'light'; + } + } + + toggleSubTitle() { + this.showSubTitle = !this.showSubTitle; + const action = this.showSubTitle ? 'remove' : 'add'; + document.querySelector('.subtitle')?.classList[action]('hidden'); + } +} diff --git a/src/app/modules/angular-slickgrid/components/__tests__/angular-slickgrid.component.spec.ts b/src/app/modules/angular-slickgrid/components/__tests__/angular-slickgrid.component.spec.ts index 706b96684..da24ca1f3 100644 --- a/src/app/modules/angular-slickgrid/components/__tests__/angular-slickgrid.component.spec.ts +++ b/src/app/modules/angular-slickgrid/components/__tests__/angular-slickgrid.component.spec.ts @@ -247,6 +247,7 @@ const mockGrid = { destroy: jest.fn(), init: jest.fn(), invalidate: jest.fn(), + invalidateRows: jest.fn(), getActiveCellNode: jest.fn(), getColumns: jest.fn(), getCellEditor: jest.fn(), @@ -1965,6 +1966,27 @@ describe('Angular-Slickgrid Custom Component instantiated via Constructor', () = expect(renderSpy).toHaveBeenCalled(); expect(updateRowSpy).toHaveBeenCalledTimes(3); }); + + it('should call invalidateRows individually when Row Detail is enabled and changed rows is different than the rows count', () => { + const renderSpy = jest.spyOn(mockGrid, 'render'); + const invalidateRowSpy = jest.spyOn(mockGrid, 'invalidateRows'); + jest.spyOn(mockGrid, 'getRenderedRange').mockReturnValue({ bottom: 10, top: 0, leftPx: 0, rightPx: 890 }); + + component.gridOptions.enableRowDetailView = true; + component.initialization(slickEventHandler); + mockDataView.onRowCountChanged.notify({ + current: 2, + previous: 0, + dataView: mockDataView, + changedRows: [1, 2], + itemCount: 5, + callingOnRowsChanged: false, + }); + + expect(component.eventHandler).toEqual(slickEventHandler); + expect(invalidateRowSpy).toHaveBeenCalled(); + expect(renderSpy).toHaveBeenCalled(); + }); }); describe('setHeaderRowVisibility grid method', () => { diff --git a/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts b/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts index d51abb7c0..bdb84e8ff 100644 --- a/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts +++ b/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts @@ -1024,8 +1024,13 @@ export class AngularSlickgridComponent implements AfterViewInit, On this.loadFilterPresetsWhenDatasetInitialized(); // When data changes in the DataView, we need to refresh the metrics and/or display a warning if the dataset is empty - this._eventHandler.subscribe(dataView.onRowCountChanged, () => { - grid.invalidate(); + this._eventHandler.subscribe(dataView.onRowCountChanged, (_e, args) => { + if (!gridOptions.enableRowDetailView || !Array.isArray(args.changedRows) || args.changedRows.length === args.itemCount) { + grid.invalidate(); + } else { + grid.invalidateRows(args.changedRows); + grid.render(); + } this.handleOnItemCountChanged(dataView.getFilteredItemCount() || 0, dataView.getItemCount() || 0); }); this._eventHandler.subscribe(dataView.onSetItemsCalled, (_e, args) => { diff --git a/src/app/modules/angular-slickgrid/extensions/__tests__/slickRowDetailView.spec.ts b/src/app/modules/angular-slickgrid/extensions/__tests__/slickRowDetailView.spec.ts index ff413fcdd..9abbc1ab0 100644 --- a/src/app/modules/angular-slickgrid/extensions/__tests__/slickRowDetailView.spec.ts +++ b/src/app/modules/angular-slickgrid/extensions/__tests__/slickRowDetailView.spec.ts @@ -52,7 +52,6 @@ const gridOptionsMock = { panelRows: 1, keyPrefix: '__', useRowClick: true, - useSimpleViewportCalc: true, saveDetailViewOnScroll: false, process: () => new Promise((resolve) => resolve('process resolved')), viewComponent: null as any, @@ -539,62 +538,19 @@ describe('SlickRowDetailView', () => { }); }); - it('should call "renderAllViewModels" when grid event "onAfterRowDetailToggle" is triggered', () => { + it('should call "redrawViewComponent" when grid event "onRowBackToViewportRange" is triggered', () => { const mockColumn = { id: 'field1', field: 'field1', width: 100, cssClass: 'red', __collapsed: true }; - const handlerSpy = jest.spyOn(plugin.eventHandler, 'subscribe'); - const getElementSpy = jest.spyOn(document.body, 'getElementsByClassName'); - const appendSpy = jest - .spyOn(angularUtilServiceStub, 'createAngularComponentAppendToDom') - .mockReturnValue({ componentRef: { instance: jest.fn(), destroy: jest.fn() } } as any); - - plugin.init(gridStub); - plugin.onAfterRowDetailToggle = new SlickEvent(); - plugin.onBeforeRowDetailToggle = new SlickEvent(); - plugin.register(); - plugin.onAfterRowDetailToggle.subscribe(() => { - expect(getElementSpy).toHaveBeenCalledWith('container_field1'); - expect(appendSpy).toHaveBeenCalledWith(TestComponent, expect.objectContaining({ className: 'container_field1' }), { - model: mockColumn, - addon: expect.anything(), - grid: gridStub, - dataView: undefined, - parent: undefined, - }); - }); - plugin.onBeforeRowDetailToggle.notify({ item: mockColumn, grid: gridStub } as any, new SlickEventData(), gridStub); - plugin.onAfterRowDetailToggle.notify({ item: mockColumn, grid: gridStub } as any, new SlickEventData(), gridStub); - - expect(handlerSpy).toHaveBeenCalled(); - }); - - it('should call "redrawViewSlot" when grid event "onRowBackToViewportRange" is triggered', () => { - const mockColumn = { id: 'field1', field: 'field1', width: 100, cssClass: 'red', __collapsed: true }; - const handlerSpy = jest.spyOn(plugin.eventHandler, 'subscribe'); - const getElementSpy = jest.spyOn(document.body, 'getElementsByClassName'); - const appendSpy = jest - .spyOn(angularUtilServiceStub, 'createAngularComponentAppendToDom') - .mockReturnValue({ componentRef: { instance: jest.fn(), destroy: jest.fn() } } as any); - const redrawSpy = jest.spyOn(plugin, 'redrawAllViewComponents'); + const redrawSpy = jest.spyOn(plugin, 'redrawViewComponent'); plugin.init(gridStub); plugin.onBeforeRowDetailToggle = new SlickEvent(); plugin.onRowBackToViewportRange = new SlickEvent(); plugin.register(); - plugin.onRowBackToViewportRange.subscribe(() => { - expect(getElementSpy).toHaveBeenCalledWith('container_field1'); - expect(appendSpy).toHaveBeenCalledWith(TestComponent, expect.objectContaining({ className: 'container_field1' }), { - model: mockColumn, - addon: expect.anything(), - grid: gridStub, - dataView: undefined, - parent: undefined, - }); - expect(redrawSpy).toHaveBeenCalled(); - }); - plugin.onBeforeRowDetailToggle.notify({ item: mockColumn, grid: gridStub } as any, new SlickEventData(), gridStub); - plugin.onRowBackToViewportRange.notify({ item: mockColumn, grid: gridStub } as any, new SlickEventData(), gridStub); + plugin.onBeforeRowDetailToggle.notify({ item: { ...mockColumn, __collapsed: false }, grid: gridStub }, new SlickEventData(), gridStub); + plugin.onBeforeRowDetailToggle.notify({ item: mockColumn, grid: gridStub }, new SlickEventData(), gridStub); + plugin.onRowBackToViewportRange.notify({ item: mockColumn, grid: gridStub, rowId: 'field1' } as any, new SlickEventData(), gridStub); - expect(handlerSpy).toHaveBeenCalled(); + expect(redrawSpy).toHaveBeenCalled(); }); it('should run the internal "onProcessing" and call "notifyTemplate" with a Promise when "process" method is defined and executed', (done) => { @@ -715,6 +671,18 @@ describe('SlickRowDetailView', () => { expect(onRowOutViewSpy).not.toHaveBeenCalled(); expect(onRowBackViewSpy).not.toHaveBeenCalled(); }); + + it('should call internal event handler subscribe and expect the "onBeforeRowOutOfViewportRange" callback to be called', () => { + const onBeforeSpy = jest.fn(); + gridOptionsMock.rowDetailView!.onBeforeRowOutOfViewportRange = onBeforeSpy; + + plugin.init(gridStub); + plugin.onBeforeRowOutOfViewportRange = new SlickEvent(); + plugin.register(); + plugin.onBeforeRowOutOfViewportRange.notify({ item: columnsMock[0], rowId: columnsMock[0].id, grid: gridStub } as any, new SlickEventData(), gridStub); + + expect(onBeforeSpy).toHaveBeenCalled(); + }); }); describe('possible error thrown', () => { diff --git a/src/app/modules/angular-slickgrid/extensions/slickRowDetailView.ts b/src/app/modules/angular-slickgrid/extensions/slickRowDetailView.ts index b08ef8620..7e65c4ee7 100644 --- a/src/app/modules/angular-slickgrid/extensions/slickRowDetailView.ts +++ b/src/app/modules/angular-slickgrid/extensions/slickRowDetailView.ts @@ -4,13 +4,13 @@ import type { OnBeforeRowDetailToggleArgs, OnRowBackToViewportRangeArgs, RxJsFacade, - SlickEventData, SlickEventHandler, SlickGrid, } from '@slickgrid-universal/common'; import { addToArrayWhenNotExists, castObservableToPromise, + SlickEventData, SlickRowSelectionModel, unsubscribeAll, } from '@slickgrid-universal/common'; @@ -28,6 +28,7 @@ export interface CreatedView { id: string | number; dataContext: any; componentRef?: ComponentRef; + rendered?: boolean; } export class SlickRowDetailView extends UniversalSlickRowDetailView { @@ -86,8 +87,12 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView { /** Dispose of all the opened Row Detail Panels Angular View Components */ disposeAllViewComponents() { - this._views.forEach((compRef) => this.disposeViewComponent(compRef)); - this._views = []; + do { + const view = this._views.pop(); + if (view) { + this.disposeView(view); + } + } while (this._views.length > 0); } /** Get the instance of the SlickGrid addon (control or plugin). */ @@ -174,7 +179,6 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView { // display preload template & re-render all the other Detail Views after toggling // the preload View will eventually go away once the data gets loaded after the "onAsyncEndUpdate" event this.renderPreloadView(); - this.renderAllViewComponents(); if (this.rowDetailViewOptions && typeof this.rowDetailViewOptions.onAfterRowDetailToggle === 'function') { this.rowDetailViewOptions.onAfterRowDetailToggle(e, args); @@ -206,6 +210,15 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView { }); } + if (this.onBeforeRowOutOfViewportRange) { + this._eventHandler.subscribe(this.onBeforeRowOutOfViewportRange, (event, args) => { + if (typeof this.rowDetailViewOptions?.onBeforeRowOutOfViewportRange === 'function') { + this.rowDetailViewOptions.onBeforeRowOutOfViewportRange(event, args); + } + this.disposeViewByItem(args.item); + }); + } + if (this.onRowOutOfViewportRange) { this.eventHandler.subscribe(this.onRowOutOfViewportRange, (e, args) => { if (this.rowDetailViewOptions && typeof this.rowDetailViewOptions.onRowOutOfViewportRange === 'function') { @@ -245,16 +258,10 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView { /** Redraw (re-render) all the expanded row detail View Components */ redrawAllViewComponents() { - this._views.forEach((compRef) => { - this.redrawViewComponent(compRef); - }); - } - - /** Render all the expanded row detail View Components */ - renderAllViewComponents() { + this.resetRenderedRows(); this._views.forEach((view) => { - if (view?.dataContext) { - this.renderViewModel(view.dataContext); + if (!view.rendered) { + this.redrawViewComponent(view); } }); } @@ -286,6 +293,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView { const containerElements = this.gridContainerElement.getElementsByClassName( `${ROW_DETAIL_CONTAINER_PREFIX}${item[this.datasetIdPropName]}` ) as HTMLCollectionOf; + if (this._viewComponent && containerElements?.length > 0) { // render row detail const componentOutput = this.angularUtilService.createAngularComponentAppendToDom( @@ -303,6 +311,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView { const viewObj = this._views.find((obj) => obj.id === item[this.datasetIdPropName]); if (viewObj) { viewObj.componentRef = componentOutput.componentRef; + viewObj.rendered = true; } return viewObj; } @@ -314,12 +323,24 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView { // protected functions // ------------------ - protected disposeViewComponent(expandedView: CreatedView): CreatedView | void { + protected disposeViewByItem(item: any, removeFromArray = false): void { + const foundViewIndex = this._views.findIndex((view: CreatedView) => view.id === item[this.datasetIdPropName]); + if (foundViewIndex >= 0 && foundViewIndex in this._views) { + const expandedView = this._views[foundViewIndex]; + this.disposeView(expandedView); + if (removeFromArray) { + this._views.splice(foundViewIndex, 1); + } + } + } + + protected disposeView(expandedView: CreatedView): CreatedView | void { const compRef = expandedView?.componentRef; if (compRef) { this.appRef.detachView(compRef.hostView); if (compRef?.destroy) { compRef.destroy(); + expandedView.rendered = false; } return expandedView; } @@ -332,7 +353,7 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView { */ protected notifyTemplate(item: any) { if (this.onAsyncResponse) { - this.onAsyncResponse.notify({ item, itemDetail: item }, undefined, this); + this.onAsyncResponse.notify({ item, itemDetail: item }, new SlickEventData(), this); } } @@ -377,27 +398,21 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView { const viewInfo: CreatedView = { id: args.item[this.datasetIdPropName], dataContext: args.item, + rendered: false, }; const idPropName = this.gridOptions.datasetIdPropertyName || 'id'; addToArrayWhenNotExists(this._views, viewInfo, idPropName); } else { // collapsing, so dispose of the View/Component - const foundViewIndex = this._views.findIndex((view: CreatedView) => view.id === args.item[this.datasetIdPropName]); - if (foundViewIndex >= 0 && foundViewIndex in this._views) { - const compRef = this._views[foundViewIndex].componentRef; - if (compRef) { - this.appRef.detachView(compRef.hostView); - compRef.destroy(); - } - this._views.splice(foundViewIndex, 1); - } + this.disposeViewByItem(args.item, true); } } /** When Row comes back to Viewport Range, we need to redraw the View */ protected handleOnRowBackToViewportRange(_e: SlickEventData, args: OnRowBackToViewportRangeArgs) { - if (args?.item) { - this.redrawAllViewComponents(); + const viewModel = Array.from(this._views).find((x) => x.id === args.rowId); + if (viewModel && !viewModel.rendered) { + this.redrawViewComponent(viewModel); } } } diff --git a/src/app/modules/angular-slickgrid/global-grid-options.ts b/src/app/modules/angular-slickgrid/global-grid-options.ts index 0dc2042f8..0f38bfb4b 100644 --- a/src/app/modules/angular-slickgrid/global-grid-options.ts +++ b/src/app/modules/angular-slickgrid/global-grid-options.ts @@ -231,7 +231,6 @@ export const GlobalGridOptions: Partial = { panelRows: 1, keyPrefix: '__', useRowClick: false, - useSimpleViewportCalc: true, saveDetailViewOnScroll: false, } as RowDetailView, rowHeight: 35, diff --git a/test/cypress/e2e/example45.cy.ts b/test/cypress/e2e/example45.cy.ts new file mode 100644 index 000000000..31ecab1d8 --- /dev/null +++ b/test/cypress/e2e/example45.cy.ts @@ -0,0 +1,430 @@ +describe('Example 45 - Row Detail with inner Grid', () => { + const rootGridTitles = ['', 'ID', 'Company Name', 'Street Address', 'City', 'Zip Code', 'Country']; + const innerGridTitles = ['Order ID', 'Ship City', 'Freight', 'Ship Name']; + const GRID_ROW_HEIGHT = 33; + let ROW_DETAIL_PANEL_COUNT = 8; + + it('should display Example title', () => { + cy.visit(`${Cypress.config('baseUrl')}/rowdetail-innergrid`); + cy.get('h2').should('contain', 'Example 45: Row Detail with inner Grid'); + }); + + it('should have exact column titles on 1st grid', () => { + cy.get('#grid45') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(rootGridTitles[index])); + }); + + it('should row detail height to 8 rows and change server delay to 40ms for faster testing', () => { + cy.get('[data-test="detail-view-row-count"]').clear().type('8'); + cy.get('[data-test="set-count-btn"]').click(); + cy.get('[data-test="server-delay"]').type('{backspace}'); + }); + + it('should open the Row Detail of the 2nd row and expect to find an inner grid with all inner column titles', () => { + cy.get('.slick-cell.detail-view-toggle:nth(1)').click().wait(40); + + cy.get('.slick-cell + .dynamic-cell-detail').find('h4').should('contain', `- Order Details (id: ${1})`); + + cy.get('#innergrid-1') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(innerGridTitles[index])); + }); + + it('should sort 2nd Row Detail inner grid "Freight" column in ascending order and filter "Ship City" with "m" and expect 2 sorted rows', () => { + cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 0); + cy.get('#innergrid-1') + .find('.slick-header-column:nth(2)') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + + cy.get('#innergrid-1 .slick-header-menu .slick-menu-command-list') + .should('be.visible') + .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-content') + .should('contain', 'Sort Ascending') + .click(); + + cy.get('#innergrid-1 .search-filter.filter-shipCity').clear().type('m*'); + + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); + + it('should open 3rd row and still expect 2nd row to be sorted and filtered', () => { + cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * (1 * (ROW_DETAIL_PANEL_COUNT + 1))}px;"] .slick-cell:nth(0)`) + .click() + .wait(40); + + cy.get('.slick-cell + .dynamic-cell-detail').find('h4').should('contain', `- Order Details (id: ${2})`); + + // 2nd row detail + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + + // 3rd row detail + cy.get('#innergrid-2 .search-filter.filter-orderId').should('have.value', ''); + cy.get('#innergrid-2 .search-filter.filter-shipCity').should('have.value', ''); + cy.get('#innergrid-2 .slick-sort-indicator-asc').should('not.exist'); + + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10261'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Rio de Janeiro'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); + + it('should go at the bottom end of the grid, then back to top and expect all Row Details to be opened but reset to default', () => { + cy.get('#grid45').type('{ctrl}{end}', { release: false }); + cy.get('#grid45').type('{ctrl}{home}', { release: false }); + cy.wait(50); + // 2nd row detail + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10261'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Rio de Janeiro'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + + // 3rd row detail + cy.get('#innergrid-2 .search-filter.filter-orderId').should('have.value', ''); + cy.get('#innergrid-2 .search-filter.filter-shipCity').should('have.value', ''); + cy.get('#innergrid-2 .slick-sort-indicator-asc').should('not.exist'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10261'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Rio de Janeiro'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); + + it('should close all rows', () => { + cy.get('[data-test="collapse-all-btn"]').click(); + }); + + it('should open 2nd row and sort inner grid "Freight" column in ascending order and filter "Order ID" and "Ship City" with "m" and expect 2 sorted rows', () => { + cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * 1}px;"] .slick-cell:nth(0)`) + .click() + .wait(40); + + cy.get('.slick-cell + .dynamic-cell-detail').find('h4').should('contain', `- Order Details (id: ${1})`); + + cy.get('#innergrid-1') + .find('.slick-header-column:nth(2)') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + + cy.get('#innergrid-1 .slick-header-menu .slick-menu-command-list') + .should('be.visible') + .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-content') + .should('contain', 'Sort Ascending') + .click(); + + cy.get('#innergrid-1 .search-filter.filter-orderId').clear().type('>102'); + cy.get('#innergrid-1 .search-filter.filter-shipCity').clear().type('m*'); + + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); + + it('should open 1st row and expect 2nd row no longer be sorted neither filtered because it has to re-rendered', () => { + cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .slick-cell:nth(0)`) + .click() + .wait(40); + + cy.get('#innergrid-1 .search-filter.filter-shipCity').should('have.value', ''); + cy.get('#innergrid-1 .slick-sort-indicator-asc').should('not.exist'); + + // default rows + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10261'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Rio de Janeiro'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); + + it('should close all rows', () => { + cy.get('[data-test="collapse-all-btn"]').click(); + }); + + it('should re-open 2nd row and sort inner grid "Freight" column in ascending order and filter "Ship City" with "m" and expect 2 sorted rows', () => { + cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * 1}px;"] .slick-cell:nth(0)`) + .click() + .wait(40); + + cy.get('.slick-cell + .dynamic-cell-detail').find('h4').should('contain', `- Order Details (id: ${1})`); + + cy.get('#innergrid-1') + .find('.slick-header-column:nth(2)') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + + cy.get('#innergrid-1 .slick-header-menu .slick-menu-command-list') + .should('be.visible') + .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-content') + .should('contain', 'Sort Ascending') + .click(); + + cy.get('#innergrid-1 .search-filter.filter-shipCity').clear().type('m*'); + + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); + + it('should scroll down when the row detail is just barely visible and then scroll back up and still expect same filters/sorting', () => { + cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 350); + cy.wait(50); + cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 0); + + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); + + it('should scroll down by 2 pages down and then scroll back up and no longer the same filters/sorting', () => { + cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 800); + cy.wait(50); + cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 0); + + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('not.contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('not.contain', 'Madrid'); + }); + + it('should close all rows and enable inner Grid State/Presets', () => { + cy.get('[data-test="collapse-all-btn"]').click(); + cy.get('[data-test="use-inner-grid-state-presets"]').click(); + }); + + it('should open again 2nd row and sort inner grid "Freight" column in ascending order & filter "Ship City" with "m" and expect 2 sorted rows', () => { + cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * 1}px;"] .slick-cell:nth(0)`) + .click() + .wait(40); + + cy.get('.slick-cell + .dynamic-cell-detail').find('h4').should('contain', `- Order Details (id: ${1})`); + + cy.get('#innergrid-1') + .find('.slick-header-column:nth(2)') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + + cy.get('#innergrid-1 .slick-header-menu .slick-menu-command-list') + .should('be.visible') + .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-content') + .should('contain', 'Sort Ascending') + .click(); + + cy.get('#innergrid-1 .search-filter.filter-shipCity').clear().type('m*'); + + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); + + it('should open again 3rd row and sort inner grid "Freight" column in ascending order & filter "Order ID" and "Ship City" with "m" and expect 2 sorted rows', () => { + cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * (1 * (ROW_DETAIL_PANEL_COUNT + 1))}px;"] .slick-cell:nth(0)`) + .click() + .wait(40); + + cy.get('.slick-cell + .dynamic-cell-detail').find('h4').should('contain', `- Order Details (id: ${2})`); + + cy.get('#innergrid-2 .slick-header-column:nth(2)') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + + cy.get('#innergrid-2 .slick-header-menu .slick-menu-command-list') + .should('be.visible') + .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-content') + .should('contain', 'Sort Ascending') + .click(); + + cy.get('#innergrid-2 .search-filter.filter-orderId').clear().type('>102'); + cy.get('#innergrid-2 .search-filter.filter-shipCity').clear().type('m*'); + + // 3rd row detail + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); + + it('should go to the bottom end of the grid and open row 987', () => { + cy.get('#grid45').type('{ctrl}{end}', { release: false }); + cy.wait(50); + cy.get('.slick-row[data-row=1001] .detail-view-toggle').first().click(); + + cy.get('#innergrid-987 .search-filter.filter-orderId').clear().type('>987'); + cy.get('.slick-empty-data-warning').should('be.visible'); + }); + + it('should go again back to top of the grid and now expect that all Row Details are still opened AND their filters/sorting are kept and reapplied', () => { + cy.get('#grid45').type('{ctrl}{home}', { release: false }); + + // 2nd row detail + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + + // 3rd row detail + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); + + it('should go back to the bottom of the grid and still expect row detail 987 to be opened with same filter and no rows inside it', () => { + cy.get('#grid45').type('{ctrl}{end}', { release: false }); + + cy.get('#innergrid-987 .search-filter.filter-orderId').clear().type('>987'); + cy.get('.slick-empty-data-warning').should('be.visible'); + }); + + it('should go back to the top of the grid once more and close 3nd row and still expect same rows in both row details', () => { + cy.get('#grid45').type('{ctrl}{home}', { release: false }); + cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * (1 * (ROW_DETAIL_PANEL_COUNT + 1))}px;"] .slick-cell:nth(0)`) + .click() + .wait(40); + + // 2nd row detail + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + + cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * (1 * (ROW_DETAIL_PANEL_COUNT + 1))}px;"] .slick-cell:nth(0)`) + .click() + .wait(40); + + // 2nd row detail + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + + // 3rd row detail + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); + + it('should change Row Detail panel height to 15, open 2nd and 3rd then execute PageDown twice', () => { + ROW_DETAIL_PANEL_COUNT = 15; + cy.get('[data-test="collapse-all-btn"]').click(); + cy.get('[data-test="detail-view-row-count"]').clear().type('15'); + cy.get('[data-test="set-count-btn"]').click(); + + cy.get('.slick-cell.detail-view-toggle:nth(1)').click().wait(40); + + // 2nd row detail + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + + // open 3rd row detail + cy.get(`.slick-row[data-row="${ROW_DETAIL_PANEL_COUNT - 1}"] .slick-cell:nth(0)`) + .click() + .wait(40); + + // 3rd row detail + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + + cy.get('#grid45').type('{pageDown}{pageDown}', { release: false }); + cy.wait(50); + cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 350); + + // expect same grid details for both grids + // 2nd row detail + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + // 3rd row detail + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); + + it('should change Row Detail panel height back to 8, open 2nd and 3rd and filter Company ID with "1..2" and expect only these 2 rows to be rendered in the grid', () => { + ROW_DETAIL_PANEL_COUNT = 8; + cy.get('[data-test="collapse-all-btn"]').click(); + cy.get('[data-test="detail-view-row-count"]').clear().type('8'); + cy.get('[data-test="set-count-btn"]').click(); + + cy.get('.slick-cell.detail-view-toggle:nth(1)').click().wait(40); + + // 2nd row detail + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + + // open 3rd row detail + cy.get(`.slick-row[data-row="9"] .slick-cell:nth(0)`).click().wait(40); + + // 3rd row detail + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + + cy.get('.search-filter.filter-companyId').type('1..2'); + cy.get('#grid45 .slick-row:not(#innergrid-1 .slick-row,#innergrid-2 .slick-row)').should( + 'have.length', + ROW_DETAIL_PANEL_COUNT * 2 + ); + cy.get('.search-filter.filter-companyId').type('{backspace}2'); + cy.get('#grid45 .slick-row:not(#innergrid-1 .slick-row,#innergrid-2 .slick-row)').should( + 'have.length', + ROW_DETAIL_PANEL_COUNT * 2 + ); + }); + + it('should clear Company ID filter and have all rows back in grid and rendered', () => { + cy.get('#grid45').find('button.slick-grid-menu-button').first().click({ force: true }); + + cy.get(`.slick-grid-menu:visible`).find('.slick-menu-item').first().find('span').contains('Clear all Filters').click(); + + cy.get('#grid45 .slick-row:not(#innergrid-1 .slick-row,#innergrid-2 .slick-row)').should( + 'have.length.greaterThan', + ROW_DETAIL_PANEL_COUNT * 2 + ); + + // 2nd row detail + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-1 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + + // 3rd row detail + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', '10281'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', '10267'); + cy.get(`#innergrid-2 [style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'München'); + }); +}); diff --git a/yarn.lock b/yarn.lock index 713059502..28e2baeb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3194,15 +3194,15 @@ resolved "https://registry.yarnpkg.com/@slickgrid-universal/binding/-/binding-5.12.2.tgz#102dc7db985dc3c52cfd13a176153f385f15a5b0" integrity sha512-o7dTmmW4DVBZi01VQHjO/cIJEH5RNz+rIrnQKwldTRoKC05vKOQTF/vuQAqTkhI7YHzFaspfvdy92oagEt5niw== -"@slickgrid-universal/common@5.12.2", "@slickgrid-universal/common@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/common/-/common-5.12.2.tgz#2fafd112f601df37e08c095f279b79a369e58be9" - integrity sha512-TLhU9FnPyqUfq276I+6AYBmgFuxNFca0JoFFYyZ2EB68vKxKVo/+EoQzJ9H5btP3xheWqatd660tHvMNvuwq8Q== +"@slickgrid-universal/common@5.13.0", "@slickgrid-universal/common@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/common/-/common-5.13.0.tgz#6971844b4b939a3b59bbea6de098d6b23d81aa02" + integrity sha512-iP1pN25uSAIoLrDnhmTt3uX+QQAL7+GPfbO/qA7zQYSP0vtP1VRtx4aA/l8g7gHvA+KMAmN+k4kybVq2LOeGFg== dependencies: "@excel-builder-vanilla/types" "^3.0.14" "@formkit/tempo" "^0.1.2" "@slickgrid-universal/binding" "5.12.2" - "@slickgrid-universal/event-pub-sub" "5.12.2" + "@slickgrid-universal/event-pub-sub" "5.13.0" "@slickgrid-universal/utils" "5.12.0" "@types/sortablejs" "^1.15.8" "@types/trusted-types" "^2.0.7" @@ -3213,101 +3213,101 @@ un-flatten-tree "^2.0.12" vanilla-calendar-pro "^2.9.10" -"@slickgrid-universal/composite-editor-component@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/composite-editor-component/-/composite-editor-component-5.12.2.tgz#317453c1ef9af9d9bb013f7243fcd988a5de936a" - integrity sha512-w2pK+tlvkOR8y75LgdfSgKQ6BBC03UBds6AEYcSbrL7bjjmJc4knIy0dwf6pwVGYoOpel+r+YqRNoDFYeRbHEg== +"@slickgrid-universal/composite-editor-component@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/composite-editor-component/-/composite-editor-component-5.13.0.tgz#d968ee28b0ba8f30b3795d6490c635cdca8ce1f0" + integrity sha512-1PU5Cf5AUnR6szMbM4Adi9s7d/bNs+93vyzPUl3X7yChyG3yr36L318NImseuLYUSG5eom5p32lEybfcFwbCvA== dependencies: "@slickgrid-universal/binding" "5.12.2" - "@slickgrid-universal/common" "5.12.2" + "@slickgrid-universal/common" "5.13.0" "@slickgrid-universal/utils" "5.12.0" -"@slickgrid-universal/custom-footer-component@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/custom-footer-component/-/custom-footer-component-5.12.2.tgz#126d0194de2ac74039e33e4dcdd264f661b3602e" - integrity sha512-aEWgqm2z1EXvl9auh2PJk7U49U2zAf2nGvPEknXd36Q5zdMLmzJjVsgS9CWFa4Y2mrJtJZ0yMoybNNnSzL55fg== +"@slickgrid-universal/custom-footer-component@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/custom-footer-component/-/custom-footer-component-5.13.0.tgz#b18479ffde9aa4b12d5ff801c28778fadf7fe968" + integrity sha512-3pFxtT686N++BqjQc1tgc8gRmmO3S+plTRKnmG2QEu4YjL44gUAnSdch0mHNfAo/wztvombw1CbtNu1UJsyolg== dependencies: "@formkit/tempo" "^0.1.2" "@slickgrid-universal/binding" "5.12.2" - "@slickgrid-universal/common" "5.12.2" + "@slickgrid-universal/common" "5.13.0" -"@slickgrid-universal/custom-tooltip-plugin@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/custom-tooltip-plugin/-/custom-tooltip-plugin-5.12.2.tgz#ef36b3e0919efa5d7e6b19397dc954a10d1a1bae" - integrity sha512-jSFX7eBxy1L8aJtEwwU9gX0zlynA1+cFPztME0GyHyjJY5EilMmOZgsCIwN3B26neRUJuN7m+DibV75WhMsMHA== +"@slickgrid-universal/custom-tooltip-plugin@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/custom-tooltip-plugin/-/custom-tooltip-plugin-5.13.0.tgz#3f4319d8d01d865c6b17077052547cc15f88c9e6" + integrity sha512-uvRI3xISJhl75cH1201a3roIJipv2mdc0I0SE2PumhR4gBg3gha3dYVbmt/XBLSD3V6qpid1fTmxYZIAwvWAwQ== dependencies: - "@slickgrid-universal/common" "5.12.2" + "@slickgrid-universal/common" "5.13.0" "@slickgrid-universal/utils" "5.12.0" -"@slickgrid-universal/empty-warning-component@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/empty-warning-component/-/empty-warning-component-5.12.2.tgz#86394d9fcaf1aaf8236c60d440fe9d2f9f49ec6f" - integrity sha512-EfFzcjp5dw5tqsZRT/eLpQuxXbVr5kmpvxg7U9dO7ms/00TzPiZEz1mQIP0pKOq8mn01QgQJh1++grjx9DIvGg== +"@slickgrid-universal/empty-warning-component@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/empty-warning-component/-/empty-warning-component-5.13.0.tgz#0c857db68692f80f077e9f1cc983960f4fd707c8" + integrity sha512-YsMCRz8dqapDSAxYuOSN+BCaiWjk1aR85eNpD00uu11DU0bE+hZbP2nqmUscerDAT+5BLF6GJzAdE+J9TjV2mQ== dependencies: - "@slickgrid-universal/common" "5.12.2" + "@slickgrid-universal/common" "5.13.0" -"@slickgrid-universal/event-pub-sub@5.12.2", "@slickgrid-universal/event-pub-sub@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/event-pub-sub/-/event-pub-sub-5.12.2.tgz#fb67f25d1214980a5f5171b38c9bcd2f82cfcc88" - integrity sha512-VgFIBN/n2fQ71usKxZcFlIfqRDk6mcwq7klug0jyreD3v73iy560GqSXW8vdddinbovGr3pzCbGYjazdkQBzRA== +"@slickgrid-universal/event-pub-sub@5.13.0", "@slickgrid-universal/event-pub-sub@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/event-pub-sub/-/event-pub-sub-5.13.0.tgz#0abb265178e0730d1d0e9faa4c748f1967bf1cc3" + integrity sha512-hjmfRw7fRoyfRgsN3ZsOiNuHmPn/+IxKJTkIhwL2HtWq2j+lP+vHfa7fqUmdMRSg/ResYYHXcF2wlCAVtln/+Q== dependencies: "@slickgrid-universal/utils" "5.12.0" -"@slickgrid-universal/excel-export@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/excel-export/-/excel-export-5.12.2.tgz#5bebf24169ad71835bf40f42941f711b3ae4aba5" - integrity sha512-JxXwxrqQjN1FfzXkt0M7cXxWJjulB0iCMTHkoCOHYKFZcLfSWidvDpqvah6xTh+KBsCqDQTD3moseL5tazLH3w== +"@slickgrid-universal/excel-export@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/excel-export/-/excel-export-5.13.0.tgz#6f87b9747c9073ba068974fa19d477dae5e293ec" + integrity sha512-6uPHhy25pxkj/XKCDghvQTnrDRsoEc1d/vsp9HDgoxbpIy7vKnBLqDAT+pnT+1n4R0nZsnyqEhnrIgV+k+d0Ew== dependencies: - "@slickgrid-universal/common" "5.12.2" + "@slickgrid-universal/common" "5.13.0" "@slickgrid-universal/utils" "5.12.0" excel-builder-vanilla "^3.0.14" -"@slickgrid-universal/graphql@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/graphql/-/graphql-5.12.2.tgz#cf7a834d8dec92b03a91e28558ae5916e8a8863b" - integrity sha512-ETKNwkG2EGl0SLAvULqvgzm30DTQAkMK6bQKE4OjjiWgOhRSIxXy2rvfp7h5SPggZgxhCgiB95Jbn88iUAmBhA== +"@slickgrid-universal/graphql@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/graphql/-/graphql-5.13.0.tgz#c8326b4b98147deed322598355627e755c25cf93" + integrity sha512-OX73GJ9bgvM0yv1EHbrel+RHIu6pDQ7TccSLs3fgtHkdWAqvJzrB54uOZRDHupgXnedajc+UkL3Exj3yBAjr/w== dependencies: - "@slickgrid-universal/common" "5.12.2" + "@slickgrid-universal/common" "5.13.0" "@slickgrid-universal/utils" "5.12.0" -"@slickgrid-universal/odata@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/odata/-/odata-5.12.2.tgz#7654cbf0841259be923f37c69e9095a620eb7d42" - integrity sha512-WvVIXoAJPhhDAoDI1Q+wWlFEhzzGYNLjMZn9TkJmCNwm1GQvVJAd8bFwjJdSWwxTm3pdv3IdNrmXXkShe+Ngvg== +"@slickgrid-universal/odata@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/odata/-/odata-5.13.0.tgz#89491508872f3b0430a90eb0a04516b5d548b2cc" + integrity sha512-ko2Wjja1S//AYrrNTSs4Bi+X3Ha6fdGt3t9RTUY36EznSp9lCORuZsXB5Y9TZTPE+URIyV2JfIiC6IyIb8Pblg== dependencies: - "@slickgrid-universal/common" "5.12.2" + "@slickgrid-universal/common" "5.13.0" "@slickgrid-universal/utils" "5.12.0" -"@slickgrid-universal/pagination-component@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/pagination-component/-/pagination-component-5.12.2.tgz#a62930d3ad64556ea678739fc98691875b8ad5e7" - integrity sha512-nC+EUZRvMYWPV8ntJ2RVagf4y1wfKqhXlkxi+m6tYF/Xa7+of5GFwi/Z4O+hta50OCWSwile1l07o9vdm3mDOw== +"@slickgrid-universal/pagination-component@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/pagination-component/-/pagination-component-5.13.0.tgz#f462437786789208a9501355a2e0e6c3bde489be" + integrity sha512-Ripo9qYvTrH/hXRHnN2OPE6vPbV3j6HSacbh6UXPFb1LWl2fHxoi49iZMrdZh7w+k2F3ElentmcjAQ3OA3FFwQ== dependencies: "@slickgrid-universal/binding" "5.12.2" - "@slickgrid-universal/common" "5.12.2" + "@slickgrid-universal/common" "5.13.0" -"@slickgrid-universal/row-detail-view-plugin@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/row-detail-view-plugin/-/row-detail-view-plugin-5.12.2.tgz#a319be4aad6f2327f7278c81897dd3207e271651" - integrity sha512-QMUVlq394m8kmz7P/e3bd5K3Wq+uC8OVdLF3DPGKyJkC0rT9MJhVxqOWhCxV2NAr3cXPtwWxbJOOHibpBQEsfQ== +"@slickgrid-universal/row-detail-view-plugin@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/row-detail-view-plugin/-/row-detail-view-plugin-5.13.0.tgz#70ff434c80e953559e4b3ebb7082e8b65dcda699" + integrity sha512-dYQYZdHSZ5QF/J13UZFZD9KzDu6z2z0E7iWe3HU9lEhR66vE+byNyM7JWfb1n8mDP/3x4agsn3QoEbKNwOPyDQ== dependencies: - "@slickgrid-universal/common" "5.12.2" + "@slickgrid-universal/common" "5.13.0" "@slickgrid-universal/utils" "5.12.0" -"@slickgrid-universal/rxjs-observable@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/rxjs-observable/-/rxjs-observable-5.12.2.tgz#e4f9fe81ab69fa36884b718598707dfab95d07c4" - integrity sha512-AxGSKgrCJrNif6YaMLdsGve4jkrNEPBe3AtkgYGKSibQcvV7FMa5IbHLmD9Z/cu4CusGyr4EWFB5murtGPHsQA== +"@slickgrid-universal/rxjs-observable@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/rxjs-observable/-/rxjs-observable-5.13.0.tgz#0389360177e91573d7c4ef3b5ac726428cd9072a" + integrity sha512-rUtNusMx4wrnCqIU3G0JrD5JQ9PKIprtKp9i1FN6tvGPBmRO3upR7EXURGquCE/DYS9m0OR+SqtX0tfhqJh5wA== dependencies: - "@slickgrid-universal/common" "5.12.2" + "@slickgrid-universal/common" "5.13.0" rxjs "^7.8.1" -"@slickgrid-universal/text-export@~5.12.2": - version "5.12.2" - resolved "https://registry.yarnpkg.com/@slickgrid-universal/text-export/-/text-export-5.12.2.tgz#faa34cd27d2f61e9d8a494ae9317aa4539c7391e" - integrity sha512-qIE2dbnvyz6mp1EOHM8sH6Dmpo47x/pqwKjbmUNq3Ry7rLTCNBoq3RZZILRZUZPJHY9Xi9pE6gKuV3Qj6aUTqQ== +"@slickgrid-universal/text-export@~5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slickgrid-universal/text-export/-/text-export-5.13.0.tgz#dc300dffec88c53dfb13d89f1fccf6ebd41c3844" + integrity sha512-innFVhDtfDyHPSgB7ibGyqL9gOhIS86duf0lfBbj1hmPP1MUgtKcK9vb0x54TlI3estNzPXp4Kf4iSHL5XZd+Q== dependencies: - "@slickgrid-universal/common" "5.12.2" + "@slickgrid-universal/common" "5.13.0" "@slickgrid-universal/utils" "5.12.0" text-encoding-utf-8 "^1.0.2"