@quillsql/react 2.16.46 → 2.16.48

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/dist/index.cjs CHANGED
@@ -15116,11 +15116,11 @@ function isValidPivot(pivot) {
15116
15116
  reason: 'Value field cannot be empty when aggregation is not "count" or "percentage"'
15117
15117
  };
15118
15118
  } else if (pivot.aggregations?.some(
15119
- (a) => a.aggregationType === "percentage" && !a.valueField && (!pivot.rowField || pivot.columnField)
15119
+ (a) => a.aggregationType === "percentage" && !a.valueField && !pivot.rowField
15120
15120
  )) {
15121
15121
  return {
15122
15122
  valid: false,
15123
- reason: "Empty percentage field only supported for pivot tables with a row field and no column field"
15123
+ reason: "Empty percentage field only supported for pivot tables with a row field"
15124
15124
  };
15125
15125
  }
15126
15126
  return { valid: true, reason: "" };
@@ -16403,7 +16403,7 @@ function formatAggregationTypeForDisplay(aggregationType) {
16403
16403
  case "max":
16404
16404
  return "Max";
16405
16405
  case "percentage":
16406
- return "Percentage";
16406
+ return "Percent";
16407
16407
  default:
16408
16408
  return aggregationType ? capitalize(String(aggregationType)) : "Value";
16409
16409
  }
@@ -16445,6 +16445,21 @@ function getDefaultPivotMeasureLabel(report) {
16445
16445
  return pivot.columnField ? "Count" : `Count ${snakeAndCamelCaseToTitleCase(tableNames[0] ?? "table")}`;
16446
16446
  }
16447
16447
  const target = valueField !== void 0 && String(valueField).trim() ? resolveValueFieldDisplayLabel(report, valueField) : tableNames.length > 0 ? tableNames.map((n) => snakeAndCamelCaseToTitleCase(n)).join(", ") : "table";
16448
+ if (aggregationType.toLowerCase() === "percentage") {
16449
+ const valueField2 = String(
16450
+ fromAggregations?.valueField2 ?? ""
16451
+ ).trim();
16452
+ if (valueField && valueField2) {
16453
+ return `Percent ${target} of ${resolveValueFieldDisplayLabel(report, valueField2)}`;
16454
+ }
16455
+ const valueFieldType = String(
16456
+ fromAggregations?.valueFieldType ?? ""
16457
+ ).trim().toLowerCase();
16458
+ if (valueField && valueFieldType === "bool") {
16459
+ return `Percent ${target}`;
16460
+ }
16461
+ return `Percent of Total ${target}`;
16462
+ }
16448
16463
  return target ? `${aggWord} ${target}` : aggWord;
16449
16464
  }
16450
16465
  function humanizeLegacyPivotMeasureLabelFragment(text) {
@@ -16452,7 +16467,10 @@ function humanizeLegacyPivotMeasureLabelFragment(text) {
16452
16467
  if (!t) return t;
16453
16468
  const m = t.match(LEGACY_AGG_SNAKE_FIELD);
16454
16469
  if (!m) return t;
16455
- return `${formatAggregationTypeForDisplay(m[1])} ${snakeAndCamelCaseToTitleCase(m[2])}`;
16470
+ if (String(m[1]).toLowerCase() === "percentage") {
16471
+ return `Percent of Total ${snakeAndCamelCaseToTitleCase(m[2] ?? "")}`;
16472
+ }
16473
+ return `${formatAggregationTypeForDisplay(m[1])} ${snakeAndCamelCaseToTitleCase(m[2] ?? "")}`;
16456
16474
  }
16457
16475
  function humanizeChartSeriesTooltipLabel(label) {
16458
16476
  const s = String(label ?? "").trim();
@@ -59847,7 +59865,8 @@ var AGGREGATION_OPTION_TYPES = [
59847
59865
  "average",
59848
59866
  "count",
59849
59867
  "max",
59850
- "min"
59868
+ "min",
59869
+ "percentage"
59851
59870
  ];
59852
59871
  var QUERY_BUILDER_MULTI_VALUE_OPERATORS = /* @__PURE__ */ new Set(["in", "notin"]);
59853
59872
  var TABLE_FORMAT_CACHE_MAX_ENTRIES = 5e3;
@@ -60337,9 +60356,31 @@ function buildPivotAggregationsWithFieldTables({
60337
60356
  schemaTables,
60338
60357
  preferredTables
60339
60358
  }) || void 0;
60359
+ const isPercentageAggregation = String(aggregation.aggregationType ?? "").toLowerCase() === "percentage";
60360
+ const typeFallbackColumns = [
60361
+ ...sourceReport?.columnInternal ?? sourceReport?.columns ?? []
60362
+ ];
60363
+ const valueFieldType = String(
60364
+ aggregation.valueFieldType ?? ""
60365
+ ).trim() || (isPercentageAggregation ? resolveFieldType({
60366
+ field: aggregation.valueField,
60367
+ table: valueFieldTable,
60368
+ schemaTables,
60369
+ fallbackColumns: typeFallbackColumns
60370
+ }) : void 0);
60371
+ const valueField2Type = String(
60372
+ aggregation.valueField2Type ?? ""
60373
+ ).trim() || (isPercentageAggregation ? resolveFieldType({
60374
+ field: aggregation.valueField2,
60375
+ table: valueFieldTable,
60376
+ schemaTables,
60377
+ fallbackColumns: typeFallbackColumns
60378
+ }) : void 0);
60340
60379
  result.push({
60341
60380
  ...aggregationWithoutPivotTables,
60342
60381
  aggregationType: aggregation.aggregationType,
60382
+ ...valueFieldType ? { valueFieldType } : {},
60383
+ ...valueField2Type ? { valueField2Type } : {},
60343
60384
  ...valueFieldTable ? { valueFieldTable } : {}
60344
60385
  });
60345
60386
  }
@@ -60383,8 +60424,9 @@ function getPivotAggregationSortFields(aggregationState, resolvedColumnField) {
60383
60424
  const aggregationType = String(aggregation.aggregationType ?? "").trim();
60384
60425
  if (!aggregationType) continue;
60385
60426
  const valueField = String(aggregation.valueField ?? "").trim();
60386
- const canonicalBaseField = valueField || "count";
60387
- const sortField = hasMultipleAggregations && aggregationType.toLowerCase() !== "count" ? `${canonicalBaseField}_${aggregationType}` : canonicalBaseField;
60427
+ const isValuelessPercentage = !valueField && aggregationType.toLowerCase() === "percentage";
60428
+ const canonicalBaseField = valueField || (isValuelessPercentage ? "percentage" : "count");
60429
+ const sortField = isValuelessPercentage ? "percentage" : hasMultipleAggregations && aggregationType.toLowerCase() !== "count" ? `${canonicalBaseField}_${aggregationType}` : canonicalBaseField;
60388
60430
  if (!seen.has(sortField)) {
60389
60431
  seen.add(sortField);
60390
60432
  fields.push(sortField);
@@ -60403,8 +60445,9 @@ function getPivotAggregationSortLabelOverrides(aggregationState, aggregationTabl
60403
60445
  const aggregationType = String(aggregation.aggregationType ?? "").trim();
60404
60446
  if (!aggregationType) continue;
60405
60447
  const valueField = String(aggregation.valueField ?? "").trim();
60406
- const canonicalBaseField = valueField || "count";
60407
- const sortField = hasMultipleAggregations && aggregationType.toLowerCase() !== "count" ? `${canonicalBaseField}_${aggregationType}` : canonicalBaseField;
60448
+ const isValuelessPercentage = !valueField && aggregationType.toLowerCase() === "percentage";
60449
+ const canonicalBaseField = valueField || (isValuelessPercentage ? "percentage" : "count");
60450
+ const sortField = isValuelessPercentage ? "percentage" : hasMultipleAggregations && aggregationType.toLowerCase() !== "count" ? `${canonicalBaseField}_${aggregationType}` : canonicalBaseField;
60408
60451
  if (labelBySortField.has(sortField)) continue;
60409
60452
  if (valueField || aggregationType.toLowerCase() !== "count") continue;
60410
60453
  const tableHint = String(aggregationTablesByIndex[index] ?? "").trim();
@@ -60558,6 +60601,9 @@ function trimAggregationSuffix(value, aggregationType) {
60558
60601
  }
60559
60602
  function getAggregationFieldKey(aggregation, hasMultipleAggregations) {
60560
60603
  if (!aggregation.aggregationType) return null;
60604
+ if (!aggregation.valueField && aggregation.aggregationType === "percentage") {
60605
+ return "percentage";
60606
+ }
60561
60607
  if (aggregation.valueField) {
60562
60608
  return hasMultipleAggregations ? `${aggregation.valueField}_${aggregation.aggregationType}` : aggregation.valueField;
60563
60609
  }
@@ -60678,6 +60724,22 @@ function buildAggregationLabel(aggregation, report) {
60678
60724
  }
60679
60725
  const rawTarget = aggregation.valueField ?? (tableNames.length > 0 ? tableNames.join(", ") : "table");
60680
60726
  const target = report ? aggregation.valueField ? resolveValueFieldDisplayLabel(report, aggregation.valueField) : rawTarget : rawTarget;
60727
+ if (String(aggregationType).toLowerCase() === "percentage") {
60728
+ const valueField2 = String(
60729
+ aggregation.valueField2 ?? ""
60730
+ ).trim();
60731
+ if (aggregation.valueField && valueField2) {
60732
+ const denominator = report ? resolveValueFieldDisplayLabel(report, valueField2) : valueField2;
60733
+ return `Percent ${target} of ${denominator}`;
60734
+ }
60735
+ const valueFieldType = String(
60736
+ aggregation.valueFieldType ?? ""
60737
+ ).trim().toLowerCase();
60738
+ if (aggregation.valueField && valueFieldType === "bool") {
60739
+ return `Percent ${target}`;
60740
+ }
60741
+ return `Percent of Total ${target}`;
60742
+ }
60681
60743
  return target ? `${aggWord} ${target}` : aggWord;
60682
60744
  }
60683
60745
  function normalizePivotColumnDisplayValue(value) {
@@ -60817,6 +60879,13 @@ function normalizePivotChartForDisplay(chart) {
60817
60879
  if (aggregation?.aggregationType === "count") {
60818
60880
  return "whole_number";
60819
60881
  }
60882
+ if (aggregation?.aggregationType === "percentage") {
60883
+ const savedFormat = yAxisFormatByField.get(field);
60884
+ if (savedFormat === "percent" || savedFormat === "percentage") {
60885
+ return savedFormat;
60886
+ }
60887
+ return "percentage";
60888
+ }
60820
60889
  const directAxisFormat = yAxisFormatByField.get(field);
60821
60890
  if (directAxisFormat) return directAxisFormat;
60822
60891
  const aggType = aggregation?.aggregationType ? String(aggregation.aggregationType) : void 0;
@@ -62013,7 +62082,8 @@ function processPivotState(next, options = {}) {
62013
62082
  const {
62014
62083
  nextState,
62015
62084
  promoteColumnToRow = false,
62016
- fallbackColumnGroupBy
62085
+ fallbackColumnGroupBy,
62086
+ isBoolAggregationField
62017
62087
  } = options;
62018
62088
  const normalizedNext = { ...next };
62019
62089
  const isClearingRowGrouping = nextState?.groupRowsBy !== void 0 && String(nextState.groupRowsBy ?? "").trim().length === 0;
@@ -62056,6 +62126,29 @@ function processPivotState(next, options = {}) {
62056
62126
  normalizedNext.aggregationState = [{ aggregationType: "count" }];
62057
62127
  normalizedNext.aggregationTablesByIndex = [void 0];
62058
62128
  }
62129
+ if (!resolvedRowField) {
62130
+ normalizedNext.aggregationState = normalizedNext.aggregationState.map(
62131
+ (aggregation, index) => {
62132
+ if (String(aggregation.aggregationType ?? "").toLowerCase() !== "percentage") {
62133
+ return aggregation;
62134
+ }
62135
+ const valueField = String(aggregation.valueField ?? "").trim();
62136
+ if (!valueField) {
62137
+ return { ...aggregation, aggregationType: "count" };
62138
+ }
62139
+ const valueField2 = String(aggregation.valueField2 ?? "").trim();
62140
+ if (valueField2 && valueField2 !== valueField) {
62141
+ return aggregation;
62142
+ }
62143
+ const table = String(aggregation.valueFieldTable ?? "").trim() || String(normalizedNext.aggregationTablesByIndex[index] ?? "").trim() || void 0;
62144
+ const isBool = isBoolAggregationField?.(valueField, table);
62145
+ if (isBool === false) {
62146
+ return { ...aggregation, aggregationType: "sum" };
62147
+ }
62148
+ return aggregation;
62149
+ }
62150
+ );
62151
+ }
62059
62152
  if (isSettingRowGrouping && normalizedNext.aggregationState.length > 0) {
62060
62153
  normalizedNext.aggregationState = [...normalizedNext.aggregationState];
62061
62154
  }
@@ -63310,6 +63403,19 @@ function useReport(reportIdArg, options = {}) {
63310
63403
  const customFieldsRef = (0, import_react62.useRef)(schemaData.customFields);
63311
63404
  schemaForReportBuilderStateRef.current = schemaForReportBuilderState;
63312
63405
  customFieldsRef.current = schemaData.customFields;
63406
+ const isBoolAggregationField = (0, import_react62.useCallback)(
63407
+ (field, table2) => {
63408
+ const fieldType = resolveFieldType({
63409
+ field,
63410
+ table: table2,
63411
+ schemaTables: schemaForReportBuilderStateRef.current
63412
+ });
63413
+ if (!fieldType) return void 0;
63414
+ const normalized = String(fieldType).trim().toLowerCase();
63415
+ return isBoolType(normalized) || normalized === "boolean";
63416
+ },
63417
+ []
63418
+ );
63313
63419
  const schemaForeignKeyMap = (0, import_react62.useMemo)(
63314
63420
  () => getSchemaForeignKeyMapping(schemaForReportBuilderState),
63315
63421
  [schemaForReportBuilderState]
@@ -64217,9 +64323,10 @@ function useReport(reportIdArg, options = {}) {
64217
64323
  const explicitTable = getPivotGroupOptionTable(targetRaw);
64218
64324
  const field = explicitTable ? decodePivotGroupOptionValue(targetRaw) : targetRaw;
64219
64325
  if (!field) return {};
64326
+ const fieldForTableResolution = field.includes(":") ? String(field.split(":")[0] ?? "").trim() : field;
64220
64327
  const table2 = explicitTable ?? optionTableLookup.get(rawValue) ?? resolveFieldTable({
64221
64328
  report: sourceReport,
64222
- field,
64329
+ field: fieldForTableResolution,
64223
64330
  schemaTables: schemaForReportBuilderState,
64224
64331
  preferredTables
64225
64332
  }) ?? void 0;
@@ -64234,7 +64341,19 @@ function useReport(reportIdArg, options = {}) {
64234
64341
  if (target === "table") {
64235
64342
  return table2 ? { table: table2 } : preferredTables[0] ? { table: preferredTables[0] } : {};
64236
64343
  }
64237
- if (aggregationType !== "count") {
64344
+ if (aggregationType === "percentage" && target.includes(":")) {
64345
+ const [numeratorRaw, denominatorRaw] = target.split(":");
64346
+ const numerator = String(numeratorRaw ?? "").trim();
64347
+ const denominator = String(denominatorRaw ?? "").trim();
64348
+ if (numerator && denominator) {
64349
+ return {
64350
+ valueField: numerator,
64351
+ valueField2: denominator,
64352
+ ...table2 ? { table: table2 } : {}
64353
+ };
64354
+ }
64355
+ }
64356
+ if (aggregationType !== "count" && aggregationType !== "percentage") {
64238
64357
  return {
64239
64358
  valueField: target,
64240
64359
  ...table2 ? { table: table2 } : {}
@@ -64418,7 +64537,9 @@ function useReport(reportIdArg, options = {}) {
64418
64537
  const pivotAggregationsFromSource = Array.isArray(sourceReport.pivot?.aggregations) ? sourceReport.pivot.aggregations : sourceReport.pivot?.aggregationType ? [
64419
64538
  {
64420
64539
  valueField: sourceReport.pivot?.valueField,
64540
+ valueFieldType: sourceReport.pivot?.valueFieldType,
64421
64541
  valueField2: sourceReport.pivot?.valueField2,
64542
+ valueField2Type: sourceReport.pivot?.valueField2Type,
64422
64543
  aggregationType: sourceReport.pivot?.aggregationType
64423
64544
  }
64424
64545
  ] : [];
@@ -64503,7 +64624,8 @@ function useReport(reportIdArg, options = {}) {
64503
64624
  chartType: prev.chartType ?? sourceReport.chartType
64504
64625
  };
64505
64626
  const normalized = processPivotState(nextFromSource, {
64506
- promoteColumnToRow: shouldPromoteColumnToRowFromPendingFlag
64627
+ promoteColumnToRow: shouldPromoteColumnToRowFromPendingFlag,
64628
+ isBoolAggregationField
64507
64629
  });
64508
64630
  return normalized;
64509
64631
  }
@@ -64843,6 +64965,10 @@ function useReport(reportIdArg, options = {}) {
64843
64965
  aggregation.valueField,
64844
64966
  aggregationTablesByIndex[index]
64845
64967
  );
64968
+ appendPivotColumn(
64969
+ aggregation.valueField2,
64970
+ aggregationTablesByIndex[index]
64971
+ );
64846
64972
  });
64847
64973
  const joinColumns = getJoinColumnsFromTables(effectiveReportBuilderTables);
64848
64974
  const useWideTableColumns = expandReportBuilderColumnsForFlatTable && !pivotState && String(chartType ?? "").toLowerCase() === "table";
@@ -65172,9 +65298,13 @@ function useReport(reportIdArg, options = {}) {
65172
65298
  const aggregationType = String(aggregation.aggregationType);
65173
65299
  const normalizedAggregationType = aggregationType.toLowerCase();
65174
65300
  const tableHint = String(aggregationTablesByIndex[index] ?? "").trim();
65175
- const target = aggregation.valueField && aggregation.valueField.trim().length > 0 ? aggregation.valueField : normalizedAggregationType === "count" ? tableHint || aggregationFallbackTableName : aggregationFallbackTarget;
65301
+ const valueField = String(aggregation.valueField ?? "").trim();
65302
+ const valueField2 = String(aggregation.valueField2 ?? "").trim();
65303
+ const isPercentageRatio = normalizedAggregationType === "percentage" && Boolean(valueField) && Boolean(valueField2);
65304
+ const target = isPercentageRatio ? `${valueField}:${valueField2}` : valueField ? valueField : normalizedAggregationType === "count" || normalizedAggregationType === "percentage" ? tableHint || aggregationFallbackTableName : aggregationFallbackTarget;
65305
+ const isBoolPercentage = normalizedAggregationType === "percentage" && Boolean(valueField) && isBoolAggregationField(valueField, tableHint || void 0) === true;
65176
65306
  return {
65177
- label: `${formatAggregationTypeForDisplay(aggregationType)} ${toTitleCaseLabel(target)}`,
65307
+ label: isPercentageRatio ? `Percent ${toTitleCaseLabel(valueField)} of ${toTitleCaseLabel(valueField2)}` : normalizedAggregationType === "percentage" ? isBoolPercentage ? `Percent ${toTitleCaseLabel(target)}` : `Percent of Total ${toTitleCaseLabel(target)}` : `${formatAggregationTypeForDisplay(aggregationType)} ${toTitleCaseLabel(target)}`,
65178
65308
  value: `${aggregationType}:${target}`
65179
65309
  };
65180
65310
  });
@@ -65182,7 +65312,8 @@ function useReport(reportIdArg, options = {}) {
65182
65312
  aggregationFallbackTableName,
65183
65313
  aggregationFallbackTarget,
65184
65314
  aggregationState,
65185
- aggregationTablesByIndex
65315
+ aggregationTablesByIndex,
65316
+ isBoolAggregationField
65186
65317
  ]);
65187
65318
  const { aggregationBaseOptions, aggregationOptionTableLookup } = (0, import_react62.useMemo)(() => {
65188
65319
  const joinScoped = fieldOptionsAllowedTableNames ? joinCompatibleSchemaTableNames.filter(
@@ -65208,17 +65339,34 @@ function useReport(reportIdArg, options = {}) {
65208
65339
  if (tableName && !tableLookup.has(countValue)) {
65209
65340
  tableLookup.set(countValue, tableName);
65210
65341
  }
65342
+ if (resolvedGroupRowsBy) {
65343
+ const percentageValue = `percentage:${countTarget}`;
65344
+ if (!seen.has(percentageValue)) {
65345
+ options2.push({
65346
+ value: percentageValue,
65347
+ label: `Percent of Total ${toTitleCaseLabel(countTarget)}`
65348
+ });
65349
+ seen.add(percentageValue);
65350
+ }
65351
+ if (tableName && !tableLookup.has(percentageValue)) {
65352
+ tableLookup.set(percentageValue, tableName);
65353
+ }
65354
+ }
65211
65355
  for (const column of table2.columns ?? []) {
65212
65356
  const fieldName = String(column.field ?? "").trim();
65213
65357
  if (!fieldName) continue;
65358
+ const isBoolColumn = isBoolType(String(column.fieldType ?? "").toLowerCase()) || String(column.jsType ?? "").toLowerCase() === "boolean";
65214
65359
  for (const aggregationType of AGGREGATION_OPTION_TYPES) {
65215
65360
  if (aggregationType === "count") continue;
65216
65361
  if (!canAggregateColumn(column, aggregationType)) continue;
65362
+ const isNumberSharePercentage = aggregationType === "percentage" && !isBoolColumn;
65363
+ if (isNumberSharePercentage && !resolvedGroupRowsBy) continue;
65217
65364
  const value = `${aggregationType}:${fieldName}`;
65218
65365
  if (seen.has(value)) continue;
65366
+ const aggregationWord = aggregationType === "percentage" ? isBoolColumn ? "Percent" : "Percent of Total" : formatAggregationTypeForDisplay(aggregationType);
65219
65367
  options2.push({
65220
65368
  value,
65221
- label: tableName ? `${formatAggregationTypeForDisplay(aggregationType)} ${toTitleCaseLabel(tableName)} ${toTitleCaseLabel(fieldName)}` : `${formatAggregationTypeForDisplay(aggregationType)} ${toTitleCaseLabel(fieldName)}`
65369
+ label: tableName ? `${aggregationWord} ${toTitleCaseLabel(tableName)} ${toTitleCaseLabel(fieldName)}` : `${aggregationWord} ${toTitleCaseLabel(fieldName)}`
65222
65370
  });
65223
65371
  seen.add(value);
65224
65372
  if (tableName && !tableLookup.has(value)) {
@@ -65241,6 +65389,7 @@ function useReport(reportIdArg, options = {}) {
65241
65389
  cleanedAggregations,
65242
65390
  fieldOptionsAllowedTableNames,
65243
65391
  joinCompatibleSchemaTableNames,
65392
+ resolvedGroupRowsBy,
65244
65393
  schemaForReportBuilderState
65245
65394
  ]);
65246
65395
  const { schemaColumnOptions, columnIdentifierLookup } = (0, import_react62.useMemo)(() => {
@@ -65483,9 +65632,13 @@ function useReport(reportIdArg, options = {}) {
65483
65632
  aggregation.aggregationType ?? ""
65484
65633
  ).toLowerCase();
65485
65634
  const tableHint = String(aggregationTablesByIndex[index] ?? "").trim();
65486
- const currentTarget = aggregation.valueField && aggregation.valueField.trim().length > 0 ? aggregation.valueField : normalizedAggregationType === "count" ? tableHint || aggregationFallbackTableName : aggregationFallbackTarget;
65635
+ const currentValueField = String(aggregation.valueField ?? "").trim();
65636
+ const currentValueField2 = String(aggregation.valueField2 ?? "").trim();
65637
+ const isPercentageRatio = normalizedAggregationType === "percentage" && Boolean(currentValueField) && Boolean(currentValueField2);
65638
+ const currentTarget = isPercentageRatio ? `${currentValueField}:${currentValueField2}` : currentValueField ? currentValueField : normalizedAggregationType === "count" || normalizedAggregationType === "percentage" ? tableHint || aggregationFallbackTableName : aggregationFallbackTarget;
65487
65639
  const currentValue = aggregation.aggregationType ? `${aggregation.aggregationType}:${currentTarget}` : void 0;
65488
- const currentLabel = aggregation.aggregationType ? `${formatAggregationTypeForDisplay(aggregation.aggregationType)} ${toTitleCaseLabel(currentTarget)}` : "Select aggregation";
65640
+ const isBoolPercentage = normalizedAggregationType === "percentage" && Boolean(currentValueField) && isBoolAggregationField(currentValueField, tableHint || void 0) === true;
65641
+ const currentLabel = aggregation.aggregationType ? isPercentageRatio ? `Percent ${toTitleCaseLabel(currentValueField)} of ${toTitleCaseLabel(currentValueField2)}` : normalizedAggregationType === "percentage" ? isBoolPercentage ? `Percent ${toTitleCaseLabel(currentTarget)}` : `Percent of Total ${toTitleCaseLabel(currentTarget)}` : `${formatAggregationTypeForDisplay(aggregation.aggregationType)} ${toTitleCaseLabel(currentTarget)}` : "Select aggregation";
65489
65642
  const options2 = currentValue ? baseOptions.some((option) => option.value === currentValue) ? baseOptions : [{ value: currentValue, label: currentLabel }, ...baseOptions] : baseOptions;
65490
65643
  return {
65491
65644
  key: `aggregation-${index}`,
@@ -65499,7 +65652,8 @@ function useReport(reportIdArg, options = {}) {
65499
65652
  aggregationFallbackTarget,
65500
65653
  aggregationState,
65501
65654
  aggregationBaseOptions,
65502
- aggregationTablesByIndex
65655
+ aggregationTablesByIndex,
65656
+ isBoolAggregationField
65503
65657
  ]);
65504
65658
  const nextPivot = (0, import_react62.useMemo)(() => {
65505
65659
  if (!sourceReport) return null;
@@ -67272,10 +67426,12 @@ function useReport(reportIdArg, options = {}) {
67272
67426
  preferredTables
67273
67427
  );
67274
67428
  const existing = next.aggregationState[index];
67429
+ const preservedValueField2 = aggregationType !== "percentage" && existing?.valueField2 ? { valueField2: existing.valueField2 } : {};
67275
67430
  const nextAggregation = {
67276
- ...existing?.valueField2 ? { valueField2: existing.valueField2 } : {},
67431
+ ...preservedValueField2,
67277
67432
  aggregationType,
67278
67433
  ...normalizedSelection.valueField ? { valueField: normalizedSelection.valueField } : {},
67434
+ ...normalizedSelection.valueField2 ? { valueField2: normalizedSelection.valueField2 } : {},
67279
67435
  ...normalizedSelection.table ? { valueFieldTable: normalizedSelection.table } : {}
67280
67436
  };
67281
67437
  if (index === next.aggregationState.length) {
@@ -67312,6 +67468,7 @@ function useReport(reportIdArg, options = {}) {
67312
67468
  {
67313
67469
  aggregationType,
67314
67470
  ...normalizedSelection.valueField ? { valueField: normalizedSelection.valueField } : {},
67471
+ ...normalizedSelection.valueField2 ? { valueField2: normalizedSelection.valueField2 } : {},
67315
67472
  ...normalizedSelection.table ? { valueFieldTable: normalizedSelection.table } : {}
67316
67473
  }
67317
67474
  ];
@@ -67436,7 +67593,8 @@ function useReport(reportIdArg, options = {}) {
67436
67593
  next.chartType = effectiveNextState.chartType || void 0;
67437
67594
  const normalizedNext = processPivotState(next, {
67438
67595
  nextState: effectiveNextState,
67439
- promoteColumnToRow: false
67596
+ promoteColumnToRow: false,
67597
+ isBoolAggregationField
67440
67598
  });
67441
67599
  return normalizedNext;
67442
67600
  }
package/dist/index.d.cts CHANGED
@@ -2972,7 +2972,9 @@ type SetReportTableColumnSidebarPatch = {
2972
2972
  };
2973
2973
  type AggregationStateItem = Partial<{
2974
2974
  valueField: string;
2975
+ valueFieldType: string;
2975
2976
  valueField2?: string;
2977
+ valueField2Type?: string;
2976
2978
  aggregationType: AggregationType;
2977
2979
  /** Resolved datasource table for `valueField` (mirrors `aggregationTablesByIndex`). */
2978
2980
  valueFieldTable?: string;
@@ -3268,7 +3270,9 @@ declare function useReport(reportIdArg?: string, options?: UseReportOptions): {
3268
3270
  groupColumnsByOptions: SelectOption[];
3269
3271
  aggregations: Partial<{
3270
3272
  valueField: string;
3273
+ valueFieldType: string;
3271
3274
  valueField2?: string;
3275
+ valueField2Type?: string;
3272
3276
  aggregationType: AggregationType;
3273
3277
  /** Resolved datasource table for `valueField` (mirrors `aggregationTablesByIndex`). */
3274
3278
  valueFieldTable?: string;
package/dist/index.d.ts CHANGED
@@ -2972,7 +2972,9 @@ type SetReportTableColumnSidebarPatch = {
2972
2972
  };
2973
2973
  type AggregationStateItem = Partial<{
2974
2974
  valueField: string;
2975
+ valueFieldType: string;
2975
2976
  valueField2?: string;
2977
+ valueField2Type?: string;
2976
2978
  aggregationType: AggregationType;
2977
2979
  /** Resolved datasource table for `valueField` (mirrors `aggregationTablesByIndex`). */
2978
2980
  valueFieldTable?: string;
@@ -3268,7 +3270,9 @@ declare function useReport(reportIdArg?: string, options?: UseReportOptions): {
3268
3270
  groupColumnsByOptions: SelectOption[];
3269
3271
  aggregations: Partial<{
3270
3272
  valueField: string;
3273
+ valueFieldType: string;
3271
3274
  valueField2?: string;
3275
+ valueField2Type?: string;
3272
3276
  aggregationType: AggregationType;
3273
3277
  /** Resolved datasource table for `valueField` (mirrors `aggregationTablesByIndex`). */
3274
3278
  valueFieldTable?: string;
package/dist/index.js CHANGED
@@ -15162,11 +15162,11 @@ function isValidPivot(pivot) {
15162
15162
  reason: 'Value field cannot be empty when aggregation is not "count" or "percentage"'
15163
15163
  };
15164
15164
  } else if (pivot.aggregations?.some(
15165
- (a) => a.aggregationType === "percentage" && !a.valueField && (!pivot.rowField || pivot.columnField)
15165
+ (a) => a.aggregationType === "percentage" && !a.valueField && !pivot.rowField
15166
15166
  )) {
15167
15167
  return {
15168
15168
  valid: false,
15169
- reason: "Empty percentage field only supported for pivot tables with a row field and no column field"
15169
+ reason: "Empty percentage field only supported for pivot tables with a row field"
15170
15170
  };
15171
15171
  }
15172
15172
  return { valid: true, reason: "" };
@@ -16449,7 +16449,7 @@ function formatAggregationTypeForDisplay(aggregationType) {
16449
16449
  case "max":
16450
16450
  return "Max";
16451
16451
  case "percentage":
16452
- return "Percentage";
16452
+ return "Percent";
16453
16453
  default:
16454
16454
  return aggregationType ? capitalize(String(aggregationType)) : "Value";
16455
16455
  }
@@ -16491,6 +16491,21 @@ function getDefaultPivotMeasureLabel(report) {
16491
16491
  return pivot.columnField ? "Count" : `Count ${snakeAndCamelCaseToTitleCase(tableNames[0] ?? "table")}`;
16492
16492
  }
16493
16493
  const target = valueField !== void 0 && String(valueField).trim() ? resolveValueFieldDisplayLabel(report, valueField) : tableNames.length > 0 ? tableNames.map((n) => snakeAndCamelCaseToTitleCase(n)).join(", ") : "table";
16494
+ if (aggregationType.toLowerCase() === "percentage") {
16495
+ const valueField2 = String(
16496
+ fromAggregations?.valueField2 ?? ""
16497
+ ).trim();
16498
+ if (valueField && valueField2) {
16499
+ return `Percent ${target} of ${resolveValueFieldDisplayLabel(report, valueField2)}`;
16500
+ }
16501
+ const valueFieldType = String(
16502
+ fromAggregations?.valueFieldType ?? ""
16503
+ ).trim().toLowerCase();
16504
+ if (valueField && valueFieldType === "bool") {
16505
+ return `Percent ${target}`;
16506
+ }
16507
+ return `Percent of Total ${target}`;
16508
+ }
16494
16509
  return target ? `${aggWord} ${target}` : aggWord;
16495
16510
  }
16496
16511
  function humanizeLegacyPivotMeasureLabelFragment(text) {
@@ -16498,7 +16513,10 @@ function humanizeLegacyPivotMeasureLabelFragment(text) {
16498
16513
  if (!t) return t;
16499
16514
  const m = t.match(LEGACY_AGG_SNAKE_FIELD);
16500
16515
  if (!m) return t;
16501
- return `${formatAggregationTypeForDisplay(m[1])} ${snakeAndCamelCaseToTitleCase(m[2])}`;
16516
+ if (String(m[1]).toLowerCase() === "percentage") {
16517
+ return `Percent of Total ${snakeAndCamelCaseToTitleCase(m[2] ?? "")}`;
16518
+ }
16519
+ return `${formatAggregationTypeForDisplay(m[1])} ${snakeAndCamelCaseToTitleCase(m[2] ?? "")}`;
16502
16520
  }
16503
16521
  function humanizeChartSeriesTooltipLabel(label) {
16504
16522
  const s = String(label ?? "").trim();
@@ -60055,7 +60073,8 @@ var AGGREGATION_OPTION_TYPES = [
60055
60073
  "average",
60056
60074
  "count",
60057
60075
  "max",
60058
- "min"
60076
+ "min",
60077
+ "percentage"
60059
60078
  ];
60060
60079
  var QUERY_BUILDER_MULTI_VALUE_OPERATORS = /* @__PURE__ */ new Set(["in", "notin"]);
60061
60080
  var TABLE_FORMAT_CACHE_MAX_ENTRIES = 5e3;
@@ -60545,9 +60564,31 @@ function buildPivotAggregationsWithFieldTables({
60545
60564
  schemaTables,
60546
60565
  preferredTables
60547
60566
  }) || void 0;
60567
+ const isPercentageAggregation = String(aggregation.aggregationType ?? "").toLowerCase() === "percentage";
60568
+ const typeFallbackColumns = [
60569
+ ...sourceReport?.columnInternal ?? sourceReport?.columns ?? []
60570
+ ];
60571
+ const valueFieldType = String(
60572
+ aggregation.valueFieldType ?? ""
60573
+ ).trim() || (isPercentageAggregation ? resolveFieldType({
60574
+ field: aggregation.valueField,
60575
+ table: valueFieldTable,
60576
+ schemaTables,
60577
+ fallbackColumns: typeFallbackColumns
60578
+ }) : void 0);
60579
+ const valueField2Type = String(
60580
+ aggregation.valueField2Type ?? ""
60581
+ ).trim() || (isPercentageAggregation ? resolveFieldType({
60582
+ field: aggregation.valueField2,
60583
+ table: valueFieldTable,
60584
+ schemaTables,
60585
+ fallbackColumns: typeFallbackColumns
60586
+ }) : void 0);
60548
60587
  result.push({
60549
60588
  ...aggregationWithoutPivotTables,
60550
60589
  aggregationType: aggregation.aggregationType,
60590
+ ...valueFieldType ? { valueFieldType } : {},
60591
+ ...valueField2Type ? { valueField2Type } : {},
60551
60592
  ...valueFieldTable ? { valueFieldTable } : {}
60552
60593
  });
60553
60594
  }
@@ -60591,8 +60632,9 @@ function getPivotAggregationSortFields(aggregationState, resolvedColumnField) {
60591
60632
  const aggregationType = String(aggregation.aggregationType ?? "").trim();
60592
60633
  if (!aggregationType) continue;
60593
60634
  const valueField = String(aggregation.valueField ?? "").trim();
60594
- const canonicalBaseField = valueField || "count";
60595
- const sortField = hasMultipleAggregations && aggregationType.toLowerCase() !== "count" ? `${canonicalBaseField}_${aggregationType}` : canonicalBaseField;
60635
+ const isValuelessPercentage = !valueField && aggregationType.toLowerCase() === "percentage";
60636
+ const canonicalBaseField = valueField || (isValuelessPercentage ? "percentage" : "count");
60637
+ const sortField = isValuelessPercentage ? "percentage" : hasMultipleAggregations && aggregationType.toLowerCase() !== "count" ? `${canonicalBaseField}_${aggregationType}` : canonicalBaseField;
60596
60638
  if (!seen.has(sortField)) {
60597
60639
  seen.add(sortField);
60598
60640
  fields.push(sortField);
@@ -60611,8 +60653,9 @@ function getPivotAggregationSortLabelOverrides(aggregationState, aggregationTabl
60611
60653
  const aggregationType = String(aggregation.aggregationType ?? "").trim();
60612
60654
  if (!aggregationType) continue;
60613
60655
  const valueField = String(aggregation.valueField ?? "").trim();
60614
- const canonicalBaseField = valueField || "count";
60615
- const sortField = hasMultipleAggregations && aggregationType.toLowerCase() !== "count" ? `${canonicalBaseField}_${aggregationType}` : canonicalBaseField;
60656
+ const isValuelessPercentage = !valueField && aggregationType.toLowerCase() === "percentage";
60657
+ const canonicalBaseField = valueField || (isValuelessPercentage ? "percentage" : "count");
60658
+ const sortField = isValuelessPercentage ? "percentage" : hasMultipleAggregations && aggregationType.toLowerCase() !== "count" ? `${canonicalBaseField}_${aggregationType}` : canonicalBaseField;
60616
60659
  if (labelBySortField.has(sortField)) continue;
60617
60660
  if (valueField || aggregationType.toLowerCase() !== "count") continue;
60618
60661
  const tableHint = String(aggregationTablesByIndex[index] ?? "").trim();
@@ -60766,6 +60809,9 @@ function trimAggregationSuffix(value, aggregationType) {
60766
60809
  }
60767
60810
  function getAggregationFieldKey(aggregation, hasMultipleAggregations) {
60768
60811
  if (!aggregation.aggregationType) return null;
60812
+ if (!aggregation.valueField && aggregation.aggregationType === "percentage") {
60813
+ return "percentage";
60814
+ }
60769
60815
  if (aggregation.valueField) {
60770
60816
  return hasMultipleAggregations ? `${aggregation.valueField}_${aggregation.aggregationType}` : aggregation.valueField;
60771
60817
  }
@@ -60886,6 +60932,22 @@ function buildAggregationLabel(aggregation, report) {
60886
60932
  }
60887
60933
  const rawTarget = aggregation.valueField ?? (tableNames.length > 0 ? tableNames.join(", ") : "table");
60888
60934
  const target = report ? aggregation.valueField ? resolveValueFieldDisplayLabel(report, aggregation.valueField) : rawTarget : rawTarget;
60935
+ if (String(aggregationType).toLowerCase() === "percentage") {
60936
+ const valueField2 = String(
60937
+ aggregation.valueField2 ?? ""
60938
+ ).trim();
60939
+ if (aggregation.valueField && valueField2) {
60940
+ const denominator = report ? resolveValueFieldDisplayLabel(report, valueField2) : valueField2;
60941
+ return `Percent ${target} of ${denominator}`;
60942
+ }
60943
+ const valueFieldType = String(
60944
+ aggregation.valueFieldType ?? ""
60945
+ ).trim().toLowerCase();
60946
+ if (aggregation.valueField && valueFieldType === "bool") {
60947
+ return `Percent ${target}`;
60948
+ }
60949
+ return `Percent of Total ${target}`;
60950
+ }
60889
60951
  return target ? `${aggWord} ${target}` : aggWord;
60890
60952
  }
60891
60953
  function normalizePivotColumnDisplayValue(value) {
@@ -61025,6 +61087,13 @@ function normalizePivotChartForDisplay(chart) {
61025
61087
  if (aggregation?.aggregationType === "count") {
61026
61088
  return "whole_number";
61027
61089
  }
61090
+ if (aggregation?.aggregationType === "percentage") {
61091
+ const savedFormat = yAxisFormatByField.get(field);
61092
+ if (savedFormat === "percent" || savedFormat === "percentage") {
61093
+ return savedFormat;
61094
+ }
61095
+ return "percentage";
61096
+ }
61028
61097
  const directAxisFormat = yAxisFormatByField.get(field);
61029
61098
  if (directAxisFormat) return directAxisFormat;
61030
61099
  const aggType = aggregation?.aggregationType ? String(aggregation.aggregationType) : void 0;
@@ -62221,7 +62290,8 @@ function processPivotState(next, options = {}) {
62221
62290
  const {
62222
62291
  nextState,
62223
62292
  promoteColumnToRow = false,
62224
- fallbackColumnGroupBy
62293
+ fallbackColumnGroupBy,
62294
+ isBoolAggregationField
62225
62295
  } = options;
62226
62296
  const normalizedNext = { ...next };
62227
62297
  const isClearingRowGrouping = nextState?.groupRowsBy !== void 0 && String(nextState.groupRowsBy ?? "").trim().length === 0;
@@ -62264,6 +62334,29 @@ function processPivotState(next, options = {}) {
62264
62334
  normalizedNext.aggregationState = [{ aggregationType: "count" }];
62265
62335
  normalizedNext.aggregationTablesByIndex = [void 0];
62266
62336
  }
62337
+ if (!resolvedRowField) {
62338
+ normalizedNext.aggregationState = normalizedNext.aggregationState.map(
62339
+ (aggregation, index) => {
62340
+ if (String(aggregation.aggregationType ?? "").toLowerCase() !== "percentage") {
62341
+ return aggregation;
62342
+ }
62343
+ const valueField = String(aggregation.valueField ?? "").trim();
62344
+ if (!valueField) {
62345
+ return { ...aggregation, aggregationType: "count" };
62346
+ }
62347
+ const valueField2 = String(aggregation.valueField2 ?? "").trim();
62348
+ if (valueField2 && valueField2 !== valueField) {
62349
+ return aggregation;
62350
+ }
62351
+ const table = String(aggregation.valueFieldTable ?? "").trim() || String(normalizedNext.aggregationTablesByIndex[index] ?? "").trim() || void 0;
62352
+ const isBool = isBoolAggregationField?.(valueField, table);
62353
+ if (isBool === false) {
62354
+ return { ...aggregation, aggregationType: "sum" };
62355
+ }
62356
+ return aggregation;
62357
+ }
62358
+ );
62359
+ }
62267
62360
  if (isSettingRowGrouping && normalizedNext.aggregationState.length > 0) {
62268
62361
  normalizedNext.aggregationState = [...normalizedNext.aggregationState];
62269
62362
  }
@@ -63518,6 +63611,19 @@ function useReport(reportIdArg, options = {}) {
63518
63611
  const customFieldsRef = useRef25(schemaData.customFields);
63519
63612
  schemaForReportBuilderStateRef.current = schemaForReportBuilderState;
63520
63613
  customFieldsRef.current = schemaData.customFields;
63614
+ const isBoolAggregationField = useCallback6(
63615
+ (field, table2) => {
63616
+ const fieldType = resolveFieldType({
63617
+ field,
63618
+ table: table2,
63619
+ schemaTables: schemaForReportBuilderStateRef.current
63620
+ });
63621
+ if (!fieldType) return void 0;
63622
+ const normalized = String(fieldType).trim().toLowerCase();
63623
+ return isBoolType(normalized) || normalized === "boolean";
63624
+ },
63625
+ []
63626
+ );
63521
63627
  const schemaForeignKeyMap = useMemo33(
63522
63628
  () => getSchemaForeignKeyMapping(schemaForReportBuilderState),
63523
63629
  [schemaForReportBuilderState]
@@ -64425,9 +64531,10 @@ function useReport(reportIdArg, options = {}) {
64425
64531
  const explicitTable = getPivotGroupOptionTable(targetRaw);
64426
64532
  const field = explicitTable ? decodePivotGroupOptionValue(targetRaw) : targetRaw;
64427
64533
  if (!field) return {};
64534
+ const fieldForTableResolution = field.includes(":") ? String(field.split(":")[0] ?? "").trim() : field;
64428
64535
  const table2 = explicitTable ?? optionTableLookup.get(rawValue) ?? resolveFieldTable({
64429
64536
  report: sourceReport,
64430
- field,
64537
+ field: fieldForTableResolution,
64431
64538
  schemaTables: schemaForReportBuilderState,
64432
64539
  preferredTables
64433
64540
  }) ?? void 0;
@@ -64442,7 +64549,19 @@ function useReport(reportIdArg, options = {}) {
64442
64549
  if (target === "table") {
64443
64550
  return table2 ? { table: table2 } : preferredTables[0] ? { table: preferredTables[0] } : {};
64444
64551
  }
64445
- if (aggregationType !== "count") {
64552
+ if (aggregationType === "percentage" && target.includes(":")) {
64553
+ const [numeratorRaw, denominatorRaw] = target.split(":");
64554
+ const numerator = String(numeratorRaw ?? "").trim();
64555
+ const denominator = String(denominatorRaw ?? "").trim();
64556
+ if (numerator && denominator) {
64557
+ return {
64558
+ valueField: numerator,
64559
+ valueField2: denominator,
64560
+ ...table2 ? { table: table2 } : {}
64561
+ };
64562
+ }
64563
+ }
64564
+ if (aggregationType !== "count" && aggregationType !== "percentage") {
64446
64565
  return {
64447
64566
  valueField: target,
64448
64567
  ...table2 ? { table: table2 } : {}
@@ -64626,7 +64745,9 @@ function useReport(reportIdArg, options = {}) {
64626
64745
  const pivotAggregationsFromSource = Array.isArray(sourceReport.pivot?.aggregations) ? sourceReport.pivot.aggregations : sourceReport.pivot?.aggregationType ? [
64627
64746
  {
64628
64747
  valueField: sourceReport.pivot?.valueField,
64748
+ valueFieldType: sourceReport.pivot?.valueFieldType,
64629
64749
  valueField2: sourceReport.pivot?.valueField2,
64750
+ valueField2Type: sourceReport.pivot?.valueField2Type,
64630
64751
  aggregationType: sourceReport.pivot?.aggregationType
64631
64752
  }
64632
64753
  ] : [];
@@ -64711,7 +64832,8 @@ function useReport(reportIdArg, options = {}) {
64711
64832
  chartType: prev.chartType ?? sourceReport.chartType
64712
64833
  };
64713
64834
  const normalized = processPivotState(nextFromSource, {
64714
- promoteColumnToRow: shouldPromoteColumnToRowFromPendingFlag
64835
+ promoteColumnToRow: shouldPromoteColumnToRowFromPendingFlag,
64836
+ isBoolAggregationField
64715
64837
  });
64716
64838
  return normalized;
64717
64839
  }
@@ -65051,6 +65173,10 @@ function useReport(reportIdArg, options = {}) {
65051
65173
  aggregation.valueField,
65052
65174
  aggregationTablesByIndex[index]
65053
65175
  );
65176
+ appendPivotColumn(
65177
+ aggregation.valueField2,
65178
+ aggregationTablesByIndex[index]
65179
+ );
65054
65180
  });
65055
65181
  const joinColumns = getJoinColumnsFromTables(effectiveReportBuilderTables);
65056
65182
  const useWideTableColumns = expandReportBuilderColumnsForFlatTable && !pivotState && String(chartType ?? "").toLowerCase() === "table";
@@ -65380,9 +65506,13 @@ function useReport(reportIdArg, options = {}) {
65380
65506
  const aggregationType = String(aggregation.aggregationType);
65381
65507
  const normalizedAggregationType = aggregationType.toLowerCase();
65382
65508
  const tableHint = String(aggregationTablesByIndex[index] ?? "").trim();
65383
- const target = aggregation.valueField && aggregation.valueField.trim().length > 0 ? aggregation.valueField : normalizedAggregationType === "count" ? tableHint || aggregationFallbackTableName : aggregationFallbackTarget;
65509
+ const valueField = String(aggregation.valueField ?? "").trim();
65510
+ const valueField2 = String(aggregation.valueField2 ?? "").trim();
65511
+ const isPercentageRatio = normalizedAggregationType === "percentage" && Boolean(valueField) && Boolean(valueField2);
65512
+ const target = isPercentageRatio ? `${valueField}:${valueField2}` : valueField ? valueField : normalizedAggregationType === "count" || normalizedAggregationType === "percentage" ? tableHint || aggregationFallbackTableName : aggregationFallbackTarget;
65513
+ const isBoolPercentage = normalizedAggregationType === "percentage" && Boolean(valueField) && isBoolAggregationField(valueField, tableHint || void 0) === true;
65384
65514
  return {
65385
- label: `${formatAggregationTypeForDisplay(aggregationType)} ${toTitleCaseLabel(target)}`,
65515
+ label: isPercentageRatio ? `Percent ${toTitleCaseLabel(valueField)} of ${toTitleCaseLabel(valueField2)}` : normalizedAggregationType === "percentage" ? isBoolPercentage ? `Percent ${toTitleCaseLabel(target)}` : `Percent of Total ${toTitleCaseLabel(target)}` : `${formatAggregationTypeForDisplay(aggregationType)} ${toTitleCaseLabel(target)}`,
65386
65516
  value: `${aggregationType}:${target}`
65387
65517
  };
65388
65518
  });
@@ -65390,7 +65520,8 @@ function useReport(reportIdArg, options = {}) {
65390
65520
  aggregationFallbackTableName,
65391
65521
  aggregationFallbackTarget,
65392
65522
  aggregationState,
65393
- aggregationTablesByIndex
65523
+ aggregationTablesByIndex,
65524
+ isBoolAggregationField
65394
65525
  ]);
65395
65526
  const { aggregationBaseOptions, aggregationOptionTableLookup } = useMemo33(() => {
65396
65527
  const joinScoped = fieldOptionsAllowedTableNames ? joinCompatibleSchemaTableNames.filter(
@@ -65416,17 +65547,34 @@ function useReport(reportIdArg, options = {}) {
65416
65547
  if (tableName && !tableLookup.has(countValue)) {
65417
65548
  tableLookup.set(countValue, tableName);
65418
65549
  }
65550
+ if (resolvedGroupRowsBy) {
65551
+ const percentageValue = `percentage:${countTarget}`;
65552
+ if (!seen.has(percentageValue)) {
65553
+ options2.push({
65554
+ value: percentageValue,
65555
+ label: `Percent of Total ${toTitleCaseLabel(countTarget)}`
65556
+ });
65557
+ seen.add(percentageValue);
65558
+ }
65559
+ if (tableName && !tableLookup.has(percentageValue)) {
65560
+ tableLookup.set(percentageValue, tableName);
65561
+ }
65562
+ }
65419
65563
  for (const column of table2.columns ?? []) {
65420
65564
  const fieldName = String(column.field ?? "").trim();
65421
65565
  if (!fieldName) continue;
65566
+ const isBoolColumn = isBoolType(String(column.fieldType ?? "").toLowerCase()) || String(column.jsType ?? "").toLowerCase() === "boolean";
65422
65567
  for (const aggregationType of AGGREGATION_OPTION_TYPES) {
65423
65568
  if (aggregationType === "count") continue;
65424
65569
  if (!canAggregateColumn(column, aggregationType)) continue;
65570
+ const isNumberSharePercentage = aggregationType === "percentage" && !isBoolColumn;
65571
+ if (isNumberSharePercentage && !resolvedGroupRowsBy) continue;
65425
65572
  const value = `${aggregationType}:${fieldName}`;
65426
65573
  if (seen.has(value)) continue;
65574
+ const aggregationWord = aggregationType === "percentage" ? isBoolColumn ? "Percent" : "Percent of Total" : formatAggregationTypeForDisplay(aggregationType);
65427
65575
  options2.push({
65428
65576
  value,
65429
- label: tableName ? `${formatAggregationTypeForDisplay(aggregationType)} ${toTitleCaseLabel(tableName)} ${toTitleCaseLabel(fieldName)}` : `${formatAggregationTypeForDisplay(aggregationType)} ${toTitleCaseLabel(fieldName)}`
65577
+ label: tableName ? `${aggregationWord} ${toTitleCaseLabel(tableName)} ${toTitleCaseLabel(fieldName)}` : `${aggregationWord} ${toTitleCaseLabel(fieldName)}`
65430
65578
  });
65431
65579
  seen.add(value);
65432
65580
  if (tableName && !tableLookup.has(value)) {
@@ -65449,6 +65597,7 @@ function useReport(reportIdArg, options = {}) {
65449
65597
  cleanedAggregations,
65450
65598
  fieldOptionsAllowedTableNames,
65451
65599
  joinCompatibleSchemaTableNames,
65600
+ resolvedGroupRowsBy,
65452
65601
  schemaForReportBuilderState
65453
65602
  ]);
65454
65603
  const { schemaColumnOptions, columnIdentifierLookup } = useMemo33(() => {
@@ -65691,9 +65840,13 @@ function useReport(reportIdArg, options = {}) {
65691
65840
  aggregation.aggregationType ?? ""
65692
65841
  ).toLowerCase();
65693
65842
  const tableHint = String(aggregationTablesByIndex[index] ?? "").trim();
65694
- const currentTarget = aggregation.valueField && aggregation.valueField.trim().length > 0 ? aggregation.valueField : normalizedAggregationType === "count" ? tableHint || aggregationFallbackTableName : aggregationFallbackTarget;
65843
+ const currentValueField = String(aggregation.valueField ?? "").trim();
65844
+ const currentValueField2 = String(aggregation.valueField2 ?? "").trim();
65845
+ const isPercentageRatio = normalizedAggregationType === "percentage" && Boolean(currentValueField) && Boolean(currentValueField2);
65846
+ const currentTarget = isPercentageRatio ? `${currentValueField}:${currentValueField2}` : currentValueField ? currentValueField : normalizedAggregationType === "count" || normalizedAggregationType === "percentage" ? tableHint || aggregationFallbackTableName : aggregationFallbackTarget;
65695
65847
  const currentValue = aggregation.aggregationType ? `${aggregation.aggregationType}:${currentTarget}` : void 0;
65696
- const currentLabel = aggregation.aggregationType ? `${formatAggregationTypeForDisplay(aggregation.aggregationType)} ${toTitleCaseLabel(currentTarget)}` : "Select aggregation";
65848
+ const isBoolPercentage = normalizedAggregationType === "percentage" && Boolean(currentValueField) && isBoolAggregationField(currentValueField, tableHint || void 0) === true;
65849
+ const currentLabel = aggregation.aggregationType ? isPercentageRatio ? `Percent ${toTitleCaseLabel(currentValueField)} of ${toTitleCaseLabel(currentValueField2)}` : normalizedAggregationType === "percentage" ? isBoolPercentage ? `Percent ${toTitleCaseLabel(currentTarget)}` : `Percent of Total ${toTitleCaseLabel(currentTarget)}` : `${formatAggregationTypeForDisplay(aggregation.aggregationType)} ${toTitleCaseLabel(currentTarget)}` : "Select aggregation";
65697
65850
  const options2 = currentValue ? baseOptions.some((option) => option.value === currentValue) ? baseOptions : [{ value: currentValue, label: currentLabel }, ...baseOptions] : baseOptions;
65698
65851
  return {
65699
65852
  key: `aggregation-${index}`,
@@ -65707,7 +65860,8 @@ function useReport(reportIdArg, options = {}) {
65707
65860
  aggregationFallbackTarget,
65708
65861
  aggregationState,
65709
65862
  aggregationBaseOptions,
65710
- aggregationTablesByIndex
65863
+ aggregationTablesByIndex,
65864
+ isBoolAggregationField
65711
65865
  ]);
65712
65866
  const nextPivot = useMemo33(() => {
65713
65867
  if (!sourceReport) return null;
@@ -67480,10 +67634,12 @@ function useReport(reportIdArg, options = {}) {
67480
67634
  preferredTables
67481
67635
  );
67482
67636
  const existing = next.aggregationState[index];
67637
+ const preservedValueField2 = aggregationType !== "percentage" && existing?.valueField2 ? { valueField2: existing.valueField2 } : {};
67483
67638
  const nextAggregation = {
67484
- ...existing?.valueField2 ? { valueField2: existing.valueField2 } : {},
67639
+ ...preservedValueField2,
67485
67640
  aggregationType,
67486
67641
  ...normalizedSelection.valueField ? { valueField: normalizedSelection.valueField } : {},
67642
+ ...normalizedSelection.valueField2 ? { valueField2: normalizedSelection.valueField2 } : {},
67487
67643
  ...normalizedSelection.table ? { valueFieldTable: normalizedSelection.table } : {}
67488
67644
  };
67489
67645
  if (index === next.aggregationState.length) {
@@ -67520,6 +67676,7 @@ function useReport(reportIdArg, options = {}) {
67520
67676
  {
67521
67677
  aggregationType,
67522
67678
  ...normalizedSelection.valueField ? { valueField: normalizedSelection.valueField } : {},
67679
+ ...normalizedSelection.valueField2 ? { valueField2: normalizedSelection.valueField2 } : {},
67523
67680
  ...normalizedSelection.table ? { valueFieldTable: normalizedSelection.table } : {}
67524
67681
  }
67525
67682
  ];
@@ -67644,7 +67801,8 @@ function useReport(reportIdArg, options = {}) {
67644
67801
  next.chartType = effectiveNextState.chartType || void 0;
67645
67802
  const normalizedNext = processPivotState(next, {
67646
67803
  nextState: effectiveNextState,
67647
- promoteColumnToRow: false
67804
+ promoteColumnToRow: false,
67805
+ isBoolAggregationField
67648
67806
  });
67649
67807
  return normalizedNext;
67650
67808
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillsql/react",
3
- "version": "2.16.46",
3
+ "version": "2.16.48",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {