diff --git a/community-modules/styles/src/internal/base/parts/_calculated-columns.scss b/community-modules/styles/src/internal/base/parts/_calculated-columns.scss index cfaa1883d98..5d2f79dac1e 100644 --- a/community-modules/styles/src/internal/base/parts/_calculated-columns.scss +++ b/community-modules/styles/src/internal/base/parts/_calculated-columns.scss @@ -1,3 +1,5 @@ +@use 'ag'; + @mixin output { .ag-calculated-column-panel .ag-panel-content-wrapper { padding: var(--ag-grid-size) var(--ag-cell-horizontal-padding); @@ -41,9 +43,15 @@ .ag-calculated-column-expression-tools { display: flex; - .ag-group-container { - flex: 1 1 auto; - justify-content: end; + .ag-label { + @include ag.unthemed-rtl( + ( + text-align: right, + ) + ); + } + + .ag-field-set-wrapper { gap: var(--ag-grid-size); flex-wrap: nowrap; align-items: center; diff --git a/community-modules/styles/src/internal/base/parts/_common-structural.scss b/community-modules/styles/src/internal/base/parts/_common-structural.scss index 11a2cb41339..cc2bedd972e 100644 --- a/community-modules/styles/src/internal/base/parts/_common-structural.scss +++ b/community-modules/styles/src/internal/base/parts/_common-structural.scss @@ -1963,6 +1963,23 @@ align-items: flex-end; } + .ag-field-set { + display: flex; + align-items: center; + } + + .ag-field-set-wrapper { + display: flex; + } + + .ag-field-set-wrapper-horizontal { + flex-direction: row; + } + + .ag-field-set-wrapper-vertical { + flex-direction: column; + } + .ag-toggle-button-icon { transition: right 0.3s; position: absolute; diff --git a/packages/ag-grid-community/src/agWidgets/agFieldSet.css b/packages/ag-grid-community/src/agWidgets/agFieldSet.css new file mode 100644 index 00000000000..bcc88bf6828 --- /dev/null +++ b/packages/ag-grid-community/src/agWidgets/agFieldSet.css @@ -0,0 +1,16 @@ +.ag-field-set { + display: flex; + align-items: center; +} + +.ag-field-set-wrapper { + display: flex; +} + +.ag-field-set-wrapper-horizontal { + flex-direction: row; +} + +.ag-field-set-wrapper-vertical { + flex-direction: column; +} diff --git a/packages/ag-grid-community/src/agWidgets/agFieldSet.ts b/packages/ag-grid-community/src/agWidgets/agFieldSet.ts new file mode 100644 index 00000000000..ac9ccaebdf1 --- /dev/null +++ b/packages/ag-grid-community/src/agWidgets/agFieldSet.ts @@ -0,0 +1,89 @@ +import type { + AgBaseComponent, + AgComponentSelector, + AgCoreBeanCollection, + AgElementParams, + BaseEvents, + BaseProperties, + IPropertiesService, +} from 'ag-stack'; +import { RefPlaceholder, _isComponent } from 'ag-stack'; + +import { AgAbstractLabel } from './agAbstractLabel'; +import type { AgLabelParams } from './agFieldParams'; +import agFieldSetCSS from './agFieldSet.css'; +import type { AgWidgetSelectorType } from './agWidgetSelectorType'; + +type AgFieldSetItem = AgBaseComponent | HTMLElement; +type AgFieldSetDirection = 'horizontal' | 'vertical'; + +const AgFieldSetElement: AgElementParams = { + tag: 'div', + cls: 'ag-field-set', + role: 'group', + children: [ + { tag: 'div', ref: 'eLabel' }, + { tag: 'div', ref: 'eWrapper', cls: 'ag-wrapper ag-field-set-wrapper ag-field-set-wrapper-horizontal' }, + ], +}; + +/** @internal AG_GRID_INTERNAL - Not for public use. Can change / be removed at any time. */ +export class AgFieldSet< + TBeanCollection extends AgCoreBeanCollection, + TProperties extends BaseProperties, + TGlobalEvents extends BaseEvents, + TCommon, + TPropertiesService extends IPropertiesService, + TComponentSelectorType extends string, + TConfig extends AgLabelParams = AgLabelParams, +> extends AgAbstractLabel< + TBeanCollection, + TProperties, + TGlobalEvents, + TCommon, + TPropertiesService, + TComponentSelectorType, + TConfig +> { + protected readonly eLabel: HTMLElement = RefPlaceholder; + private readonly eWrapper: HTMLElement = RefPlaceholder; + + constructor(config?: TConfig) { + super(config, AgFieldSetElement as AgElementParams); + this.registerCSS(agFieldSetCSS); + } + + public override postConstruct(): void { + super.postConstruct(); + + this.addCss('ag-field-set'); + this.getGui().setAttribute('aria-labelledby', this.getLabelId()); + } + + public addItems(items: AgFieldSetItem[]): void { + for (const item of items) { + this.addItem(item); + } + } + + public addItem(item: AgFieldSetItem): void { + const el = _isComponent(item) ? item.getGui() : item; + + this.eWrapper.appendChild(el); + } + + public setDirection(direction: AgFieldSetDirection): this { + const eWrapper = this.eWrapper; + + eWrapper.classList.toggle('ag-field-set-wrapper-horizontal', direction === 'horizontal'); + eWrapper.classList.toggle('ag-field-set-wrapper-vertical', direction === 'vertical'); + + return this; + } +} + +/** @internal AG_GRID_INTERNAL - Not for public use. Can change / be removed at any time. */ +export const AgFieldSetSelector: AgComponentSelector = { + selector: 'AG-FIELD-SET', + component: AgFieldSet, +}; diff --git a/packages/ag-grid-community/src/agWidgets/agWidgetSelectorType.ts b/packages/ag-grid-community/src/agWidgets/agWidgetSelectorType.ts index a7b68e90437..06b572e7554 100644 --- a/packages/ag-grid-community/src/agWidgets/agWidgetSelectorType.ts +++ b/packages/ag-grid-community/src/agWidgets/agWidgetSelectorType.ts @@ -3,6 +3,7 @@ export type AgWidgetSelectorType = | 'AG-CHECKBOX' | 'AG-COLOR-INPUT' | 'AG-COLOR-PICKER' + | 'AG-FIELD-SET' | 'AG-GROUP-COMPONENT' | 'AG-INPUT-DATE-FIELD' | 'AG-INPUT-NUMBER-FIELD' diff --git a/packages/ag-grid-community/src/main-internal.ts b/packages/ag-grid-community/src/main-internal.ts index eeb88906898..0bb5cfbbebb 100644 --- a/packages/ag-grid-community/src/main-internal.ts +++ b/packages/ag-grid-community/src/main-internal.ts @@ -394,6 +394,7 @@ export { AgAbstractInputField } from './agWidgets/agAbstractInputField'; export { AgAbstractLabel } from './agWidgets/agAbstractLabel'; export { AgCheckbox, AgCheckboxSelector } from './agWidgets/agCheckbox'; export { AgContentEditableField, AgContentEditableFieldSelector } from './agWidgets/agContentEditableField'; +export { AgFieldSet, AgFieldSetSelector } from './agWidgets/agFieldSet'; export type { AgCheckboxParams, AgFieldParams, diff --git a/packages/ag-grid-community/src/widgets/gridWidgetTypes.ts b/packages/ag-grid-community/src/widgets/gridWidgetTypes.ts index 05bcb250c3c..6c45d9c74bd 100644 --- a/packages/ag-grid-community/src/widgets/gridWidgetTypes.ts +++ b/packages/ag-grid-community/src/widgets/gridWidgetTypes.ts @@ -1,5 +1,6 @@ import type { AgCheckbox } from '../agWidgets/agCheckbox'; import type { AgCheckboxParams } from '../agWidgets/agFieldParams'; +import type { AgFieldSet } from '../agWidgets/agFieldSet'; import type { AgInputDateField } from '../agWidgets/agInputDateField'; import type { AgInputNumberField } from '../agWidgets/agInputNumberField'; import type { AgInputTextArea } from '../agWidgets/agInputTextArea'; @@ -102,3 +103,13 @@ export type GridSelect = AgSelect< AgComponentSelectorType, TValue >; + +/** @internal AG_GRID_INTERNAL - Not for public use. Can change / be removed at any time. */ +export type GridFieldSet = AgFieldSet< + BeanCollection, + GridOptionsWithDefaults, + AgEventTypeParams, + AgGridCommon, + GridOptionsService, + AgComponentSelectorType +>; diff --git a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnForm.ts b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnForm.ts index a2e115df75a..6011e2c06c1 100644 --- a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnForm.ts +++ b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnForm.ts @@ -3,6 +3,7 @@ import { RefPlaceholder, _getDocument, _setDisplayed } from 'ag-stack'; import type { CalculatedColumnExpressionPicker, ElementParams, + GridFieldSet, GridInputTextArea, GridInputTextField, GridSelect, @@ -10,6 +11,7 @@ import type { TooltipFeature, } from 'ag-grid-community'; import { + AgFieldSetSelector, AgInputTextAreaSelector, AgInputTextFieldSelector, AgSelectSelector, @@ -19,8 +21,6 @@ import { import { AgAutocompleteList } from '../advancedFilter/autocomplete/agAutocompleteList'; import type { AutocompleteEntry } from '../advancedFilter/autocomplete/autocompleteParams'; -import { AgGroupComponentSelector } from '../agStack/agGroupComponent'; -import type { GroupComponent } from '../widgets/gridEnterpriseWidgetTypes'; import { CalculatedColumnAutocompleteRow } from './calculatedColumnAutocompleteRow'; import type { CalculatedColumnDataTypeOption, @@ -68,7 +68,7 @@ const CalculatedColumnFormElement: ElementParams = { children: [ { tag: 'ag-input-text-area', ref: 'eExpression' }, { - tag: 'ag-group-component', + tag: 'ag-field-set', cls: 'ag-calculated-column-expression-tools', ref: 'eExpressionTools', children: [ @@ -117,7 +117,7 @@ export class CalculatedColumnForm extends Component { private readonly eApply: HTMLButtonElement = RefPlaceholder; private readonly eCancel: HTMLButtonElement = RefPlaceholder; private readonly eActions: HTMLElement = RefPlaceholder; - private readonly eExpressionTools: GroupComponent = RefPlaceholder; + private readonly eExpressionTools: GridFieldSet = RefPlaceholder; private activeReplacement: { start: number; end: number } | null = null; private suggestionSource: HTMLElement | null = null; @@ -150,7 +150,7 @@ export class CalculatedColumnForm extends Component { AgInputTextFieldSelector, AgSelectSelector, AgInputTextAreaSelector, - AgGroupComponentSelector, + AgFieldSetSelector, ]); this.expressionPickers = new Set(expressionPickers); } @@ -202,8 +202,9 @@ export class CalculatedColumnForm extends Component { this.eExpressionTools .setDirection('horizontal') - .setTitle(translate('calculatedColumnExpressionToolsLabel', 'Insert')) - .hideOpenCloseIcons(true); + .setLabelWidth('flex') + .setLabel(translate('calculatedColumnExpressionToolsLabel', 'Insert')) + .setLabelSeparator(':'); } private setupActionButtons(): void { diff --git a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumns.css b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumns.css index 8e660f2e064..7840f757fc6 100644 --- a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumns.css +++ b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumns.css @@ -44,8 +44,11 @@ .ag-calculated-column-expression-tools { display: flex; - :where(.ag-group-container) { - flex: 1 1 auto; + :where(.ag-label) { + text-align: right; + } + + :where(.ag-field-set-wrapper) { justify-content: end; gap: var(--ag-spacing); flex-wrap: nowrap; diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/autoColService.ts b/packages/ag-grid-enterprise/src/rowHierarchy/autoColService.ts index 867210dc13e..1ad9c6c6360 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/autoColService.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/autoColService.ts @@ -1,7 +1,9 @@ import type { + CellRendererSelectorResult, ColDef, ColumnEventType, IAutoColService, + ITooltipParams, NamedBean, PropertyValueChangedEvent, RowNode, @@ -234,9 +236,108 @@ export class AutoColService extends BeanStub implements NamedBean, IAutoColServi res.initialSort = undefined; } + if (!underlyingColumn) { + this.applyDynamicSingleColumnTooltips(res, autoGroupColumnDef); + } + return res; } + // Each group row's `rowGroupColumn` differs per row, so tooltips must dispatch at render time. + private applyDynamicSingleColumnTooltips(res: ColDef, autoGroupColumnDef: ColDef | undefined): void { + const leafTooltipValueGetter = autoGroupColumnDef?.tooltipValueGetter; + const leafTooltipField = autoGroupColumnDef?.tooltipField; + const leafTooltipComponent = autoGroupColumnDef?.tooltipComponent; + const leafTooltipComponentParams = autoGroupColumnDef?.tooltipComponentParams; + const leafTooltipComponentSelector = autoGroupColumnDef?.tooltipComponentSelector; + + const rowGroupCols = this.beans.rowGroupColsSvc?.columns ?? []; + const anyGroupColHasTooltip = rowGroupCols.some( + (col) => + col.colDef.tooltipValueGetter || + col.colDef.tooltipField || + col.colDef.tooltipComponent || + col.colDef.tooltipComponentSelector + ); + + const hasTooltipConfig = + anyGroupColHasTooltip || + leafTooltipValueGetter != null || + leafTooltipField != null || + leafTooltipComponent != null || + leafTooltipComponentSelector != null; + + if (!hasTooltipConfig) { + return; + } + + res.tooltipField = undefined; + res.tooltipComponent = undefined; + res.tooltipComponentParams = undefined; + + res.tooltipValueGetter = (params) => { + if (params.node?.group) { + const groupedCol = params.node.rowGroupColumn as AgColumn | undefined; + if (!groupedCol) { + return undefined; + } + const colDef = groupedCol.colDef; + if (colDef.tooltipValueGetter) { + return colDef.tooltipValueGetter(params); + } + if (colDef.tooltipField) { + return params.value; + } + return undefined; + } + if (leafTooltipValueGetter) { + return leafTooltipValueGetter(params); + } + if (leafTooltipField && params.data) { + return (params.data as Record)[leafTooltipField]; + } + return undefined; + }; + + const needsSelector = + anyGroupColHasTooltip || leafTooltipComponent != null || leafTooltipComponentSelector != null; + if (!needsSelector) { + return; + } + + // tooltipComponentSelector is typed for cell params but called with ITooltipParams at runtime + const callSelector = ( + sel: ColDef['tooltipComponentSelector'], + params: ITooltipParams + ): CellRendererSelectorResult | undefined => + (sel as unknown as (p: ITooltipParams) => CellRendererSelectorResult | undefined)(params); + + const selector = (params: ITooltipParams): CellRendererSelectorResult | undefined => { + if (params.node?.group) { + const groupedCol = params.node.rowGroupColumn as AgColumn | undefined; + const colDef = groupedCol?.colDef; + if (!colDef) { + return undefined; + } + if (colDef.tooltipComponentSelector) { + return callSelector(colDef.tooltipComponentSelector, params); + } + if (!colDef.tooltipComponent) { + return undefined; + } + return { component: colDef.tooltipComponent, params: colDef.tooltipComponentParams }; + } + if (leafTooltipComponentSelector) { + return callSelector(leafTooltipComponentSelector, params); + } + if (leafTooltipComponent == null) { + return undefined; + } + return { component: leafTooltipComponent, params: leafTooltipComponentParams }; + }; + res.tooltipComponentSelector = selector as unknown as ColDef['tooltipComponentSelector']; + } + private createBaseColDef(rowGroupCol?: AgColumn): ColDef { const userDef = this.gos.get('autoGroupColumnDef'); const localeTextFunc = this.getLocaleTextFunc(); @@ -252,6 +353,12 @@ export class AutoColService extends BeanStub implements NamedBean, IAutoColServi if (rowGroupCol) { res.headerName = this.beans.colNames.getDisplayNameForColumn(rowGroupCol, 'header') ?? undefined; res.headerValueGetter = rowGroupCol.colDef.headerValueGetter; + res.headerTooltip = rowGroupCol.colDef.headerTooltip; + res.tooltipField = rowGroupCol.colDef.tooltipField; + res.tooltipValueGetter = rowGroupCol.colDef.tooltipValueGetter; + res.tooltipComponent = rowGroupCol.colDef.tooltipComponent; + res.tooltipComponentParams = rowGroupCol.colDef.tooltipComponentParams; + res.tooltipComponentSelector = rowGroupCol.colDef.tooltipComponentSelector; } return res; } diff --git a/packages/ag-stack/src/rendering/agPositionableFeature.ts b/packages/ag-stack/src/rendering/agPositionableFeature.ts index 4f6a23d741c..9340178f199 100644 --- a/packages/ag-stack/src/rendering/agPositionableFeature.ts +++ b/packages/ag-stack/src/rendering/agPositionableFeature.ts @@ -370,7 +370,9 @@ export class AgPositionableFeature< } } - if (this.getHeight() === height) { + // only skip when a height style is already in place: on the first sizing pass the measured + // height can match the target before any style is set, which would leave the panel unsized. + if (eGui.style.height !== '' && this.getHeight() === height) { return; } diff --git a/testing/behavioural/src/tooltip/tooltip-group-col-inheritance.test.ts b/testing/behavioural/src/tooltip/tooltip-group-col-inheritance.test.ts new file mode 100644 index 00000000000..32c0ea061b1 --- /dev/null +++ b/testing/behavioural/src/tooltip/tooltip-group-col-inheritance.test.ts @@ -0,0 +1,405 @@ +import { getByTestId, waitFor } from '@testing-library/dom'; +import { userEvent } from '@testing-library/user-event'; + +import { GROUP_AUTO_COLUMN_ID, TooltipModule, agTestIdFor, getGridElement, setupAgTestIds } from 'ag-grid-community'; +import type { GridOptions, ITooltipComp, ITooltipParams, Module } from 'ag-grid-community'; +import { RowGroupingModule } from 'ag-grid-enterprise'; + +import { GridColumns, GridRows, TestGridsManager, asyncSetTimeout } from '../test-utils'; + +describe('Tooltip inheritance in group columns', () => { + const gridMgr = new TestGridsManager({ + includeDefaultModules: true, + modules: [TooltipModule, RowGroupingModule] as Module[], + }); + + beforeAll(() => setupAgTestIds()); + afterEach(() => gridMgr.reset()); + + const TOOLTIP_SHOW_DELAY = 200; + + const getTooltips = () => Array.from(document.querySelectorAll('.ag-tooltip, .ag-tooltip-custom')); + const waitForTooltips = async (count: number) => + await waitFor(() => expect(getTooltips().length).toBe(count), { timeout: 2000 }); + + // TC2 – single column grouping: group cell inherits tooltipValueGetter from underlying colDef + test('group cell inherits tooltipValueGetter when grouped (singleColumn)', async () => { + const gridOptions: GridOptions = { + columnDefs: [ + { + field: 'country', + rowGroup: true, + hide: true, + tooltipValueGetter: (params) => `Tooltip: ${params.value}`, + }, + { field: 'athlete' }, + ], + rowData: [{ country: 'Australia', athlete: 'Alice' }], + tooltipShowDelay: TOOLTIP_SHOW_DELAY, + }; + + const api = await gridMgr.createGridAndWait('tooltip-group-single', gridOptions); + await new GridColumns(api, 'group cell inherits tooltipValueGetter (singleColumn) setup').checkColumns(` + CENTER + ├── ag-Grid-AutoColumn "Group" width:200 + └── athlete "Athlete" width:200 + `); + await new GridRows(api, 'group cell inherits tooltipValueGetter (singleColumn) setup').check(` + ROOT id:ROOT_NODE_ID + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + + const gridDiv = getGridElement(api)! as HTMLElement; + const groupCell = await waitFor(() => + getByTestId(gridDiv, agTestIdFor.autoGroupCell('row-group-country-Australia')) + ); + + await userEvent.hover(groupCell); + await asyncSetTimeout(TOOLTIP_SHOW_DELAY + 50); + await waitForTooltips(1); + expect(getTooltips()[0]).toHaveTextContent('Tooltip: Australia'); + await new GridRows(api, 'group cell inherits tooltipValueGetter (singleColumn) final state').check(` + ROOT id:ROOT_NODE_ID + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + }); + + // TC2 – multiple column grouping: group cell inherits tooltipValueGetter from underlying colDef + test('group cell inherits tooltipValueGetter when grouped (multipleColumns)', async () => { + const gridOptions: GridOptions = { + columnDefs: [ + { + field: 'country', + rowGroup: true, + hide: true, + tooltipValueGetter: (params) => `Country: ${params.value}`, + }, + { field: 'athlete' }, + ], + rowData: [{ country: 'Australia', athlete: 'Alice' }], + groupDisplayType: 'multipleColumns', + tooltipShowDelay: TOOLTIP_SHOW_DELAY, + }; + + const api = await gridMgr.createGridAndWait('tooltip-group-multiple', gridOptions); + await new GridColumns(api, 'group cell inherits tooltipValueGetter (multipleColumns) setup').checkColumns(` + CENTER + ├── ag-Grid-AutoColumn-country "Country" width:200 + └── athlete "Athlete" width:200 + `); + await new GridRows(api, 'group cell inherits tooltipValueGetter (multipleColumns) setup').check(` + ROOT id:ROOT_NODE_ID ag-Grid-AutoColumn-country:null + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn-country:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + + const gridDiv = getGridElement(api)! as HTMLElement; + const autoColId = `${GROUP_AUTO_COLUMN_ID}-country`; + const groupCell = await waitFor(() => + getByTestId(gridDiv, agTestIdFor.cell('row-group-country-Australia', autoColId)) + ); + + await userEvent.hover(groupCell); + await asyncSetTimeout(TOOLTIP_SHOW_DELAY + 50); + await waitForTooltips(1); + expect(getTooltips()[0]).toHaveTextContent('Country: Australia'); + await new GridRows(api, 'group cell inherits tooltipValueGetter (multipleColumns) final state').check(` + ROOT id:ROOT_NODE_ID ag-Grid-AutoColumn-country:null + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn-country:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + }); + + // TC2 – tooltipField is inherited (singleColumn) + test('group cell inherits tooltipField from underlying colDef', async () => { + const gridOptions: GridOptions = { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true, tooltipField: 'country' }, + { field: 'athlete' }, + ], + rowData: [{ country: 'Australia', athlete: 'Alice' }], + tooltipShowDelay: TOOLTIP_SHOW_DELAY, + }; + + const api = await gridMgr.createGridAndWait('tooltip-group-field', gridOptions); + await new GridColumns(api, 'group cell inherits tooltipField setup').checkColumns(` + CENTER + ├── ag-Grid-AutoColumn "Group" width:200 + └── athlete "Athlete" width:200 + `); + await new GridRows(api, 'group cell inherits tooltipField setup').check(` + ROOT id:ROOT_NODE_ID + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + + const gridDiv = getGridElement(api)! as HTMLElement; + const groupCell = await waitFor(() => + getByTestId(gridDiv, agTestIdFor.autoGroupCell('row-group-country-Australia')) + ); + + await userEvent.hover(groupCell); + await asyncSetTimeout(TOOLTIP_SHOW_DELAY + 50); + await waitForTooltips(1); + expect(getTooltips()[0]).toHaveTextContent('Australia'); + await new GridRows(api, 'group cell inherits tooltipField final state').check(` + ROOT id:ROOT_NODE_ID + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + }); + + // TC4 – grouped header inherits headerTooltip from underlying colDef (multipleColumns) + test('grouped header inherits headerTooltip from underlying colDef (multipleColumns)', async () => { + const gridOptions: GridOptions = { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true, headerTooltip: 'Country header tooltip' }, + { field: 'athlete' }, + ], + rowData: [{ country: 'Australia', athlete: 'Alice' }], + groupDisplayType: 'multipleColumns', + tooltipShowDelay: TOOLTIP_SHOW_DELAY, + }; + + const api = await gridMgr.createGridAndWait('tooltip-group-header', gridOptions); + await new GridColumns(api, 'grouped header inherits headerTooltip (multipleColumns) setup').checkColumns(` + CENTER + ├── ag-Grid-AutoColumn-country "Country" width:200 + └── athlete "Athlete" width:200 + `); + await new GridRows(api, 'grouped header inherits headerTooltip (multipleColumns) setup').check(` + ROOT id:ROOT_NODE_ID ag-Grid-AutoColumn-country:null + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn-country:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + + const gridDiv = getGridElement(api)! as HTMLElement; + const autoColId = `${GROUP_AUTO_COLUMN_ID}-country`; + const headerCell = await waitFor(() => getByTestId(gridDiv, agTestIdFor.headerCell(autoColId))); + + await userEvent.hover(headerCell); + await asyncSetTimeout(TOOLTIP_SHOW_DELAY + 50); + await waitForTooltips(1); + expect(getTooltips()[0]).toHaveTextContent('Country header tooltip'); + await new GridRows(api, 'grouped header inherits headerTooltip (multipleColumns) final state').check(` + ROOT id:ROOT_NODE_ID ag-Grid-AutoColumn-country:null + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn-country:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + }); + + // TC5 – leaf rows in the group column use autoGroupColumnDef tooltip settings + test('leaf rows use autoGroupColumnDef tooltipValueGetter', async () => { + const gridOptions: GridOptions = { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'athlete' }], + autoGroupColumnDef: { + field: 'athlete', + tooltipValueGetter: (params) => `Leaf: ${params.value}`, + }, + rowData: [{ country: 'Australia', athlete: 'Alice' }], + tooltipShowDelay: TOOLTIP_SHOW_DELAY, + }; + + const api = await gridMgr.createGridAndWait('tooltip-group-leaf', gridOptions); + await new GridColumns(api, 'leaf rows use autoGroupColumnDef tooltipValueGetter setup').checkColumns(` + CENTER + ├── ag-Grid-AutoColumn "Group" width:200 + └── athlete "Athlete" width:200 + `); + await new GridRows(api, 'leaf rows use autoGroupColumnDef tooltipValueGetter setup').check(` + ROOT id:ROOT_NODE_ID + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn:"Australia" + · └── LEAF hidden id:0 ag-Grid-AutoColumn:"Alice" country:"Australia" athlete:"Alice" + `); + + api.setRowNodeExpanded(api.getRowNode('row-group-country-Australia')!, true); + + const gridDiv = getGridElement(api)! as HTMLElement; + const leafCell = await waitFor(() => getByTestId(gridDiv, agTestIdFor.autoGroupCell('0'))); + + await userEvent.hover(leafCell); + await asyncSetTimeout(TOOLTIP_SHOW_DELAY + 50); + await waitForTooltips(1); + expect(getTooltips()[0]).toHaveTextContent('Leaf: Alice'); + await new GridRows(api, 'leaf rows use autoGroupColumnDef tooltipValueGetter final state').check(` + ROOT id:ROOT_NODE_ID + └─┬ LEAF_GROUP id:row-group-country-Australia ag-Grid-AutoColumn:"Australia" + · └── LEAF id:0 ag-Grid-AutoColumn:"Alice" country:"Australia" athlete:"Alice" + `); + }); + + // group rows always use underlying colDef tooltip even when autoGroupColumnDef also sets one + test('group rows use underlying colDef tooltip, ignoring autoGroupColumnDef tooltipValueGetter', async () => { + const gridOptions: GridOptions = { + columnDefs: [ + { + field: 'country', + rowGroup: true, + hide: true, + tooltipValueGetter: () => 'Inherited tooltip', + }, + { field: 'athlete' }, + ], + autoGroupColumnDef: { + tooltipValueGetter: () => 'autoGroupColumnDef tooltip', + }, + rowData: [{ country: 'Australia', athlete: 'Alice' }], + tooltipShowDelay: TOOLTIP_SHOW_DELAY, + }; + + const api = await gridMgr.createGridAndWait('tooltip-group-override', gridOptions); + await new GridColumns(api, 'group rows use underlying colDef tooltip setup').checkColumns(` + CENTER + ├── ag-Grid-AutoColumn "Group" width:200 + └── athlete "Athlete" width:200 + `); + await new GridRows(api, 'group rows use underlying colDef tooltip setup').check(` + ROOT id:ROOT_NODE_ID + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + + const gridDiv = getGridElement(api)! as HTMLElement; + const groupCell = await waitFor(() => + getByTestId(gridDiv, agTestIdFor.autoGroupCell('row-group-country-Australia')) + ); + + await userEvent.hover(groupCell); + await asyncSetTimeout(TOOLTIP_SHOW_DELAY + 50); + await waitForTooltips(1); + expect(getTooltips()[0]).toHaveTextContent('Inherited tooltip'); + await new GridRows(api, 'group rows use underlying colDef tooltip final state').check(` + ROOT id:ROOT_NODE_ID + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + }); + + // tooltipComponentSelector is inherited by the auto group column (multipleColumns) + test('group column inherits tooltipComponentSelector (multipleColumns)', async () => { + class CustomTooltip implements ITooltipComp { + private eGui!: HTMLElement; + init(params: ITooltipParams) { + this.eGui = document.createElement('div'); + this.eGui.className = 'ag-tooltip-custom custom-selector-tooltip'; + this.eGui.textContent = `selector:${params.value}`; + } + getGui() { + return this.eGui; + } + } + + const gridOptions: GridOptions = { + columnDefs: [ + { + field: 'country', + rowGroup: true, + hide: true, + tooltipValueGetter: (params) => params.value, + tooltipComponentSelector: () => ({ component: CustomTooltip }), + }, + { field: 'athlete' }, + ], + rowData: [{ country: 'Australia', athlete: 'Alice' }], + groupDisplayType: 'multipleColumns', + tooltipShowDelay: TOOLTIP_SHOW_DELAY, + }; + + const api = await gridMgr.createGridAndWait('tooltip-group-selector-multiple', gridOptions); + await new GridColumns(api, 'group column inherits tooltipComponentSelector (multipleColumns) setup') + .checkColumns(` + CENTER + ├── ag-Grid-AutoColumn-country "Country" width:200 + └── athlete "Athlete" width:200 + `); + await new GridRows(api, 'group column inherits tooltipComponentSelector (multipleColumns) setup').check(` + ROOT id:ROOT_NODE_ID ag-Grid-AutoColumn-country:null + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn-country:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + + const gridDiv = getGridElement(api)! as HTMLElement; + const autoColId = `${GROUP_AUTO_COLUMN_ID}-country`; + const groupCell = await waitFor(() => + getByTestId(gridDiv, agTestIdFor.cell('row-group-country-Australia', autoColId)) + ); + + await userEvent.hover(groupCell); + await asyncSetTimeout(TOOLTIP_SHOW_DELAY + 50); + await waitForTooltips(1); + expect(document.querySelector('.custom-selector-tooltip')).not.toBeNull(); + expect(getTooltips()[0]).toHaveTextContent('selector:Australia'); + await new GridRows(api, 'group column inherits tooltipComponentSelector (multipleColumns) final state').check( + ` + ROOT id:ROOT_NODE_ID ag-Grid-AutoColumn-country:null + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn-country:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + ` + ); + }); + + // tooltipComponentSelector on the source column is dispatched in singleColumn mode + test('group cell uses tooltipComponentSelector from source column (singleColumn)', async () => { + class CustomTooltip implements ITooltipComp { + private eGui!: HTMLElement; + init(params: ITooltipParams) { + this.eGui = document.createElement('div'); + this.eGui.className = 'ag-tooltip-custom custom-selector-tooltip'; + this.eGui.textContent = `selector:${params.value}`; + } + getGui() { + return this.eGui; + } + } + + const gridOptions: GridOptions = { + columnDefs: [ + { + field: 'country', + rowGroup: true, + hide: true, + tooltipValueGetter: (params) => params.value, + tooltipComponentSelector: () => ({ component: CustomTooltip }), + }, + { field: 'athlete' }, + ], + rowData: [{ country: 'Australia', athlete: 'Alice' }], + tooltipShowDelay: TOOLTIP_SHOW_DELAY, + }; + + const api = await gridMgr.createGridAndWait('tooltip-group-selector-single', gridOptions); + await new GridColumns(api, 'group cell uses tooltipComponentSelector from source column (singleColumn) setup') + .checkColumns(` + CENTER + ├── ag-Grid-AutoColumn "Group" width:200 + └── athlete "Athlete" width:200 + `); + await new GridRows(api, 'group cell uses tooltipComponentSelector from source column (singleColumn) setup') + .check(` + ROOT id:ROOT_NODE_ID + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + + const gridDiv = getGridElement(api)! as HTMLElement; + const groupCell = await waitFor(() => + getByTestId(gridDiv, agTestIdFor.autoGroupCell('row-group-country-Australia')) + ); + + await userEvent.hover(groupCell); + await asyncSetTimeout(TOOLTIP_SHOW_DELAY + 50); + await waitForTooltips(1); + expect(document.querySelector('.custom-selector-tooltip')).not.toBeNull(); + expect(getTooltips()[0]).toHaveTextContent('selector:Australia'); + await new GridRows( + api, + 'group cell uses tooltipComponentSelector from source column (singleColumn) final state' + ).check(` + ROOT id:ROOT_NODE_ID + └─┬ LEAF_GROUP collapsed id:row-group-country-Australia ag-Grid-AutoColumn:"Australia" + · └── LEAF hidden id:0 country:"Australia" athlete:"Alice" + `); + }); +}); diff --git a/yarn.lock b/yarn.lock index bd7adf7020f..40ea0f2f0a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19299,7 +19299,7 @@ pretty-ms@^9.2.0: dependencies: parse-ms "^4.0.0" -prismjs@^1.29.0, prismjs@^1.30.0: +prismjs@^1.30.0: version "1.30.0" resolved "https://registry.ag-grid.com/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9" integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw== @@ -21567,7 +21567,16 @@ string-length@^4.0.2: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.ag-grid.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.ag-grid.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -21680,7 +21689,7 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.ag-grid.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -21694,6 +21703,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.ag-grid.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.2" resolved "https://registry.ag-grid.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" @@ -23963,7 +23979,7 @@ wordwrap@^1.0.0: resolved "https://registry.ag-grid.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.ag-grid.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -23990,6 +24006,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.ag-grid.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.ag-grid.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"