@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.
Files changed (65) hide show
  1. package/esm/impl/data/resultTypes.d.ts.map +1 -1
  2. package/esm/impl/data/resultTypes.js +1 -0
  3. package/esm/impl/structure/headers/TotalHeaderCell.js +1 -1
  4. package/esm/locales.d.ts.map +1 -1
  5. package/esm/locales.js +6 -0
  6. package/esm/next/PivotTableNext.d.ts.map +1 -1
  7. package/esm/next/PivotTableNext.js +2 -1
  8. package/esm/next/components/Cell/AttributeCell.d.ts.map +1 -1
  9. package/esm/next/components/Cell/AttributeCell.js +38 -25
  10. package/esm/next/components/Header/MeasureHeader.d.ts.map +1 -1
  11. package/esm/next/components/Header/MeasureHeader.js +18 -4
  12. package/esm/next/components/Header/PivotGroupHeader.d.ts.map +1 -1
  13. package/esm/next/components/Header/PivotGroupHeader.js +17 -4
  14. package/esm/next/components/Header/utils/constructAggregationsMenuItems.d.ts.map +1 -1
  15. package/esm/next/components/Header/utils/constructAggregationsMenuItems.js +3 -2
  16. package/esm/next/components/TotalLabel/RenameTotalLabelPopover.d.ts +19 -0
  17. package/esm/next/components/TotalLabel/RenameTotalLabelPopover.d.ts.map +1 -0
  18. package/esm/next/components/TotalLabel/RenameTotalLabelPopover.js +19 -0
  19. package/esm/next/components/TotalLabel/TotalActionMenu.d.ts +10 -0
  20. package/esm/next/components/TotalLabel/TotalActionMenu.d.ts.map +1 -0
  21. package/esm/next/components/TotalLabel/TotalActionMenu.js +33 -0
  22. package/esm/next/context/PivotTablePropsContext.d.ts +1 -1
  23. package/esm/next/context/PivotTablePropsContext.d.ts.map +1 -1
  24. package/esm/next/context/PivotTablePropsContext.js +1 -0
  25. package/esm/next/context/TotalLabelContext.d.ts +13 -0
  26. package/esm/next/context/TotalLabelContext.d.ts.map +1 -0
  27. package/esm/next/context/TotalLabelContext.js +74 -0
  28. package/esm/next/features/aggregations/totalLabelTarget.d.ts +35 -0
  29. package/esm/next/features/aggregations/totalLabelTarget.d.ts.map +1 -0
  30. package/esm/next/features/aggregations/totalLabelTarget.js +111 -0
  31. package/esm/next/features/aggregations/totals.d.ts +33 -0
  32. package/esm/next/features/aggregations/totals.d.ts.map +1 -0
  33. package/esm/next/features/aggregations/totals.js +73 -0
  34. package/esm/next/features/columns/shared.d.ts.map +1 -1
  35. package/esm/next/features/columns/shared.js +11 -3
  36. package/esm/next/features/transposition/totalHeaderColDef.d.ts.map +1 -1
  37. package/esm/next/features/transposition/totalHeaderColDef.js +7 -2
  38. package/esm/next/hooks/header/useHeaderCellAriaLabel.d.ts +11 -2
  39. package/esm/next/hooks/header/useHeaderCellAriaLabel.d.ts.map +1 -1
  40. package/esm/next/hooks/header/useHeaderCellAriaLabel.js +30 -6
  41. package/esm/next/hooks/header/useHeaderMenu.d.ts +2 -0
  42. package/esm/next/hooks/header/useHeaderMenu.d.ts.map +1 -1
  43. package/esm/next/hooks/header/useHeaderMenu.js +3 -2
  44. package/esm/next/hooks/header/useHeaderMenuSorting.d.ts +1 -1
  45. package/esm/next/hooks/header/useHeaderMenuSorting.d.ts.map +1 -1
  46. package/esm/next/hooks/header/useHeaderMenuSorting.js +7 -4
  47. package/esm/next/hooks/header/useTotalLabelHeader.d.ts +29 -0
  48. package/esm/next/hooks/header/useTotalLabelHeader.d.ts.map +1 -0
  49. package/esm/next/hooks/header/useTotalLabelHeader.js +48 -0
  50. package/esm/next/hooks/totals/useUpdateTotals.d.ts.map +1 -1
  51. package/esm/next/hooks/totals/useUpdateTotals.js +10 -4
  52. package/esm/next/hooks/useInteractionProps.d.ts.map +1 -1
  53. package/esm/next/hooks/useInteractionProps.js +45 -10
  54. package/esm/next/types/menu.d.ts +9 -0
  55. package/esm/next/types/menu.d.ts.map +1 -1
  56. package/esm/next/types/menu.js +1 -1
  57. package/esm/sdk-ui-pivot.d.ts +9 -0
  58. package/esm/tableState.d.ts.map +1 -1
  59. package/esm/tableState.js +1 -0
  60. package/package.json +16 -16
  61. package/styles/css/main.css +18 -0
  62. package/styles/css/main.css.map +1 -1
  63. package/styles/css/pivotTableNext.css +18 -0
  64. package/styles/css/pivotTableNext.css.map +1 -1
  65. 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;AAE7D,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,CAUf;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
+ {"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
- return isTableGrandTotalHeaderValue(cell) || isTableTotalHeaderValue(cell)
47
- ? (translateTotalValue(cell.formattedValue, intl) ?? cell.formattedValue)
48
- : cell.formattedValue;
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":"AAEA,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;AAK7D;;;;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,CAmBjB"}
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
- headerClass: getHeaderCellClassName,
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 undefined to skip
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;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAClC,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,SAAS,EAAE,MAAM,GAAG,SAAS,GAC9B,IAAI,CAMN"}
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 undefined to skip
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 && ariaLabel) {
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;;;;;;;;;;;;;GAaG;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;CACrD,EACD,kBAAkB,EAAE,kBAAkB,GAAG,uBAAuB,GAAG,IAAI;;;;;;;;;;;;;+BAmErC,UAAU,CAAC,cAAc,CAAC,GAAG,aAAa;;;;;;;;;;;;;;;;;EA4B/E"}
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,CAAC,MAAM,EAAE,kBAAkB,GAAG,uBAAuB,GAAG,IAAI;;;;mCAiEjF,gBAAgB;gCAiBf,UAAU,CAAC,cAAc,CAAC,GAAG,aAAa;;EAyBzD"}
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
- const columnName = isRegularHeader ? params.displayName : "";
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;AAMrF;;;;;GAKG;AACH,wBAAgB,eAAe;oCAIP,MAAM,EAAE,oBAAoB,MAAM,EAAE,YAAY,OAAO,YAAY,OAAO;EA4CjG"}
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 { isEqual, uniqWith } from "lodash-es";
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((def) => isEqual(def, total)))
23
- : uniqWith([...currentTotals, ...totalDefinitions], isEqual);
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":"AA8BA,OAAO,EAIH,KAAK,WAAW,EACnB,MAAM,oBAAoB,CAAC;AAuB5B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,IAAI,CAAC,gBAAgB,EAAE,WAAW,KAAK,WAAW,CA4TpF"}
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"}