@gooddata/sdk-ui-pivot 11.57.0-alpha.5 → 11.57.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/impl/data/resultTypes.d.ts.map +1 -1
- package/esm/impl/data/resultTypes.js +1 -0
- package/esm/impl/structure/headers/TotalHeaderCell.js +1 -1
- package/esm/locales.d.ts.map +1 -1
- package/esm/locales.js +6 -0
- package/esm/next/PivotTableNext.d.ts.map +1 -1
- package/esm/next/PivotTableNext.js +2 -1
- package/esm/next/components/Cell/AttributeCell.d.ts.map +1 -1
- package/esm/next/components/Cell/AttributeCell.js +38 -25
- package/esm/next/components/Header/MeasureHeader.d.ts.map +1 -1
- package/esm/next/components/Header/MeasureHeader.js +18 -4
- package/esm/next/components/Header/PivotGroupHeader.d.ts.map +1 -1
- package/esm/next/components/Header/PivotGroupHeader.js +17 -4
- package/esm/next/components/Header/utils/constructAggregationsMenuItems.d.ts.map +1 -1
- package/esm/next/components/Header/utils/constructAggregationsMenuItems.js +3 -2
- package/esm/next/components/TotalLabel/RenameTotalLabelPopover.d.ts +19 -0
- package/esm/next/components/TotalLabel/RenameTotalLabelPopover.d.ts.map +1 -0
- package/esm/next/components/TotalLabel/RenameTotalLabelPopover.js +19 -0
- package/esm/next/components/TotalLabel/TotalActionMenu.d.ts +10 -0
- package/esm/next/components/TotalLabel/TotalActionMenu.d.ts.map +1 -0
- package/esm/next/components/TotalLabel/TotalActionMenu.js +33 -0
- package/esm/next/context/PivotTablePropsContext.d.ts +1 -1
- package/esm/next/context/PivotTablePropsContext.d.ts.map +1 -1
- package/esm/next/context/PivotTablePropsContext.js +1 -0
- package/esm/next/context/TotalLabelContext.d.ts +13 -0
- package/esm/next/context/TotalLabelContext.d.ts.map +1 -0
- package/esm/next/context/TotalLabelContext.js +74 -0
- package/esm/next/features/aggregations/totalLabelTarget.d.ts +35 -0
- package/esm/next/features/aggregations/totalLabelTarget.d.ts.map +1 -0
- package/esm/next/features/aggregations/totalLabelTarget.js +111 -0
- package/esm/next/features/aggregations/totals.d.ts +33 -0
- package/esm/next/features/aggregations/totals.d.ts.map +1 -0
- package/esm/next/features/aggregations/totals.js +73 -0
- package/esm/next/features/columns/shared.d.ts.map +1 -1
- package/esm/next/features/columns/shared.js +11 -3
- package/esm/next/features/transposition/totalHeaderColDef.d.ts.map +1 -1
- package/esm/next/features/transposition/totalHeaderColDef.js +7 -2
- package/esm/next/hooks/header/useHeaderCellAriaLabel.d.ts +11 -2
- package/esm/next/hooks/header/useHeaderCellAriaLabel.d.ts.map +1 -1
- package/esm/next/hooks/header/useHeaderCellAriaLabel.js +30 -6
- package/esm/next/hooks/header/useHeaderMenu.d.ts +2 -0
- package/esm/next/hooks/header/useHeaderMenu.d.ts.map +1 -1
- package/esm/next/hooks/header/useHeaderMenu.js +3 -2
- package/esm/next/hooks/header/useHeaderMenuSorting.d.ts +1 -1
- package/esm/next/hooks/header/useHeaderMenuSorting.d.ts.map +1 -1
- package/esm/next/hooks/header/useHeaderMenuSorting.js +7 -4
- package/esm/next/hooks/header/useTotalLabelHeader.d.ts +29 -0
- package/esm/next/hooks/header/useTotalLabelHeader.d.ts.map +1 -0
- package/esm/next/hooks/header/useTotalLabelHeader.js +48 -0
- package/esm/next/hooks/totals/useUpdateTotals.d.ts.map +1 -1
- package/esm/next/hooks/totals/useUpdateTotals.js +10 -4
- package/esm/next/hooks/useInteractionProps.d.ts.map +1 -1
- package/esm/next/hooks/useInteractionProps.js +45 -10
- package/esm/next/types/menu.d.ts +9 -0
- package/esm/next/types/menu.d.ts.map +1 -1
- package/esm/next/types/menu.js +1 -1
- package/esm/sdk-ui-pivot.d.ts +9 -0
- package/esm/tableState.d.ts.map +1 -1
- package/esm/tableState.js +1 -0
- package/package.json +16 -16
- package/styles/css/main.css +18 -0
- package/styles/css/main.css.map +1 -1
- package/styles/css/pivotTableNext.css +18 -0
- package/styles/css/pivotTableNext.css.map +1 -1
- package/styles/scss/pivotTableNext.scss +27 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { bucketsFind } from "@gooddata/sdk-model";
|
|
3
|
+
import { BucketNames, isAttributeTotalScope, isGrandTotalColumnDefinition, isGrandTotalRowDefinition, isMeasureGroupHeaderColumnDefinition, isMeasureTotalScope, isSubtotalColumnDefinition, isSubtotalRowDefinition, isTableGrandTotalHeaderValue, isTableTotalHeaderValue, } from "@gooddata/sdk-ui";
|
|
4
|
+
import { getResultTotalType, getTotalAlias } from "./totals.js";
|
|
5
|
+
/**
|
|
6
|
+
* Resolves the total definition represented by a total/grand-total label cell (e.g. the "Sum A" or
|
|
7
|
+
* "RowSum Σ" cell, not a numeric value cell). Only grand-total and subtotal rows ever produce these
|
|
8
|
+
* label cells - every "totalHeader"/"grandTotalHeader" cell sdk-ui emits comes from a
|
|
9
|
+
* grandTotalRow/subtotalRow mapper, so its rowDefinition is always a total row. Total/subtotal
|
|
10
|
+
* *column* headers (not cell data) are resolved separately by resolveTotalLabelTargetFromColumnDefinition.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveTotalLabelTargetFromTotalLabelCellData(cellData) {
|
|
13
|
+
if (!cellData || (!isTableTotalHeaderValue(cellData) && !isTableGrandTotalHeaderValue(cellData))) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
if (isMeasureGroupHeaderColumnDefinition(cellData.columnDefinition)) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
if (isTableTotalHeaderValue(cellData) && cellData.value.totalHeaderItem.measureIndex !== undefined) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const { rowDefinition } = cellData;
|
|
23
|
+
if (isGrandTotalRowDefinition(rowDefinition)) {
|
|
24
|
+
const measureIdentifier = measureIdentifierForGrandTotalRow(rowDefinition);
|
|
25
|
+
return {
|
|
26
|
+
type: rowDefinition.totalType,
|
|
27
|
+
attributeIdentifier: rowDefinition.attributeDescriptor.attributeHeader.localIdentifier,
|
|
28
|
+
bucketType: BucketNames.ATTRIBUTE,
|
|
29
|
+
...(measureIdentifier === undefined ? {} : { measureIdentifier }),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (isSubtotalRowDefinition(rowDefinition)) {
|
|
33
|
+
const totalScope = rowDefinition.rowScope.find(isAttributeTotalScope);
|
|
34
|
+
const type = getResultTotalType(totalScope?.header.totalHeaderItem.type);
|
|
35
|
+
if (totalScope && type) {
|
|
36
|
+
const measureIdentifier = measureIdentifierForSubtotalRow(rowDefinition);
|
|
37
|
+
return {
|
|
38
|
+
type,
|
|
39
|
+
attributeIdentifier: totalScope.descriptor.attributeHeader.localIdentifier,
|
|
40
|
+
bucketType: BucketNames.ATTRIBUTE,
|
|
41
|
+
...(measureIdentifier === undefined ? {} : { measureIdentifier }),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
function measureIdentifierForGrandTotalRow(rowDefinition) {
|
|
48
|
+
const [soleMeasureDescriptor] = rowDefinition.measureDescriptors;
|
|
49
|
+
return rowDefinition.measureDescriptors.length === 1
|
|
50
|
+
? soleMeasureDescriptor.measureHeaderItem.localIdentifier
|
|
51
|
+
: undefined;
|
|
52
|
+
}
|
|
53
|
+
function measureIdentifierForSubtotalRow(rowDefinition) {
|
|
54
|
+
return rowDefinition.rowScope.find(isMeasureTotalScope)?.descriptor.measureHeaderItem.localIdentifier;
|
|
55
|
+
}
|
|
56
|
+
export function isFirstTotalHeaderAttributeCell(rowData, colId) {
|
|
57
|
+
let firstColId;
|
|
58
|
+
let firstColumnIndex = Infinity;
|
|
59
|
+
for (const [id, cell] of Object.entries(rowData?.cellDataByColId ?? {})) {
|
|
60
|
+
if (cell.columnDefinition.type !== "attribute" ||
|
|
61
|
+
(!isTableTotalHeaderValue(cell) && !isTableGrandTotalHeaderValue(cell))) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (cell.columnDefinition.columnIndex < firstColumnIndex) {
|
|
65
|
+
firstColumnIndex = cell.columnDefinition.columnIndex;
|
|
66
|
+
firstColId = id;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return firstColId === colId;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolves the total definition represented by a total or subtotal column.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveTotalLabelTargetFromColumnDefinition(columnDefinition) {
|
|
75
|
+
if (!columnDefinition ||
|
|
76
|
+
(!isGrandTotalColumnDefinition(columnDefinition) && !isSubtotalColumnDefinition(columnDefinition))) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
// A grand total spanning multiple column attributes has a single total definition anchored to
|
|
80
|
+
// the outermost attribute (confirmed against real execution fixtures) even though every
|
|
81
|
+
// pivoted level shows an attributeTotalScope entry - collectColumnDefinitions.ts's reversed
|
|
82
|
+
// search resolves a display header for that case, not the total definition's identity, so it
|
|
83
|
+
// doesn't apply here.
|
|
84
|
+
const totalScope = columnDefinition.columnScope.find(isAttributeTotalScope);
|
|
85
|
+
const type = getResultTotalType(columnDefinition.totalHeader.totalHeaderItem.type);
|
|
86
|
+
if (!totalScope || !type) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
type,
|
|
91
|
+
attributeIdentifier: totalScope.descriptor.attributeHeader.localIdentifier,
|
|
92
|
+
bucketType: BucketNames.COLUMNS,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function getTotalAliasForTarget(buckets, target) {
|
|
96
|
+
const totals = bucketsFind(buckets, target.bucketType)?.totals ?? [];
|
|
97
|
+
return getTotalAlias(totals, target.type, target.attributeIdentifier, target.measureIdentifier);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Resolves the custom label (alias) for the total a cell represents, if it has one. A single source
|
|
101
|
+
* of truth for both applying the alias to a cell's formatted value and for later deciding whether
|
|
102
|
+
* that formatted value is already-final custom text that must not be run through total-type
|
|
103
|
+
* translation (an alias that happens to equal a raw type key, e.g. "sum", must still display verbatim).
|
|
104
|
+
*/
|
|
105
|
+
export function resolveCustomTotalLabel(cellData, dataView) {
|
|
106
|
+
const target = resolveTotalLabelTargetFromTotalLabelCellData(cellData);
|
|
107
|
+
if (!target) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
return getTotalAliasForTarget(dataView.definition.buckets, target);
|
|
111
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ITotal, type TotalType } from "@gooddata/sdk-model";
|
|
2
|
+
import type { BucketNames } from "@gooddata/sdk-ui";
|
|
3
|
+
/**
|
|
4
|
+
* Buckets that can contain pivot-table totals whose labels are editable.
|
|
5
|
+
*/
|
|
6
|
+
export type TotalLabelBucketType = typeof BucketNames.ATTRIBUTE | typeof BucketNames.COLUMNS;
|
|
7
|
+
/**
|
|
8
|
+
* Converts the backend result-header total code (for example, `SUM`) to the SDK total type.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getResultTotalType(type: string | undefined): TotalType | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Tests whether two totals describe the same calculation. The alias is deliberately ignored because
|
|
13
|
+
* it changes only the displayed label, not the identity of the total.
|
|
14
|
+
*/
|
|
15
|
+
export declare function areTotalsSameDefinition(first: ITotal, second: ITotal): boolean;
|
|
16
|
+
export declare function getTotalAlias(totals: ITotal[], type: TotalType, attributeIdentifier: string, measureIdentifier?: string): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Resolves each definition's inherited alias independently (not just the first definition's,
|
|
19
|
+
* broadcast to the rest) - a single call can carry definitions for several measures under the same
|
|
20
|
+
* attribute (see constructAggregationsMenuItems), and when scopeToMeasure is true that is exactly
|
|
21
|
+
* the case scopeToMeasure exists to keep separate: one measure's alias must never leak onto another
|
|
22
|
+
* measure's total just because they share a position in the same definitions array.
|
|
23
|
+
*/
|
|
24
|
+
export declare function withInheritedAlias(currentTotals: ITotal[], definitions: ITotal[], scopeToMeasure: boolean): ITotal[];
|
|
25
|
+
/**
|
|
26
|
+
* Updates the custom label on all matching totals (or just the matching measure's, see
|
|
27
|
+
* `getTotalAlias`). Returns `undefined` when no total matches, or when every matching total
|
|
28
|
+
* already had this exact alias - callers push the result as a properties change, and a
|
|
29
|
+
* content-identical array would register as a dirty/unsaved change for nothing (e.g. "Reset to
|
|
30
|
+
* default" on an already-default total, or re-saving the same label).
|
|
31
|
+
*/
|
|
32
|
+
export declare function updateTotalAlias(totals: ITotal[], type: TotalType, attributeIdentifier: string, alias: string | undefined, measureIdentifier?: string): ITotal[] | undefined;
|
|
33
|
+
//# sourceMappingURL=totals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"totals.d.ts","sourceRoot":"","sources":["../../../../src/next/features/aggregations/totals.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,WAAW,CAAC,SAAS,GAAG,OAAO,WAAW,CAAC,OAAO,CAAC;AAE7F;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAGlF;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAM9E;AAED,wBAAgB,aAAa,CACzB,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,SAAS,EACf,mBAAmB,EAAE,MAAM,EAC3B,iBAAiB,CAAC,EAAE,MAAM,GAC3B,MAAM,GAAG,SAAS,CAQpB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAC9B,aAAa,EAAE,MAAM,EAAE,EACvB,WAAW,EAAE,MAAM,EAAE,EACrB,cAAc,EAAE,OAAO,GACxB,MAAM,EAAE,CAoBV;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC5B,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,SAAS,EACf,mBAAmB,EAAE,MAAM,EAC3B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,iBAAiB,CAAC,EAAE,MAAM,GAC3B,MAAM,EAAE,GAAG,SAAS,CA6BtB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { DEFAULT_TOTAL_FUNCTIONS } from "../../constants/internal.js";
|
|
3
|
+
/**
|
|
4
|
+
* Converts the backend result-header total code (for example, `SUM`) to the SDK total type.
|
|
5
|
+
*/
|
|
6
|
+
export function getResultTotalType(type) {
|
|
7
|
+
const normalizedType = type?.toLowerCase();
|
|
8
|
+
return normalizedType && DEFAULT_TOTAL_FUNCTIONS.includes(normalizedType) ? normalizedType : undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Tests whether two totals describe the same calculation. The alias is deliberately ignored because
|
|
12
|
+
* it changes only the displayed label, not the identity of the total.
|
|
13
|
+
*/
|
|
14
|
+
export function areTotalsSameDefinition(first, second) {
|
|
15
|
+
return (first.type === second.type &&
|
|
16
|
+
first.measureIdentifier === second.measureIdentifier &&
|
|
17
|
+
first.attributeIdentifier === second.attributeIdentifier);
|
|
18
|
+
}
|
|
19
|
+
export function getTotalAlias(totals, type, attributeIdentifier, measureIdentifier) {
|
|
20
|
+
return totals.find((total) => total.type === type &&
|
|
21
|
+
total.attributeIdentifier === attributeIdentifier &&
|
|
22
|
+
(measureIdentifier === undefined || total.measureIdentifier === measureIdentifier) &&
|
|
23
|
+
total.alias)?.alias;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolves each definition's inherited alias independently (not just the first definition's,
|
|
27
|
+
* broadcast to the rest) - a single call can carry definitions for several measures under the same
|
|
28
|
+
* attribute (see constructAggregationsMenuItems), and when scopeToMeasure is true that is exactly
|
|
29
|
+
* the case scopeToMeasure exists to keep separate: one measure's alias must never leak onto another
|
|
30
|
+
* measure's total just because they share a position in the same definitions array.
|
|
31
|
+
*/
|
|
32
|
+
export function withInheritedAlias(currentTotals, definitions, scopeToMeasure) {
|
|
33
|
+
let changed = false;
|
|
34
|
+
const updatedDefinitions = definitions.map((definition) => {
|
|
35
|
+
const existingAlias = getTotalAlias(currentTotals, definition.type, definition.attributeIdentifier, scopeToMeasure ? definition.measureIdentifier : undefined);
|
|
36
|
+
if (!existingAlias) {
|
|
37
|
+
return definition;
|
|
38
|
+
}
|
|
39
|
+
changed = true;
|
|
40
|
+
return { ...definition, alias: existingAlias };
|
|
41
|
+
});
|
|
42
|
+
return changed ? updatedDefinitions : definitions;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Updates the custom label on all matching totals (or just the matching measure's, see
|
|
46
|
+
* `getTotalAlias`). Returns `undefined` when no total matches, or when every matching total
|
|
47
|
+
* already had this exact alias - callers push the result as a properties change, and a
|
|
48
|
+
* content-identical array would register as a dirty/unsaved change for nothing (e.g. "Reset to
|
|
49
|
+
* default" on an already-default total, or re-saving the same label).
|
|
50
|
+
*/
|
|
51
|
+
export function updateTotalAlias(totals, type, attributeIdentifier, alias, measureIdentifier) {
|
|
52
|
+
const trimmedAlias = alias?.trim() || undefined;
|
|
53
|
+
let matchFound = false;
|
|
54
|
+
let changed = false;
|
|
55
|
+
const updatedTotals = totals.map((total) => {
|
|
56
|
+
if (total.type !== type ||
|
|
57
|
+
total.attributeIdentifier !== attributeIdentifier ||
|
|
58
|
+
(measureIdentifier !== undefined && total.measureIdentifier !== measureIdentifier)) {
|
|
59
|
+
return total;
|
|
60
|
+
}
|
|
61
|
+
matchFound = true;
|
|
62
|
+
if (total.alias === trimmedAlias) {
|
|
63
|
+
return total;
|
|
64
|
+
}
|
|
65
|
+
changed = true;
|
|
66
|
+
if (!trimmedAlias) {
|
|
67
|
+
const { alias: _alias, ...totalWithoutAlias } = total;
|
|
68
|
+
return totalWithoutAlias;
|
|
69
|
+
}
|
|
70
|
+
return { ...total, alias: trimmedAlias };
|
|
71
|
+
});
|
|
72
|
+
return matchFound && changed ? updatedTotals : undefined;
|
|
73
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../src/next/features/columns/shared.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,KAAK,sBAAsB,EAAE,KAAK,kBAAkB,EAAkB,MAAM,qBAAqB,CAAC;AAC3G,OAAO,EACH,KAAK,+BAA+B,EACpC,KAAK,eAAe,EASvB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../src/next/features/columns/shared.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,KAAK,sBAAsB,EAAE,KAAK,kBAAkB,EAAkB,MAAM,qBAAqB,CAAC;AAC3G,OAAO,EACH,KAAK,+BAA+B,EACpC,KAAK,eAAe,EASvB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D,eAAO,MAAM,oBAAoB,aAAc,eAAe,GAAG,SAAS,YAWzE,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,qBAAqB,CACjC,MAAM,EAAE,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,IAAI,GAAG,SAAS,EACpG,KAAK,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAQf;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAMxF;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACrC,MAAM,EAAE,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,IAAI,GAAG,SAAS,EACpG,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,SAAS,GAChB,MAAM,GAAG,IAAI,CAmBf;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,SAAS,GAAG,MAAM,CAI/F;AAWD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAChC,MAAM,EAAE,wBAAwB,EAChC,WAAW,EAAE,GAAG,EAChB,gBAAgB,EAAE,+BAA+B,GAClD,OAAO,CAuBT;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,EAAE,CAMpE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CACxB,UAAU,EAAE,aAAa,GAAG,SAAS,EACrC,UAAU,EAAE,aAAa,GAAG,SAAS,EACrC,kBAAkB,EAAE,MAAM,GAC3B,OAAO,CAoCT;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,eAAe,EAAE,sBAAsB,EAAE,IAAI,EAAE,SAAS,UAM9F;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,6BAExE"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// (C) 2025-2026 GoodData Corporation
|
|
2
2
|
import { emptyHeaderTitleFromIntl, isTableAttributeHeaderValue, isTableGrandTotalHeaderValue, isTableGrandTotalMeasureValue, isTableGrandTotalSubtotalMeasureValue, isTableOverallTotalMeasureValue, isTableSubtotalMeasureValue, isTableTotalHeaderValue, } from "@gooddata/sdk-ui";
|
|
3
3
|
import { totalTypeMessages } from "../../../locales.js";
|
|
4
|
+
import { resolveCustomTotalLabel } from "../aggregations/totalLabelTarget.js";
|
|
4
5
|
export const isTableTotalCellData = (cellData) => {
|
|
5
6
|
if (!cellData) {
|
|
6
7
|
return false;
|
|
@@ -43,9 +44,16 @@ export function extractIntlFormattedValue(params, colId, intl) {
|
|
|
43
44
|
if (!cell) {
|
|
44
45
|
return null;
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
if (!isTableGrandTotalHeaderValue(cell) && !isTableTotalHeaderValue(cell)) {
|
|
48
|
+
return cell.formattedValue;
|
|
49
|
+
}
|
|
50
|
+
// Resolve the alias directly instead of re-deriving "was formattedValue already replaced
|
|
51
|
+
// upstream" from its own (possibly already-mutated) value - a custom alias must be shown
|
|
52
|
+
// verbatim even when it happens to collide with a raw total-type key like "sum", so it has to
|
|
53
|
+
// win the ?? chain before translation is attempted.
|
|
54
|
+
const dataView = params?.data?.dataView;
|
|
55
|
+
const customLabel = dataView ? resolveCustomTotalLabel(cell, dataView) : undefined;
|
|
56
|
+
return customLabel ?? translateTotalValue(cell.formattedValue, intl) ?? cell.formattedValue;
|
|
49
57
|
}
|
|
50
58
|
/**
|
|
51
59
|
* Extracts translated formatted value from total header.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"totalHeaderColDef.d.ts","sourceRoot":"","sources":["../../../../src/next/features/transposition/totalHeaderColDef.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"totalHeaderColDef.d.ts","sourceRoot":"","sources":["../../../../src/next/features/transposition/totalHeaderColDef.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,KAAK,gCAAgC,EAAE,KAAK,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAG9G,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAO7D;;;;GAIG;AACH,wBAAgB,uBAAuB,CACnC,KAAK,EAAE,MAAM,EACb,gBAAgB,EAAE,CAAC,8BAA8B,GAAG,gCAAgC,CAAC,GAAG;IACpF,YAAY,EAAE,IAAI,CAAC;CACtB,EACD,IAAI,EAAE,SAAS,GAChB,eAAe,CAwBjB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// (C) 2025 GoodData Corporation
|
|
1
|
+
// (C) 2025-2026 GoodData Corporation
|
|
2
2
|
import { MetricCell } from "../../components/Cell/MetricCell.js";
|
|
3
3
|
import { extractFormattedValue, extractIntlTotalHeaderValue } from "../columns/shared.js";
|
|
4
|
+
import { e } from "../styling/bem.js";
|
|
4
5
|
import { getMeasureCellStyle } from "../styling/cell.js";
|
|
5
6
|
import { getHeaderCellClassName } from "../styling/headerCell.js";
|
|
6
7
|
/**
|
|
@@ -23,7 +24,11 @@ export function createTotalHeaderColDef(colId, columnDefinition, intl) {
|
|
|
23
24
|
return extractFormattedValue(params, colId);
|
|
24
25
|
},
|
|
25
26
|
cellStyle: getMeasureCellStyle,
|
|
26
|
-
|
|
27
|
+
// This header shows the total's own label (e.g. "Sum", or a custom rename) rather than a
|
|
28
|
+
// numeric value, so it must not pick up the metric column's right-aligned styling - append a
|
|
29
|
+
// modifier that overrides it back to left-aligned text.
|
|
30
|
+
headerClass: (params) => `${getHeaderCellClassName(params)} ${e("header-cell", { "total-label": true })}`,
|
|
31
|
+
headerComponent: "MeasureHeader",
|
|
27
32
|
sortable: false,
|
|
28
33
|
};
|
|
29
34
|
}
|
|
@@ -4,10 +4,19 @@
|
|
|
4
4
|
* AG Grid's header wrapper (eGridHeader) is the element that receives keyboard focus,
|
|
5
5
|
* so ARIA attributes must be set there rather than on the custom header component's div.
|
|
6
6
|
*
|
|
7
|
+
* A falsy ariaLabel is a no-op UNLESS this hook has previously set an explicit label on this same
|
|
8
|
+
* element (e.g. a pivot group header whose renamed total was then reset) - only then does it fall
|
|
9
|
+
* back to defaultAriaLabel (or remove the attribute if none is given). This hook must never touch
|
|
10
|
+
* an element it has not itself labelled: this component's own visible header text is aria-hidden,
|
|
11
|
+
* so removing a label we didn't set (on a header that was never renamed) could leave the element
|
|
12
|
+
* with no accessible name at all instead of whatever accessible name it already had.
|
|
13
|
+
*
|
|
7
14
|
* @param eGridHeader - AG Grid's header element that receives focus
|
|
8
|
-
* @param ariaLabel - The ARIA label to set, or
|
|
15
|
+
* @param ariaLabel - The ARIA label to set, or a falsy value to fall back to defaultAriaLabel
|
|
16
|
+
* @param defaultAriaLabel - Label to fall back to once this hook has taken over labelling this
|
|
17
|
+
* element and ariaLabel later becomes falsy again; the attribute is removed if this is also falsy
|
|
9
18
|
*
|
|
10
19
|
* @internal
|
|
11
20
|
*/
|
|
12
|
-
export declare function useHeaderCellAriaLabel(eGridHeader: HTMLElement | undefined, ariaLabel: string | undefined): void;
|
|
21
|
+
export declare function useHeaderCellAriaLabel(eGridHeader: HTMLElement | undefined, ariaLabel: string | undefined, defaultAriaLabel?: string): void;
|
|
13
22
|
//# sourceMappingURL=useHeaderCellAriaLabel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHeaderCellAriaLabel.d.ts","sourceRoot":"","sources":["../../../../src/next/hooks/header/useHeaderCellAriaLabel.ts"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"useHeaderCellAriaLabel.d.ts","sourceRoot":"","sources":["../../../../src/next/hooks/header/useHeaderCellAriaLabel.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CAClC,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,gBAAgB,CAAC,EAAE,MAAM,GAC1B,IAAI,CAwBN"}
|
|
@@ -1,20 +1,44 @@
|
|
|
1
|
-
// (C) 2025 GoodData Corporation
|
|
2
|
-
import { useEffect } from "react";
|
|
1
|
+
// (C) 2025-2026 GoodData Corporation
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
3
|
/**
|
|
4
4
|
* Sets an ARIA label on AG Grid's header element for screen reader accessibility.
|
|
5
5
|
*
|
|
6
6
|
* AG Grid's header wrapper (eGridHeader) is the element that receives keyboard focus,
|
|
7
7
|
* so ARIA attributes must be set there rather than on the custom header component's div.
|
|
8
8
|
*
|
|
9
|
+
* A falsy ariaLabel is a no-op UNLESS this hook has previously set an explicit label on this same
|
|
10
|
+
* element (e.g. a pivot group header whose renamed total was then reset) - only then does it fall
|
|
11
|
+
* back to defaultAriaLabel (or remove the attribute if none is given). This hook must never touch
|
|
12
|
+
* an element it has not itself labelled: this component's own visible header text is aria-hidden,
|
|
13
|
+
* so removing a label we didn't set (on a header that was never renamed) could leave the element
|
|
14
|
+
* with no accessible name at all instead of whatever accessible name it already had.
|
|
15
|
+
*
|
|
9
16
|
* @param eGridHeader - AG Grid's header element that receives focus
|
|
10
|
-
* @param ariaLabel - The ARIA label to set, or
|
|
17
|
+
* @param ariaLabel - The ARIA label to set, or a falsy value to fall back to defaultAriaLabel
|
|
18
|
+
* @param defaultAriaLabel - Label to fall back to once this hook has taken over labelling this
|
|
19
|
+
* element and ariaLabel later becomes falsy again; the attribute is removed if this is also falsy
|
|
11
20
|
*
|
|
12
21
|
* @internal
|
|
13
22
|
*/
|
|
14
|
-
export function useHeaderCellAriaLabel(eGridHeader, ariaLabel) {
|
|
23
|
+
export function useHeaderCellAriaLabel(eGridHeader, ariaLabel, defaultAriaLabel) {
|
|
24
|
+
const hasManagedLabel = useRef(false);
|
|
15
25
|
useEffect(() => {
|
|
16
|
-
if (eGridHeader
|
|
26
|
+
if (!eGridHeader) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (ariaLabel) {
|
|
17
30
|
eGridHeader.setAttribute("aria-label", ariaLabel);
|
|
31
|
+
hasManagedLabel.current = true;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!hasManagedLabel.current) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (defaultAriaLabel) {
|
|
38
|
+
eGridHeader.setAttribute("aria-label", defaultAriaLabel);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
eGridHeader.removeAttribute("aria-label");
|
|
18
42
|
}
|
|
19
|
-
}, [eGridHeader, ariaLabel]);
|
|
43
|
+
}, [eGridHeader, ariaLabel, defaultAriaLabel]);
|
|
20
44
|
}
|
|
@@ -12,6 +12,7 @@ import { type AgGridHeaderGroupParams, type AgGridHeaderParams } from "../../typ
|
|
|
12
12
|
* @param includeCellWrapping - Whether cell wrapping menu items should be included
|
|
13
13
|
* @param measureIdentifiers - Array of measure identifiers for the cell
|
|
14
14
|
* @param pivotAttributeDescriptors - Array of pivot attribute descriptors
|
|
15
|
+
* @param ariaLabelDisplayNameOverride - Overrides the name used to build the header's ARIA label
|
|
15
16
|
* @param gridApi - Optional ag-grid API for checking current state
|
|
16
17
|
* @returns Filtered menu items and handlers based on permissions
|
|
17
18
|
*/
|
|
@@ -25,6 +26,7 @@ export declare function useHeaderMenu(options: {
|
|
|
25
26
|
}, additionalContext: {
|
|
26
27
|
measureIdentifiers: string[];
|
|
27
28
|
pivotAttributeDescriptors: IAttributeDescriptor[];
|
|
29
|
+
ariaLabelDisplayNameOverride?: string;
|
|
28
30
|
}, agGridHeaderParams: AgGridHeaderParams | AgGridHeaderGroupParams | null): {
|
|
29
31
|
aggregationsItems: import("../../types/menu.js").IAggregationsMenuItem[];
|
|
30
32
|
handleAggregationsItemClick: (item: import("../../types/menu.js").IAggregationsSubMenuItem) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHeaderMenu.d.ts","sourceRoot":"","sources":["../../../../src/next/hooks/header/useHeaderMenu.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC;AAExC,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,KAAK,uBAAuB,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAO9F
|
|
1
|
+
{"version":3,"file":"useHeaderMenu.d.ts","sourceRoot":"","sources":["../../../../src/next/hooks/header/useHeaderMenu.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC;AAExC,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,KAAK,uBAAuB,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAO9F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CACzB,OAAO,EAAE;IACL,iBAAiB,EAAE,OAAO,CAAC;IAC3B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,mBAAmB,EAAE,OAAO,CAAC;CAChC,EACD,iBAAiB,EAAE;IACf,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,yBAAyB,EAAE,oBAAoB,EAAE,CAAC;IAClD,4BAA4B,CAAC,EAAE,MAAM,CAAC;CACzC,EACD,kBAAkB,EAAE,kBAAkB,GAAG,uBAAuB,GAAG,IAAI;;;;;;;;;;;;;+BAmErC,UAAU,CAAC,cAAc,CAAC,GAAG,aAAa;;;;;;;;;;;;;;;;;EA4B/E"}
|
|
@@ -14,12 +14,13 @@ import { useHeaderMenuTextWrapping } from "./useHeaderMenuTextWrapping.js";
|
|
|
14
14
|
* @param includeCellWrapping - Whether cell wrapping menu items should be included
|
|
15
15
|
* @param measureIdentifiers - Array of measure identifiers for the cell
|
|
16
16
|
* @param pivotAttributeDescriptors - Array of pivot attribute descriptors
|
|
17
|
+
* @param ariaLabelDisplayNameOverride - Overrides the name used to build the header's ARIA label
|
|
17
18
|
* @param gridApi - Optional ag-grid API for checking current state
|
|
18
19
|
* @returns Filtered menu items and handlers based on permissions
|
|
19
20
|
*/
|
|
20
21
|
export function useHeaderMenu(options, additionalContext, agGridHeaderParams) {
|
|
21
22
|
const { allowAggregations, allowTextWrapping, allowSorting, allowDrilling, includeHeaderWrapping, includeCellWrapping, } = options;
|
|
22
|
-
const { measureIdentifiers, pivotAttributeDescriptors } = additionalContext;
|
|
23
|
+
const { measureIdentifiers, pivotAttributeDescriptors, ariaLabelDisplayNameOverride } = additionalContext;
|
|
23
24
|
// Aggregations
|
|
24
25
|
const { aggregationsItems, handleAggregationsItemClick } = useHeaderMenuAggregations(measureIdentifiers, pivotAttributeDescriptors);
|
|
25
26
|
const sanitizedAggregationProps = allowAggregations
|
|
@@ -34,7 +35,7 @@ export function useHeaderMenu(options, additionalContext, agGridHeaderParams) {
|
|
|
34
35
|
? { textWrappingItems, handleTextWrappingItemClick }
|
|
35
36
|
: { textWrappingItems: [], handleTextWrappingItemClick: () => { } };
|
|
36
37
|
// Sorting
|
|
37
|
-
const { sortDirection, sortIndex, sortingItems, handleSortingItemClick, handleProgressSort, headerCellAriaLabel, } = useHeaderMenuSorting(agGridHeaderParams);
|
|
38
|
+
const { sortDirection, sortIndex, sortingItems, handleSortingItemClick, handleProgressSort, headerCellAriaLabel, } = useHeaderMenuSorting(agGridHeaderParams, ariaLabelDisplayNameOverride);
|
|
38
39
|
const sanitizedSortingProps = allowSorting
|
|
39
40
|
? {
|
|
40
41
|
sortingItems,
|
|
@@ -6,7 +6,7 @@ import { type ISortingMenuItem } from "../../types/menu.js";
|
|
|
6
6
|
*
|
|
7
7
|
* @internal
|
|
8
8
|
*/
|
|
9
|
-
export declare function useHeaderMenuSorting(params: AgGridHeaderParams | AgGridHeaderGroupParams | null): {
|
|
9
|
+
export declare function useHeaderMenuSorting(params: AgGridHeaderParams | AgGridHeaderGroupParams | null, displayNameOverride?: string): {
|
|
10
10
|
sortDirection: import("ag-grid-community").SortDirection | undefined;
|
|
11
11
|
sortIndex: number | undefined;
|
|
12
12
|
sortingItems: ISortingMenuItem[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHeaderMenuSorting.d.ts","sourceRoot":"","sources":["../../../../src/next/hooks/header/useHeaderMenuSorting.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAe,MAAM,OAAO,CAAC;AAMrD,OAAO,EAEH,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EAE1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAgC5D;;;;GAIG;AACH,wBAAgB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"useHeaderMenuSorting.d.ts","sourceRoot":"","sources":["../../../../src/next/hooks/header/useHeaderMenuSorting.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAe,MAAM,OAAO,CAAC;AAMrD,OAAO,EAEH,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EAE1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAgC5D;;;;GAIG;AACH,wBAAgB,oBAAoB,CAChC,MAAM,EAAE,kBAAkB,GAAG,uBAAuB,GAAG,IAAI,EAC3D,mBAAmB,CAAC,EAAE,MAAM;;;;mCAqEjB,gBAAgB;gCAiBf,UAAU,CAAC,cAAc,CAAC,GAAG,aAAa;;EAyBzD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// (C) 2025 GoodData Corporation
|
|
1
|
+
// (C) 2025-2026 GoodData Corporation
|
|
2
2
|
import { useCallback } from "react";
|
|
3
3
|
import { useIntl } from "react-intl";
|
|
4
4
|
import { messages } from "../../../locales.js";
|
|
@@ -29,7 +29,7 @@ function buildSortedWithRankLabel(intl, sortDirection, sortIndex) {
|
|
|
29
29
|
*
|
|
30
30
|
* @internal
|
|
31
31
|
*/
|
|
32
|
-
export function useHeaderMenuSorting(params) {
|
|
32
|
+
export function useHeaderMenuSorting(params, displayNameOverride) {
|
|
33
33
|
const intl = useIntl();
|
|
34
34
|
// Only regular header params support sorting (not group headers)
|
|
35
35
|
const isRegularHeader = isAgGridHeaderParams(params);
|
|
@@ -46,8 +46,11 @@ export function useHeaderMenuSorting(params) {
|
|
|
46
46
|
params.setSort(direction, true);
|
|
47
47
|
}
|
|
48
48
|
}, [params, isRegularHeader]);
|
|
49
|
-
// Get column name for ARIA labels
|
|
50
|
-
|
|
49
|
+
// Get column name for ARIA labels. Group headers (not regular/sortable headers) fall back to
|
|
50
|
+
// "" - not params.displayName - so useHeaderCellAriaLabel leaves AG Grid's own group/expand-
|
|
51
|
+
// aware computed accessible name alone, except when there IS a renamed total to announce via
|
|
52
|
+
// displayNameOverride.
|
|
53
|
+
const columnName = displayNameOverride ?? (isRegularHeader ? params.displayName : "");
|
|
51
54
|
// Build ARIA label for sorting menu items
|
|
52
55
|
// Format: "[Column Name], sort [ascending/descending], [sorted ascending/descending, rank N]"
|
|
53
56
|
const buildSortAriaLabel = (direction) => {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type MouseEvent } from "react";
|
|
2
|
+
import { type ITotalLabelTarget } from "../../features/aggregations/totalLabelTarget.js";
|
|
3
|
+
export interface IUseTotalLabelHeaderResult {
|
|
4
|
+
/** The custom alias for `totalLabelTarget`, or undefined if there's no target or no alias set. */
|
|
5
|
+
label: string | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Opens the rename menu; undefined (not wired to the header at all) when there's no target, or
|
|
8
|
+
* when renaming is disabled - otherwise a click would still preventDefault/stopPropagation and
|
|
9
|
+
* then no-op (openTotalLabelMenu's own `enabled` check), silently swallowing the click instead
|
|
10
|
+
* of letting it reach AG Grid's own ancestor listeners.
|
|
11
|
+
* Handles both a direct click and a keyboard Enter/Space event forwarded by the caller's own
|
|
12
|
+
* useHeaderSpaceKey call (`useHeaderSpaceKey(params, onClick ?? handleHeaderClick)`) - there is
|
|
13
|
+
* no separate keydown listener here, since AG Grid only exposes one focusable wrapper element
|
|
14
|
+
* per header and a second independent listener on it can't stop the first from also firing.
|
|
15
|
+
*/
|
|
16
|
+
onClick: ((event: MouseEvent<HTMLDivElement> | KeyboardEvent) => void) | undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Wires up total/subtotal header rename behavior (click-to-open, keyboard Enter/Space via the
|
|
20
|
+
* caller's existing useHeaderSpaceKey, custom alias display, aria-haspopup) shared by
|
|
21
|
+
* PivotGroupHeader and MeasureHeader. Whether renaming can actually happen (`totalLabelsEditable`)
|
|
22
|
+
* is decided solely by TotalLabelContext's own openTotalLabelMenu - callers don't duplicate that
|
|
23
|
+
* business rule. `enabled` is read here only to decide whether to wire up the click/aria affordances
|
|
24
|
+
* at all, so a disabled header doesn't swallow clicks meant for AG Grid's own ancestor listeners.
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export declare function useTotalLabelHeader(eGridHeader: HTMLElement, totalLabelTarget: ITotalLabelTarget | undefined): IUseTotalLabelHeaderResult;
|
|
29
|
+
//# sourceMappingURL=useTotalLabelHeader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTotalLabelHeader.d.ts","sourceRoot":"","sources":["../../../../src/next/hooks/header/useTotalLabelHeader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAA0B,MAAM,OAAO,CAAC;AAGhE,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AAEzF,MAAM,WAAW,0BAA0B;IACvC,kGAAkG;IAClG,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B;;;;;;;;;OASG;IACH,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC,cAAc,CAAC,GAAG,aAAa,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CACtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAC/B,WAAW,EAAE,WAAW,EACxB,gBAAgB,EAAE,iBAAiB,GAAG,SAAS,GAChD,0BAA0B,CAyC5B"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { useCallback, useEffect } from "react";
|
|
3
|
+
import { useTotalLabelContext } from "../../context/TotalLabelContext.js";
|
|
4
|
+
/**
|
|
5
|
+
* Wires up total/subtotal header rename behavior (click-to-open, keyboard Enter/Space via the
|
|
6
|
+
* caller's existing useHeaderSpaceKey, custom alias display, aria-haspopup) shared by
|
|
7
|
+
* PivotGroupHeader and MeasureHeader. Whether renaming can actually happen (`totalLabelsEditable`)
|
|
8
|
+
* is decided solely by TotalLabelContext's own openTotalLabelMenu - callers don't duplicate that
|
|
9
|
+
* business rule. `enabled` is read here only to decide whether to wire up the click/aria affordances
|
|
10
|
+
* at all, so a disabled header doesn't swallow clicks meant for AG Grid's own ancestor listeners.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export function useTotalLabelHeader(eGridHeader, totalLabelTarget) {
|
|
15
|
+
const { enabled, openTotalLabelMenu, getCustomTotalLabel } = useTotalLabelContext();
|
|
16
|
+
const hasTarget = totalLabelTarget !== undefined;
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!eGridHeader || !enabled || !hasTarget) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// AG Grid owns this element and may already carry its own aria-haspopup for reasons unrelated
|
|
22
|
+
// to total-label renaming - preserve and restore it instead of clobbering it on cleanup.
|
|
23
|
+
const previousAriaHasPopup = eGridHeader.getAttribute("aria-haspopup");
|
|
24
|
+
eGridHeader.setAttribute("aria-haspopup", "menu");
|
|
25
|
+
return () => {
|
|
26
|
+
if (previousAriaHasPopup === null) {
|
|
27
|
+
eGridHeader.removeAttribute("aria-haspopup");
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
eGridHeader.setAttribute("aria-haspopup", previousAriaHasPopup);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}, [eGridHeader, enabled, hasTarget]);
|
|
34
|
+
const handleClick = useCallback((event) => {
|
|
35
|
+
if (!totalLabelTarget) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
event.stopPropagation();
|
|
40
|
+
openTotalLabelMenu({ ...totalLabelTarget, anchor: eGridHeader });
|
|
41
|
+
}, [openTotalLabelMenu, eGridHeader, totalLabelTarget]);
|
|
42
|
+
return {
|
|
43
|
+
label: getCustomTotalLabel(totalLabelTarget),
|
|
44
|
+
// Gated the same way the aria-haspopup effect above is - a header with nothing to open
|
|
45
|
+
// should not be wired to swallow clicks at all.
|
|
46
|
+
onClick: enabled && totalLabelTarget ? handleClick : undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUpdateTotals.d.ts","sourceRoot":"","sources":["../../../../src/next/hooks/totals/useUpdateTotals.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,MAAM,EAAqC,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"useUpdateTotals.d.ts","sourceRoot":"","sources":["../../../../src/next/hooks/totals/useUpdateTotals.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,MAAM,EAAqC,MAAM,qBAAqB,CAAC;AAQrF;;;;;GAKG;AACH,wBAAgB,eAAe;oCAKP,MAAM,EAAE,oBAAoB,MAAM,EAAE,YAAY,OAAO,YAAY,OAAO;EAqDjG"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// (C) 2025-2026 GoodData Corporation
|
|
2
2
|
import { useCallback } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { uniqWith } from "lodash-es";
|
|
4
4
|
import { bucketsFind, sanitizeBucketTotals } from "@gooddata/sdk-model";
|
|
5
5
|
import { BucketNames } from "@gooddata/sdk-ui";
|
|
6
6
|
import { usePivotTableProps } from "../../context/PivotTablePropsContext.js";
|
|
7
7
|
import { orderTotals } from "../../features/aggregations/ordering.js";
|
|
8
|
+
import { areTotalsSameDefinition, withInheritedAlias } from "../../features/aggregations/totals.js";
|
|
9
|
+
import { useIsTransposed } from "../shared/useIsTransposed.js";
|
|
8
10
|
/**
|
|
9
11
|
* Manages totals updates and communicates changes externally.
|
|
10
12
|
* Follows the same pattern as useUpdateTextWrapping.
|
|
@@ -13,14 +15,18 @@ import { orderTotals } from "../../features/aggregations/ordering.js";
|
|
|
13
15
|
*/
|
|
14
16
|
export function useUpdateTotals() {
|
|
15
17
|
const { pushData, execution } = usePivotTableProps();
|
|
18
|
+
const isTransposed = useIsTransposed();
|
|
16
19
|
const onUpdateTotals = useCallback((currentTotals, totalDefinitions, isActive, isColumn) => {
|
|
17
20
|
if (!pushData) {
|
|
18
21
|
return;
|
|
19
22
|
}
|
|
20
23
|
// Update total definitions based on current state
|
|
21
24
|
const updatedTotals = isActive
|
|
22
|
-
? currentTotals.filter((total) => !totalDefinitions.some((
|
|
23
|
-
: uniqWith([
|
|
25
|
+
? currentTotals.filter((total) => !totalDefinitions.some((definition) => areTotalsSameDefinition(definition, total)))
|
|
26
|
+
: uniqWith([
|
|
27
|
+
...currentTotals,
|
|
28
|
+
...withInheritedAlias(currentTotals, totalDefinitions, !isColumn && isTransposed),
|
|
29
|
+
], areTotalsSameDefinition);
|
|
24
30
|
// Apply consistent ordering to ensure external consumers receive properly ordered totals
|
|
25
31
|
const orderedTotals = orderTotals(updatedTotals);
|
|
26
32
|
if (isColumn) {
|
|
@@ -47,7 +53,7 @@ export function useUpdateTotals() {
|
|
|
47
53
|
},
|
|
48
54
|
});
|
|
49
55
|
}
|
|
50
|
-
}, [pushData, execution]);
|
|
56
|
+
}, [pushData, execution, isTransposed]);
|
|
51
57
|
return {
|
|
52
58
|
onUpdateTotals,
|
|
53
59
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useInteractionProps.d.ts","sourceRoot":"","sources":["../../../src/next/hooks/useInteractionProps.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useInteractionProps.d.ts","sourceRoot":"","sources":["../../../src/next/hooks/useInteractionProps.ts"],"names":[],"mappings":"AAmCA,OAAO,EAIH,KAAK,WAAW,EACnB,MAAM,oBAAoB,CAAC;AAuB5B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,IAAI,CAAC,gBAAgB,EAAE,WAAW,KAAK,WAAW,CA0WpF"}
|