diff --git a/src/app/examples/grid-base-row-editing.component.ts b/src/app/examples/grid-base-row-editing.component.ts index 1f07c44d7..bf989fc92 100644 --- a/src/app/examples/grid-base-row-editing.component.ts +++ b/src/app/examples/grid-base-row-editing.component.ts @@ -1,6 +1,6 @@ - import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { SlickCustomTooltip } from '@slickgrid-universal/custom-tooltip-plugin'; import { Subscription } from 'rxjs'; import { @@ -198,6 +198,7 @@ export class GridBaseRowEditingComponent implements OnInit { deleteButtonPrompt: 'Are you sure you want to delete this row?', }, }, + externalResources: [new SlickCustomTooltip()], }; } diff --git a/src/app/examples/grid-composite-editor.component.ts b/src/app/examples/grid-composite-editor.component.ts index 1cc4c0f8f..f1ba71401 100644 --- a/src/app/examples/grid-composite-editor.component.ts +++ b/src/app/examples/grid-composite-editor.component.ts @@ -1,6 +1,7 @@ import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { ExcelExportService } from '@slickgrid-universal/excel-export'; +import { SlickCustomTooltip } from '@slickgrid-universal/custom-tooltip-plugin'; import { SlickCompositeEditor, SlickCompositeEditorComponent } from '@slickgrid-universal/composite-editor-component'; import { @@ -64,7 +65,7 @@ const customEditableInputFormatter: Formatter = (_row, _cell, value, columnDef, const gridOptions = grid && grid.getOptions && grid.getOptions(); const isEditableLine = gridOptions.editable && columnDef.editor; value = (value === null || value === undefined) ? '' : value; - return isEditableLine ? { text: value, addClasses: 'editable-field', toolTip: 'Click to Edit' } : value; + return isEditableLine ? { text: value, addClasses: 'editable-field' } : value; }; // you can create custom validator to pass to an inline editor @@ -131,7 +132,8 @@ export class GridCompositeEditorComponent implements OnDestroy, OnInit { prepareGrid() { this.columnDefinitions = [ { - id: 'title', name: 'Title', field: 'title', sortable: true, type: FieldType.string, minWidth: 75, + id: 'title', name: ' Title ', + field: 'title', sortable: true, type: FieldType.string, minWidth: 75, cssClass: 'text-uppercase fw-bold', columnGroup: 'Common Factor', filterable: true, filter: { model: Filters.compoundInputText }, editor: { @@ -397,7 +399,7 @@ export class GridCompositeEditorComponent implements OnDestroy, OnInit { excelExportOptions: { exportWithFormatter: false }, - externalResources: [new ExcelExportService(), this.compositeEditorInstance], + externalResources: [new ExcelExportService(), new SlickCustomTooltip(), this.compositeEditorInstance], enableFiltering: true, rowSelectionOptions: { // True (Single Selection), False (Multiple Selections) diff --git a/src/app/examples/grid-custom-tooltip.component.scss b/src/app/examples/grid-custom-tooltip.component.scss index 86e6ab70a..62073ee39 100644 --- a/src/app/examples/grid-custom-tooltip.component.scss +++ b/src/app/examples/grid-custom-tooltip.component.scss @@ -35,6 +35,9 @@ $slick-button-border-color: #ababab !default; // it's preferable to use CSS Variables (or SASS) but if you want to change colors of your tooltip for 1 column in particular you can do it this way // e.g. change css of 5th column 4 (zero index: l4) +.l4 { + --slick-tooltip-color: #fff; +} .l4 .header-tooltip-title, .l4 .headerrow-tooltip-title { color: #ffffff; diff --git a/src/app/examples/grid-custom-tooltip.component.ts b/src/app/examples/grid-custom-tooltip.component.ts index 0b95d8bd6..6d757b4f3 100644 --- a/src/app/examples/grid-custom-tooltip.component.ts +++ b/src/app/examples/grid-custom-tooltip.component.ts @@ -110,6 +110,7 @@ export class GridCustomTooltipComponent implements OnInit { // define tooltip options here OR for the entire grid via the grid options (cell tooltip options will have precedence over grid options) customTooltip: { useRegularTooltip: true, // note regular tooltip will try to find a "title" attribute in the cell formatter (it won't work without a cell formatter) + useRegularTooltipFromCellTextOnly: true, }, }, { @@ -158,7 +159,12 @@ export class GridCustomTooltipComponent implements OnInit { formatter: Formatters.percentCompleteBar, sortable: true, filterable: true, filter: { model: Filters.slider, operator: '>=' }, - customTooltip: { useRegularTooltip: true, }, + customTooltip: { + position: 'center', + formatter: (_row, _cell, value) => typeof value === 'string' && value.includes('%') ? value : `${value}%`, + headerFormatter: undefined, + headerRowFormatter: undefined + }, }, { id: 'start', name: 'Start', field: 'start', sortable: true, @@ -434,12 +440,12 @@ export class GridCustomTooltipComponent implements OnInit { tooltipFormatter(row: number, cell: number, value: any, column: Column, dataContext: any, grid: SlickGrid) { const tooltipTitle = 'Custom Tooltip'; - const effortDrivenHtml = Formatters.checkmarkMaterial(row, cell, dataContext.effortDriven, column, dataContext, grid); + const effortDrivenHtml = Formatters.checkmarkMaterial(row, cell, dataContext.effortDriven, column, dataContext, grid) as HTMLElement; return `
${tooltipTitle}
Id:
${dataContext.id}
Title:
${dataContext.title}
-
Effort Driven:
${effortDrivenHtml}
+
Effort Driven:
${effortDrivenHtml.outerHTML || ''}
Completion:
${this.loadCompletionIcons(dataContext.percentComplete)}
`; } @@ -449,9 +455,9 @@ export class GridCustomTooltipComponent implements OnInit { // use a 2nd Formatter to get the percent completion // any properties provided from the `asyncPost` will end up in the `__params` property (unless a different prop name is provided via `asyncParamsPropName`) - const completionBar = Formatters.percentCompleteBarWithText(row, cell, dataContext.percentComplete, column, dataContext, grid); + const completionBar = Formatters.percentCompleteBarWithText(row, cell, dataContext.percentComplete, column, dataContext, grid) as HTMLElement; const out = `
${tooltipTitle}
-
Completion:
${completionBar}
+
Completion:
${completionBar.outerHTML || ''}
Lifespan:
${dataContext.__params.lifespan.toFixed(2)}
Ratio:
${dataContext.__params.ratio.toFixed(2)}
`; diff --git a/test/cypress/e2e/example30.cy.ts b/test/cypress/e2e/example30.cy.ts index 605b90ee5..490a7a0cf 100644 --- a/test/cypress/e2e/example30.cy.ts +++ b/test/cypress/e2e/example30.cy.ts @@ -2,7 +2,7 @@ import { changeTimezone, zeroPadding } from '../plugins/utilities'; describe('Example 30 Composite Editor Modal', () => { const fullPreTitles = ['', 'Common Factor', 'Analysis', 'Period', 'Item', '']; - const fullTitles = ['', 'Title', 'Duration', 'Cost', '% Complete', 'Complexity', 'Start', 'Completed', 'Finish', 'Product', 'Country of Origin', 'Action']; + const fullTitles = ['', ' Title ', 'Duration', 'Cost', '% Complete', 'Complexity', 'Start', 'Completed', 'Finish', 'Product', 'Country of Origin', 'Action']; const GRID_ROW_HEIGHT = 35; const EDITABLE_CELL_RGB_COLOR = 'rgba(227, 240, 251, 0.57)'; @@ -32,6 +32,23 @@ describe('Example 30 Composite Editor Modal', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); + it('should display 2 different tooltips when hovering icons on "Title" column', () => { + cy.get('.slick-column-name').as('title-column'); + cy.get('@title-column') + .find('.fa-exclamation-triangle') + .trigger('mouseover'); + + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip .tooltip-body').contains('Task must always be followed by a number'); + + cy.get('@title-column') + .find('.fa-info-circle') + .trigger('mouseover'); + + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip .tooltip-body').contains('Title is always rendered as UPPERCASE'); + }); + it('should have "TASK 0" (uppercase) incremented by 1 after each row', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).contains('TASK 0', { matchCase: false }) .should('have.css', 'text-transform', 'uppercase'); @@ -288,7 +305,7 @@ describe('Example 30 Composite Editor Modal', () => { cy.get('.slick-editor-modal').should('not.exist'); }); - it('should have new TASK 8888 displayed on first row', () => { + it('should have new TASK 8899 displayed on first row', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).contains('TASK 8899', { matchCase: false }); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '33 days'); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '17'); diff --git a/test/cypress/e2e/example32.cy.ts b/test/cypress/e2e/example32.cy.ts index 5c46b9e42..da0c447a2 100644 --- a/test/cypress/e2e/example32.cy.ts +++ b/test/cypress/e2e/example32.cy.ts @@ -36,6 +36,9 @@ describe('Example 32 - Regular & Custom Tooltips', () => { cy.get('.slick-custom-tooltip').should('be.visible'); cy.get('.slick-custom-tooltip').contains('Task 2 - (async tooltip)'); + cy.get('.tooltip-2cols-row:nth(0)').find('div:nth(0)').contains('Completion:'); + cy.get('.tooltip-2cols-row:nth(0)').find('div').should('have.class', 'percent-complete-bar-with-text'); + cy.get('.tooltip-2cols-row:nth(1)').find('div:nth(0)').contains('Lifespan:'); cy.get('.tooltip-2cols-row:nth(1)').find('div:nth(1)').contains(/\d+$/); // use regexp to make sure it's a number @@ -167,7 +170,7 @@ describe('Example 32 - Regular & Custom Tooltips', () => { it('should mouse over header-row (filter) 2nd column Title and expect a tooltip to show rendered from an headerRowFormatter', () => { cy.get(`.slick-headerrow-columns .slick-headerrow-column:nth(1)`).as('checkbox0-filter'); - cy.get('@checkbox0-filter').trigger('mouseenter'); + cy.get('@checkbox0-filter').trigger('mouseover'); cy.get('.slick-custom-tooltip').should('be.visible'); cy.get('.slick-custom-tooltip').contains('Custom Tooltip - Header Row (filter)'); @@ -180,7 +183,7 @@ describe('Example 32 - Regular & Custom Tooltips', () => { it('should mouse over header-row (filter) Finish column and NOT expect any tooltip to show since it is disabled on that column', () => { cy.get(`.slick-headerrow-columns .slick-headerrow-column:nth(8)`).as('finish-filter'); - cy.get('@finish-filter').trigger('mouseenter'); + cy.get('@finish-filter').trigger('mouseover'); cy.get('.slick-custom-tooltip').should('not.exist'); cy.get('@finish-filter').trigger('mouseout'); @@ -188,7 +191,7 @@ describe('Example 32 - Regular & Custom Tooltips', () => { it('should mouse over header-row (filter) Prerequisite column and expect to see tooltip of selected filter options', () => { cy.get(`.slick-headerrow-columns .slick-headerrow-column:nth(10)`).as('checkbox10-header'); - cy.get('@checkbox10-header').trigger('mouseenter'); + cy.get('@checkbox10-header').trigger('mouseover'); cy.get('.filter-prerequisites .ms-choice span').contains('15 of 500 selected'); cy.get('.slick-custom-tooltip').should('be.visible'); @@ -199,7 +202,7 @@ describe('Example 32 - Regular & Custom Tooltips', () => { it('should mouse over header title on 1st column with checkbox and NOT expect any tooltip to show since it is disabled on that column', () => { cy.get(`.slick-header-columns .slick-header-column:nth(0)`).as('checkbox-header'); - cy.get('@checkbox-header').trigger('mouseenter'); + cy.get('@checkbox-header').trigger('mouseover'); cy.get('.slick-custom-tooltip').should('not.exist'); cy.get('@checkbox-header').trigger('mouseout'); @@ -207,7 +210,7 @@ describe('Example 32 - Regular & Custom Tooltips', () => { it('should mouse over header title on 2nd column with Title name and expect a tooltip to show rendered from an headerFormatter', () => { cy.get(`.slick-header-columns .slick-header-column:nth(1)`).as('checkbox0-header'); - cy.get('@checkbox0-header').trigger('mouseenter'); + cy.get('@checkbox0-header').trigger('mouseover'); cy.get('.slick-custom-tooltip').should('be.visible'); cy.get('.slick-custom-tooltip').contains('Custom Tooltip - Header'); @@ -220,7 +223,7 @@ describe('Example 32 - Regular & Custom Tooltips', () => { it('should mouse over header title on 2nd column with Finish name and NOT expect any tooltip to show since it is disabled on that column', () => { cy.get(`.slick-header-columns .slick-header-column:nth(8)`).as('finish-header'); - cy.get('@finish-header').trigger('mouseenter'); + cy.get('@finish-header').trigger('mouseover'); cy.get('.slick-custom-tooltip').should('not.exist'); cy.get('@finish-header').trigger('mouseout'); diff --git a/test/cypress/e2e/example35.cy.ts b/test/cypress/e2e/example35.cy.ts index 34f371b3e..6a5c94660 100644 --- a/test/cypress/e2e/example35.cy.ts +++ b/test/cypress/e2e/example35.cy.ts @@ -22,7 +22,7 @@ describe('Example 35 - Row Based Editing', () => { it('should only allow to toggle a single row into editmode on single mode', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click(); - cy.get('.action-btns--edit').eq(1).click(); + cy.get('.action-btns--edit:nth(0)').click({ force: true }); cy.get('.slick-row.slick-rbe-editmode').should('have.length', 1); }); @@ -30,22 +30,22 @@ describe('Example 35 - Row Based Editing', () => { it('should allow to toggle a multiple rows into editmode on multiple mode', () => { cy.reload(); cy.get('[data-test="single-multi-toggle"]').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click(); - cy.get('.action-btns--edit').eq(1).click(); - cy.get('.action-btns--edit').eq(2).click(); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true }); + cy.get('.action-btns--edit').eq(1).click({ force: true }); + cy.get('.action-btns--edit').eq(2).click({ force: true }); cy.get('.slick-row.slick-rbe-editmode').should('have.length', 3); }); it('should not display editor in rows not being in editmode', () => { cy.reload(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).click(); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).click({ force: true }); cy.get('input').should('have.length', 0); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click(); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true }); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).click(); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).click({ force: true }); cy.get('input').should('have.length', 1); }); @@ -53,7 +53,7 @@ describe('Example 35 - Row Based Editing', () => { it('should highlight modified cells and maintain proper index on sorting', () => { cy.reload(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click(); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true }); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l0.r0`).click().type('abc{enter}'); cy.get('.slick-cell').first().should('have.class', 'slick-rbe-unsaved-cell'); @@ -66,7 +66,7 @@ describe('Example 35 - Row Based Editing', () => { it('should stay in editmode if saving failed', () => { cy.reload(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click(); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true }); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l1.r1`).click().type('50{enter}'); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).click().type('50'); @@ -83,12 +83,12 @@ describe('Example 35 - Row Based Editing', () => { it('should save changes on update button click', () => { cy.reload(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click(); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true }); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l1.r1`).click().type('30{enter}'); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).type('30'); - cy.get('.action-btns--update').first().click(); + cy.get('.action-btns--update').first().click({ force: true }); cy.get('[data-test="fetch-result"]') .should('contain', 'success'); @@ -98,27 +98,27 @@ describe('Example 35 - Row Based Editing', () => { }); it('should cleanup status when starting a new edit mode', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click(); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true }); cy.get('[data-test="fetch-result"]').should('be.empty'); - cy.get('.action-btns--cancel').first().click(); + cy.get('.action-btns--cancel').first().click({ force: true }); }); it('should revert changes on cancel click', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click(); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] .action-btns--edit`).click({ force: true }); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l1.r1`).click().type('50{enter}'); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2.r2`).type('50{enter}'); - cy.get('.action-btns--cancel').first().click(); + cy.get('.action-btns--cancel').first().click({ force: true }); cy.get('.slick-cell.l1.r1').first().should('contain', '30'); cy.get('.slick-cell.l2.r2').first().should('contain', '30'); }); it('should delete a row when clicking it', () => { - cy.get('.action-btns--delete').first().click(); + cy.get('.action-btns--delete').first().click({ force: true }); cy.on('window:confirm', () => true); @@ -143,20 +143,25 @@ describe('Example 35 - Row Based Editing', () => { cy.get('[data-test="toggle-language"]').click(); cy.get('[data-test="selected-locale"]').should('contain', 'fr.json'); - // this seems to be a bug in Cypress, it doesn't seem to be able to click on the button - // but at least it triggers a rerender, which makes it refetch the actual button instead of a cached one - cy.get('.action-btns--update').first().click({ force: true }); + cy.get('.action-btns--edit').first().click({ force: true }); - cy.get('.action-btns--update') - .first() - .should(($btn) => { - expect($btn.attr('title')).to.equal('Mettre à jour la ligne actuelle'); - }); + cy.get('.action-btns--cancel').first().as('cancel-btn'); + cy.get('@cancel-btn').should(($btn) => { + expect($btn.attr('title')).to.equal('Annuler la ligne actuelle'); + }); + cy.get('@cancel-btn').trigger('mouseover', { position: 'top' }); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip .tooltip-body').contains('Annuler la ligne actuelle'); - cy.get('.action-btns--cancel') - .first() - .should(($btn) => { - expect($btn.attr('title')).to.equal('Annuler la ligne actuelle'); - }); + cy.get('.action-btns--update').first().as('update-btn'); + cy.get('@update-btn').should(($btn) => { + expect($btn.attr('title')).to.equal('Mettre à jour la ligne actuelle'); + }); + + cy.get('@update-btn').trigger('mouseover', { position: 'top' }); + + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip .tooltip-body').contains('Mettre à jour la ligne actuelle'); + cy.get('@update-btn').first().click({ force: true }); }); -}); +}); \ No newline at end of file diff --git a/test/mockSlickEvent.ts b/test/mockSlickEvent.ts index 9fc14c95e..ded7a7a0a 100644 --- a/test/mockSlickEvent.ts +++ b/test/mockSlickEvent.ts @@ -1,4 +1,4 @@ -import { Handler, SlickEvent, SlickEventData } from '@slickgrid-universal/common'; +import type { Handler, SlickEvent, SlickEventData } from '@slickgrid-universal/common'; type MergeTypes = { [key in keyof A]: key extends keyof B ? B[key] : A[key]; } & B; // @ts-ignore