@canvas-components/pivot-table 0.2.2 → 0.2.3

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
@@ -432,6 +432,101 @@ function resolvePivotIndicatorAggregator(indicator, registry) {
432
432
  }
433
433
  return aggregator;
434
434
  }
435
+ var DATE_PATTERNS = {
436
+ dateSlash: "YYYY/MM/DD",
437
+ dateDash: "YYYY-MM-DD",
438
+ dateFullCN: "YYYY\u5E74M\u6708D\u65E5",
439
+ time: "HH:mm:ss",
440
+ timeShort: "HH:mm",
441
+ datetime: "YYYY/MM/DD HH:mm:ss",
442
+ datetime12: "YYYY/MM/DD Ah:mm"
443
+ };
444
+ function formatPivotIndicatorValue(value, dataFormat) {
445
+ if (typeof dataFormat === "object") {
446
+ if (dataFormat.type === "custom" && dataFormat.pattern) {
447
+ return utils.formatCustomNumberPattern(value, dataFormat.pattern);
448
+ }
449
+ return formatPivotIndicatorValueWithOptions(value, dataFormat);
450
+ }
451
+ if (dataFormat === "default" || dataFormat === "text") {
452
+ return String(value ?? "");
453
+ }
454
+ if (dataFormat === "number") {
455
+ return utils.formatNumberValue(value, {
456
+ decimalPlaces: "auto",
457
+ thousandsSeparator: false
458
+ });
459
+ }
460
+ if (dataFormat === "thousands") {
461
+ return utils.formatNumberValue(value, {
462
+ decimalPlaces: "auto",
463
+ thousandsSeparator: true
464
+ });
465
+ }
466
+ if (dataFormat === "decimal") {
467
+ return utils.formatNumberValue(value, {
468
+ decimalPlaces: 2,
469
+ thousandsSeparator: true
470
+ });
471
+ }
472
+ if (dataFormat === "percent" || dataFormat === "percentDecimal") {
473
+ return utils.formatPercentValue(value, dataFormat === "percentDecimal" ? 2 : 0);
474
+ }
475
+ if (dataFormat === "scientific") return utils.formatScientificValue(value);
476
+ if (dataFormat === "numberToChineseUpper") {
477
+ return utils.formatChineseNumber(value, "financial") ?? String(value ?? "");
478
+ }
479
+ if (dataFormat === "chineseUpperToNumber") {
480
+ const parsed = utils.parseChineseNumber(value);
481
+ return parsed == null ? String(value ?? "") : String(parsed);
482
+ }
483
+ if (dataFormat === "cny" || dataFormat === "cnyDecimal") {
484
+ return utils.formatNumberValue(value, {
485
+ decimalPlaces: dataFormat === "cnyDecimal" ? 2 : 0,
486
+ thousandsSeparator: true,
487
+ symbol: "\xA5"
488
+ });
489
+ }
490
+ if (dataFormat === "usd" || dataFormat === "usdDecimal") {
491
+ return utils.formatNumberValue(value, {
492
+ decimalPlaces: dataFormat === "usdDecimal" ? 2 : 0,
493
+ thousandsSeparator: true,
494
+ symbol: "$"
495
+ });
496
+ }
497
+ const pattern = DATE_PATTERNS[dataFormat];
498
+ return pattern ? utils.formatDateTimeValue(value, pattern) ?? String(value ?? "") : String(value ?? "");
499
+ }
500
+ function formatPivotIndicatorValueWithOptions(value, dataFormat) {
501
+ if (dataFormat.pattern && dataFormat.type !== "custom") {
502
+ const formatted = utils.formatDateTimeValue(value, dataFormat.pattern);
503
+ if (formatted != null) return formatted;
504
+ }
505
+ const decimalPlaces = Math.max(
506
+ 0,
507
+ Math.min(20, dataFormat.decimalPlaces ?? 2)
508
+ );
509
+ if (dataFormat.type === "percent" || dataFormat.type === "percentDecimal") {
510
+ return utils.formatPercentValue(value, decimalPlaces);
511
+ }
512
+ if (dataFormat.type === "scientific") {
513
+ return utils.formatScientificValue(value, decimalPlaces);
514
+ }
515
+ if (dataFormat.type === "cny" || dataFormat.type === "cnyDecimal" || dataFormat.type === "usd" || dataFormat.type === "usdDecimal") {
516
+ return utils.formatNumberValue(value, {
517
+ decimalPlaces,
518
+ thousandsSeparator: dataFormat.useGrouping ?? true,
519
+ symbol: dataFormat.symbol ?? (dataFormat.type.startsWith("usd") ? "$" : "\xA5")
520
+ });
521
+ }
522
+ if (dataFormat.type === "number" || dataFormat.type === "thousands" || dataFormat.type === "decimal") {
523
+ return utils.formatNumberValue(value, {
524
+ decimalPlaces,
525
+ thousandsSeparator: dataFormat.useGrouping ?? dataFormat.type !== "number"
526
+ });
527
+ }
528
+ return formatPivotIndicatorValue(value, dataFormat.type);
529
+ }
435
530
 
436
531
  // src/domain/aggregation/aggregate-records.ts
437
532
  function aggregatePivotRecords(params) {
@@ -496,7 +591,7 @@ function aggregatePivotRecords(params) {
496
591
  recordCount: item.recordIndexes.length,
497
592
  rowPathKey: item.rowPathKey,
498
593
  columnPathKey: item.columnPathKey
499
- }) ?? formatAggregateValue(value, item.indicator.precision),
594
+ }) ?? (item.indicator.dataFormat ? formatPivotIndicatorValue(value, item.indicator.dataFormat) : formatAggregateValue(value, item.indicator.precision)),
500
595
  recordCount: item.recordIndexes.length,
501
596
  recordIndexes: item.recordIndexes
502
597
  });
@@ -1046,7 +1141,6 @@ function resolveThumbOffset(scroll, maxScroll, track, thumb) {
1046
1141
  // src/domain/layout/pivot-layout.ts
1047
1142
  var DEFAULT_ROW_HEIGHT = 40;
1048
1143
  var DEFAULT_COLUMN_WIDTH = 120;
1049
- var DEFAULT_HEADER_SIZE = 40;
1050
1144
  var DEFAULT_ROW_DIMENSION_WIDTH2 = 140;
1051
1145
  var SCROLLBAR_THICKNESS2 = 10;
1052
1146
  function buildPivotLayoutSnapshot(params) {
@@ -1078,7 +1172,7 @@ function buildPivotLayoutSnapshot(params) {
1078
1172
  options.columns.length + (indicatorLayout === "columns" && hasIndicators ? 1 : 0),
1079
1173
  1
1080
1174
  );
1081
- const columnHeaderHeight = columnHeaderLevels * DEFAULT_HEADER_SIZE;
1175
+ const columnHeaderHeight = columnHeaderLevels * rowHeight;
1082
1176
  const reportFilterHeight = (options.filterDimensions?.length ?? 0) * rowHeight;
1083
1177
  const tableHeaderHeight = reportFilterHeight + columnHeaderHeight;
1084
1178
  const rawBodyWidth = Math.max(width - rowHeaderWidth, 0);
@@ -1212,6 +1306,7 @@ function buildPivotLayoutSnapshot(params) {
1212
1306
  rowHeaderWidth,
1213
1307
  scrollLeft: scroll.left,
1214
1308
  levels: columnHeaderLevels,
1309
+ headerSize: rowHeight,
1215
1310
  headerTop: reportFilterHeight,
1216
1311
  expandedNodeIds: params.expandedColumnNodeIds
1217
1312
  });
@@ -1612,7 +1707,7 @@ function buildColumnHeaderCells(params) {
1612
1707
  x,
1613
1708
  y: params.headerTop,
1614
1709
  width: columnWidth,
1615
- height: DEFAULT_HEADER_SIZE * (params.levels - (slot.indicatorKey ? 1 : 0))
1710
+ height: params.headerSize * (params.levels - (slot.indicatorKey ? 1 : 0))
1616
1711
  }
1617
1712
  });
1618
1713
  } else {
@@ -1652,9 +1747,9 @@ function buildColumnHeaderCells(params) {
1652
1747
  ),
1653
1748
  rect: {
1654
1749
  x,
1655
- y: params.headerTop + level * DEFAULT_HEADER_SIZE,
1750
+ y: params.headerTop + level * params.headerSize,
1656
1751
  width: columnWidth,
1657
- height: DEFAULT_HEADER_SIZE
1752
+ height: params.headerSize
1658
1753
  }
1659
1754
  });
1660
1755
  });
@@ -1681,9 +1776,9 @@ function buildColumnHeaderCells(params) {
1681
1776
  sortDirection: null,
1682
1777
  rect: {
1683
1778
  x,
1684
- y: params.headerTop + (params.levels - 1) * DEFAULT_HEADER_SIZE,
1779
+ y: params.headerTop + (params.levels - 1) * params.headerSize,
1685
1780
  width: columnWidth,
1686
- height: DEFAULT_HEADER_SIZE
1781
+ height: params.headerSize
1687
1782
  }
1688
1783
  });
1689
1784
  }
@@ -2720,6 +2815,15 @@ function resolvePivotSelectionRect(layout, selection) {
2720
2815
  const visibleStartY = Math.max(startY, clipRect.y);
2721
2816
  const visibleEndY = Math.min(endY, clipRect.y + clipRect.height);
2722
2817
  if (visibleStartX >= visibleEndX || visibleStartY >= visibleEndY) return null;
2818
+ const singleCell = kind === "cell" && indexes.startRowIndex === indexes.endRowIndex && indexes.startColumnIndex === indexes.endColumnIndex;
2819
+ if (singleCell) {
2820
+ return {
2821
+ x: startX,
2822
+ y: startY,
2823
+ width: endX - startX,
2824
+ height: endY - startY
2825
+ };
2826
+ }
2723
2827
  return {
2724
2828
  x: visibleStartX,
2725
2829
  y: visibleStartY,
@@ -3272,8 +3376,16 @@ function renderPivotTable(params) {
3272
3376
  );
3273
3377
  layout.columnHeaderCells.forEach(
3274
3378
  (cell) => drawPivotGridRect(context, cell.rect, theme.borderColor, {
3275
- skipTop: cell.rect.y > 0,
3276
- skipLeft: true,
3379
+ skipTop: hasColumnHeaderCellAbove(
3380
+ layout.columnHeaderCells,
3381
+ cell,
3382
+ layout.columnHeaderRect.y
3383
+ ),
3384
+ skipLeft: hasColumnHeaderCellLeft(
3385
+ layout.columnHeaderCells,
3386
+ cell,
3387
+ layout.columnHeaderRect.x
3388
+ ),
3277
3389
  skipRight: isSameCoordinate2(
3278
3390
  cell.rect.x + cell.rect.width,
3279
3391
  layout.tableWidth
@@ -3343,10 +3455,12 @@ function renderPivotTable(params) {
3343
3455
  drawPivotResizeGuide(context, params.resizeGuideX, contentHeight);
3344
3456
  }
3345
3457
  context.restore();
3346
- context.save();
3347
- clipPivotViewport(context, tableViewportRect);
3348
- drawPivotViewportRightBorder(context, tableViewportRect, theme.borderColor);
3349
- context.restore();
3458
+ if (!layout.scrollbar.vertical) {
3459
+ context.save();
3460
+ clipPivotViewport(context, tableViewportRect);
3461
+ drawPivotViewportRightBorder(context, tableViewportRect, theme.borderColor);
3462
+ context.restore();
3463
+ }
3350
3464
  drawPivotViewportBorder(
3351
3465
  context,
3352
3466
  { x: 0, y: 0, width: viewportWidth, height: contentHeight },
@@ -3375,6 +3489,34 @@ function renderPivotTable(params) {
3375
3489
  bodyCellCount: layout.bodyCells.length
3376
3490
  };
3377
3491
  }
3492
+ function hasColumnHeaderCellAbove(cells, cell, headerTop) {
3493
+ if (isSameCoordinate2(cell.rect.y, headerTop)) return headerTop > 0;
3494
+ const left = cell.rect.x;
3495
+ const right = left + cell.rect.width;
3496
+ return cells.some((candidate) => {
3497
+ if (candidate === cell) return false;
3498
+ const candidateBottom = candidate.rect.y + candidate.rect.height;
3499
+ if (!isSameCoordinate2(candidateBottom, cell.rect.y)) return false;
3500
+ const candidateLeft = candidate.rect.x;
3501
+ const candidateRight = candidateLeft + candidate.rect.width;
3502
+ return candidateLeft < right && candidateRight > left;
3503
+ });
3504
+ }
3505
+ function hasColumnHeaderCellLeft(cells, cell, headerLeft) {
3506
+ if (cell.rect.x <= headerLeft + 0.01) {
3507
+ return true;
3508
+ }
3509
+ const top = cell.rect.y;
3510
+ const bottom = top + cell.rect.height;
3511
+ return cells.some((candidate) => {
3512
+ if (candidate === cell) return false;
3513
+ const candidateRight = candidate.rect.x + candidate.rect.width;
3514
+ if (!isSameCoordinate2(candidateRight, cell.rect.x)) return false;
3515
+ const candidateTop = candidate.rect.y;
3516
+ const candidateBottom = candidateTop + candidate.rect.height;
3517
+ return candidateTop < bottom && candidateBottom > top;
3518
+ });
3519
+ }
3378
3520
  function resolvePivotViewportWidth(layout) {
3379
3521
  const horizontalRight = layout.scrollbar.horizontal ? layout.scrollbar.horizontal.increaseButton.x + layout.scrollbar.horizontal.increaseButton.width : 0;
3380
3522
  const verticalRight = layout.scrollbar.vertical ? layout.scrollbar.vertical.decreaseButton.x + layout.scrollbar.vertical.decreaseButton.width : 0;
@@ -3677,7 +3819,7 @@ function isSameCoordinate2(first, second) {
3677
3819
  return Math.abs(first - second) < 0.01;
3678
3820
  }
3679
3821
  function resolveBodyDisplayText(cell, indicator) {
3680
- if (!indicator || indicator.formatter || typeof cell.value !== "number") {
3822
+ if (!indicator || indicator.formatter || indicator.dataFormat || typeof cell.value !== "number") {
3681
3823
  return cell.formattedValue;
3682
3824
  }
3683
3825
  if (indicator.cellType === "percent") {
@@ -3814,10 +3956,18 @@ function drawSortIcon(context, rect, direction, rightPadding) {
3814
3956
  function drawText(context, text, rect, color, padding, align) {
3815
3957
  const availableWidth = Math.max(rect.width - padding * 2, 0);
3816
3958
  const value = truncateText(context, text, availableWidth);
3959
+ context.textBaseline = "alphabetic";
3960
+ const metrics = context.measureText(value);
3961
+ const ascent = metrics.actualBoundingBoxAscent ?? 0;
3962
+ const descent = metrics.actualBoundingBoxDescent ?? 0;
3963
+ const hasGlyphBounds = ascent > 0 || descent > 0;
3964
+ const centerY = rect.y + rect.height / 2;
3817
3965
  context.fillStyle = color;
3818
3966
  context.textAlign = align;
3967
+ context.textBaseline = hasGlyphBounds ? "alphabetic" : "middle";
3819
3968
  const x = align === "right" ? rect.x + rect.width - padding : align === "center" ? rect.x + rect.width / 2 : rect.x + padding;
3820
- context.fillText(value, x, rect.y + rect.height / 2, availableWidth);
3969
+ const y = hasGlyphBounds ? centerY + (ascent - descent) / 2 : centerY;
3970
+ context.fillText(value, x, y, availableWidth);
3821
3971
  }
3822
3972
  function truncateText(context, text, maxWidth) {
3823
3973
  if (context.measureText(text).width <= maxWidth) return text;
@@ -3865,7 +4015,7 @@ function hitTestPivotLayout(layout, x, y) {
3865
4015
  onSortIcon: false
3866
4016
  };
3867
4017
  }
3868
- const bodyCell = findLastContaining(layout.bodyCells, x, y);
4018
+ const bodyCell = contains(layout.bodyRect, x, y) ? findLastContaining(layout.bodyCells, x, y) : void 0;
3869
4019
  if (bodyCell) {
3870
4020
  return {
3871
4021
  region: "body",
@@ -3875,7 +4025,7 @@ function hitTestPivotLayout(layout, x, y) {
3875
4025
  onSortIcon: false
3876
4026
  };
3877
4027
  }
3878
- const rowHeaderCell = findLastContaining(layout.rowHeaderCells, x, y);
4028
+ const rowHeaderCell = contains(layout.rowHeaderRect, x, y) ? findLastContaining(layout.rowHeaderCells, x, y) : void 0;
3879
4029
  if (rowHeaderCell) {
3880
4030
  return {
3881
4031
  region: "rowHeader",
@@ -3885,9 +4035,7 @@ function hitTestPivotLayout(layout, x, y) {
3885
4035
  onSortIcon: isSortIconHit(rowHeaderCell, x)
3886
4036
  };
3887
4037
  }
3888
- const columnHeaderCell = layout.columnHeaderCells.find(
3889
- (cell) => contains(cell.rect, x, y)
3890
- );
4038
+ const columnHeaderCell = contains(layout.columnHeaderRect, x, y) ? layout.columnHeaderCells.find((cell) => contains(cell.rect, x, y)) : void 0;
3891
4039
  if (columnHeaderCell) {
3892
4040
  return {
3893
4041
  region: "columnHeader",
@@ -4403,7 +4551,7 @@ var PivotTable = class {
4403
4551
  }
4404
4552
  if (hit.reportFilterCell) return;
4405
4553
  if (hit.bodyCell) {
4406
- this.selectCell(toCellAddress(hit.bodyCell), event.shiftKey);
4554
+ this.selectPointerCell(toCellAddress(hit.bodyCell), event.shiftKey);
4407
4555
  return;
4408
4556
  }
4409
4557
  const header = hit.headerCell;
@@ -4505,7 +4653,76 @@ var PivotTable = class {
4505
4653
  });
4506
4654
  __publicField(this, "handleContextMenu", (event) => {
4507
4655
  const hit = this.resolveHit(event);
4508
- if (!hit?.bodyCell || !this.core || !this.layout) return;
4656
+ if (!hit || !this.core || !this.layout) return;
4657
+ if (hit.reportFilterCell) {
4658
+ const cell = hit.reportFilterCell;
4659
+ event.preventDefault();
4660
+ this.options.ui?.onContextMenuRequest?.({
4661
+ x: event.clientX,
4662
+ y: event.clientY,
4663
+ target: "header",
4664
+ title: cell.label,
4665
+ axis: "filter",
4666
+ onCopy: () => this.copyPivotText(`${cell.label} ${cell.valueLabel}`),
4667
+ onOpenFilter: () => this.openReportFilterPanel(cell),
4668
+ ...this.createContextMenuSettings(),
4669
+ onExportCsv: () => downloadBlob(
4670
+ new Blob([this.exportCsv()], { type: "text/csv;charset=utf-8" }),
4671
+ "pivot-table.csv"
4672
+ ),
4673
+ onExportXlsx: () => downloadBlob(
4674
+ new Blob([toArrayBuffer(this.exportXlsx())], {
4675
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
4676
+ }),
4677
+ "pivot-table.xlsx"
4678
+ ),
4679
+ onClose: () => this.options.ui?.onContextMenuRequest?.(null)
4680
+ });
4681
+ return;
4682
+ }
4683
+ if (hit.headerCell) {
4684
+ const header = hit.headerCell;
4685
+ const model2 = this.core.getPivotModel();
4686
+ const node = header.nodeId ? (header.axis === "row" ? model2?.rowNodes : model2?.columnNodes)?.get(
4687
+ header.nodeId
4688
+ ) : void 0;
4689
+ const dimensionKey = header.dimensionKey ?? node?.dimensionKey;
4690
+ event.preventDefault();
4691
+ this.options.ui?.onContextMenuRequest?.({
4692
+ x: event.clientX,
4693
+ y: event.clientY,
4694
+ target: "header",
4695
+ title: header.label,
4696
+ axis: header.axis,
4697
+ nodeKind: node?.kind ?? header.kind,
4698
+ indicatorKey: header.indicatorKey ?? void 0,
4699
+ onCopy: () => this.copyPivotText(header.label),
4700
+ sortDirection: header.sortDirection,
4701
+ onSortAsc: header.sortable && dimensionKey ? () => this.setDimensionSort(header.axis, dimensionKey, "asc") : void 0,
4702
+ onSortDesc: header.sortable && dimensionKey ? () => this.setDimensionSort(header.axis, dimensionKey, "desc") : void 0,
4703
+ onClearSort: header.sortable && dimensionKey ? () => this.setDimensionSort(header.axis, dimensionKey, null) : void 0,
4704
+ onOpenFilter: header.filterable ? () => this.openFilterPanel(hit) : void 0,
4705
+ expanded: header.expandable ? header.expanded : void 0,
4706
+ onExpand: header.expandable && header.nodeId ? () => header.axis === "row" ? this.expandRowNode(header.nodeId) : this.expandColumnNode(header.nodeId) : void 0,
4707
+ onCollapse: header.expandable && header.nodeId ? () => header.axis === "row" ? this.collapseRowNode(header.nodeId) : this.collapseColumnNode(header.nodeId) : void 0,
4708
+ onExpandAll: () => this.expandAll(header.axis),
4709
+ onCollapseAll: () => this.collapseAll(header.axis),
4710
+ ...this.createContextMenuSettings(header.indicatorKey ?? void 0),
4711
+ onExportCsv: () => downloadBlob(
4712
+ new Blob([this.exportCsv()], { type: "text/csv;charset=utf-8" }),
4713
+ "pivot-table.csv"
4714
+ ),
4715
+ onExportXlsx: () => downloadBlob(
4716
+ new Blob([toArrayBuffer(this.exportXlsx())], {
4717
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
4718
+ }),
4719
+ "pivot-table.xlsx"
4720
+ ),
4721
+ onClose: () => this.options.ui?.onContextMenuRequest?.(null)
4722
+ });
4723
+ return;
4724
+ }
4725
+ if (!hit.bodyCell) return;
4509
4726
  event.preventDefault();
4510
4727
  const address = toCellAddress(hit.bodyCell);
4511
4728
  const currentRange = resolvePivotSelectionRange(
@@ -4515,7 +4732,7 @@ var PivotTable = class {
4515
4732
  const insideCurrentRange = Boolean(
4516
4733
  currentRange && hit.bodyCell.rowIndex >= currentRange.startRowIndex && hit.bodyCell.rowIndex <= currentRange.endRowIndex && hit.bodyCell.columnIndex >= currentRange.startColumnIndex && hit.bodyCell.columnIndex <= currentRange.endColumnIndex
4517
4734
  );
4518
- if (!insideCurrentRange) this.selectCell(address);
4735
+ if (!insideCurrentRange) this.selectPointerCell(address);
4519
4736
  const selectionRange = resolvePivotSelectionRange(
4520
4737
  this.layout,
4521
4738
  this.core.getSelection()
@@ -4525,15 +4742,28 @@ var PivotTable = class {
4525
4742
  columnPathKey: hit.bodyCell.columnPathKey,
4526
4743
  indicatorKey: hit.bodyCell.indicatorKey
4527
4744
  });
4745
+ const model = this.core.getPivotModel();
4746
+ const rowNode = hit.bodyCell.rowNodeId ? model?.rowNodes.get(hit.bodyCell.rowNodeId) : void 0;
4747
+ const summary = rowNode?.kind === "subtotal" || rowNode?.kind === "grandTotal";
4748
+ const indicator = this.options.indicators.find(
4749
+ (item) => item.key === hit.bodyCell?.indicatorKey
4750
+ );
4751
+ const summaryAggregator = typeof indicator?.aggregator === "string" && isBuiltInPivotAggregator(indicator.aggregator) ? indicator.aggregator : "sum";
4528
4752
  this.options.ui?.onContextMenuRequest?.({
4529
4753
  x: event.clientX,
4530
4754
  y: event.clientY,
4755
+ target: summary ? "summary" : "body",
4756
+ title: summary ? rowNode.label : void 0,
4757
+ nodeKind: rowNode?.kind,
4758
+ indicatorKey: hit.bodyCell.indicatorKey,
4531
4759
  recordCount: records.length,
4760
+ summaryAggregator: summary ? summaryAggregator : void 0,
4761
+ summaryPrecision: summary ? indicator?.precision : void 0,
4532
4762
  onCopy: () => {
4533
- const model = this.core?.getPivotModel();
4534
- const text = model ? createPivotRangeSelectionText({
4763
+ const model2 = this.core?.getPivotModel();
4764
+ const text = model2 ? createPivotRangeSelectionText({
4535
4765
  layout: this.layout,
4536
- model,
4766
+ model: model2,
4537
4767
  startRowIndex: hit.bodyCell.rowIndex,
4538
4768
  endRowIndex: hit.bodyCell.rowIndex,
4539
4769
  startColumnIndex: hit.bodyCell.columnIndex,
@@ -4558,11 +4788,11 @@ var PivotTable = class {
4558
4788
  columnCount: this.layout.columnSlots.length
4559
4789
  },
4560
4790
  onCopyCustomSelection: (range) => {
4561
- const model = this.core?.getPivotModel();
4562
- if (!model || !this.layout) return;
4791
+ const model2 = this.core?.getPivotModel();
4792
+ if (!model2 || !this.layout) return;
4563
4793
  const text = createPivotRangeSelectionText({
4564
4794
  layout: this.layout,
4565
- model,
4795
+ model: model2,
4566
4796
  startRowIndex: range.startRow - 1,
4567
4797
  endRowIndex: range.endRow - 1,
4568
4798
  startColumnIndex: range.startColumn - 1,
@@ -4572,6 +4802,7 @@ var PivotTable = class {
4572
4802
  this.copyPivotText(text);
4573
4803
  }
4574
4804
  },
4805
+ ...this.createContextMenuSettings(hit.bodyCell.indicatorKey),
4575
4806
  onExportCsv: () => downloadBlob(
4576
4807
  new Blob([this.exportCsv()], { type: "text/csv;charset=utf-8" }),
4577
4808
  "pivot-table.csv"
@@ -4587,9 +4818,9 @@ var PivotTable = class {
4587
4818
  });
4588
4819
  __publicField(this, "handlePointerDown", (event) => {
4589
4820
  if (!this.layout || !this.host) return;
4590
- this.stopWheelScrollSmoothing();
4591
4821
  const scrollbarHit = this.resolveScrollbarHit(event);
4592
4822
  if (scrollbarHit && event.button === 0) {
4823
+ this.stopWheelScrollSmoothing();
4593
4824
  this.beginScrollbarInteraction(event, scrollbarHit);
4594
4825
  return;
4595
4826
  }
@@ -4600,7 +4831,7 @@ var PivotTable = class {
4600
4831
  if (cellHit?.bodyCell) {
4601
4832
  const address = toCellAddress(cellHit.bodyCell);
4602
4833
  this.host.root.focus({ preventScroll: true });
4603
- const selection = this.selectCell(address, event.shiftKey);
4834
+ const selection = this.selectPointerCell(address, event.shiftKey);
4604
4835
  const text = this.resolveBodyText(cellHit.bodyCell);
4605
4836
  const textOriginX = this.resolveBodyTextOriginX(cellHit.bodyCell, text);
4606
4837
  const anchorOffset = text ? resolvePivotTextOffset({
@@ -4686,8 +4917,12 @@ var PivotTable = class {
4686
4917
  if (selection) this.setSelection(selection);
4687
4918
  this.host.root.setPointerCapture(event.pointerId);
4688
4919
  }
4920
+ if (cellHit?.headerCell || cellHit?.reportFilterCell) {
4921
+ event.preventDefault();
4922
+ }
4689
4923
  return;
4690
4924
  }
4925
+ this.stopWheelScrollSmoothing();
4691
4926
  event.preventDefault();
4692
4927
  const startWidth = hit.kind === "rowDimension" ? this.layout.rowDimensionWidths[hit.index] ?? 140 : this.layout.columnWidths[hit.index] ?? this.layout.columnWidth;
4693
4928
  this.resizeSession = {
@@ -4877,9 +5112,16 @@ var PivotTable = class {
4877
5112
  }
4878
5113
  /** 选中指定值单元格。 */
4879
5114
  selectCell(address, extend = false) {
5115
+ return this.commitCellSelection(address, extend, true);
5116
+ }
5117
+ /** 鼠标命中的单元格已在视口内,选中时不再校正滚动位置。 */
5118
+ selectPointerCell(address, extend = false) {
5119
+ return this.commitCellSelection(address, extend, false);
5120
+ }
5121
+ commitCellSelection(address, extend, reveal) {
4880
5122
  const selection = this.core?.selectCell(address, extend) ?? null;
4881
5123
  if (selection) {
4882
- this.scrollToCell(address);
5124
+ if (reveal) this.scrollToCell(address);
4883
5125
  this.scheduleRender();
4884
5126
  }
4885
5127
  return selection;
@@ -5037,6 +5279,27 @@ var PivotTable = class {
5037
5279
  this.advanceWheelScrollSmoothing(nextTime);
5038
5280
  });
5039
5281
  }
5282
+ createContextMenuSettings(indicatorKey) {
5283
+ const indicator = indicatorKey ? this.options.indicators.find((item) => item.key === indicatorKey) : void 0;
5284
+ return {
5285
+ density: resolvePivotTableDensity(this.options.rowHeight),
5286
+ onChangeDensity: (density) => {
5287
+ this.updateOptions({
5288
+ ...this.options,
5289
+ rowHeight: resolvePivotTableDensityRowHeight(density)
5290
+ });
5291
+ },
5292
+ dataFormat: indicator?.dataFormat ?? "default",
5293
+ onChangeDataFormat: indicator ? (dataFormat) => {
5294
+ this.updateOptions({
5295
+ ...this.options,
5296
+ indicators: this.options.indicators.map(
5297
+ (item) => item.key === indicator.key ? { ...item, dataFormat } : item
5298
+ )
5299
+ });
5300
+ } : void 0
5301
+ };
5302
+ }
5040
5303
  updateTooltip(hit, event) {
5041
5304
  if (!this.host) return;
5042
5305
  const cell = hit?.bodyCell ?? hit?.headerCell;
@@ -5242,7 +5505,7 @@ var PivotTable = class {
5242
5505
  const indicator = this.options.indicators.find(
5243
5506
  (item) => item.key === cell.indicatorKey
5244
5507
  );
5245
- if (!indicator || indicator.formatter || typeof cell.value !== "number")
5508
+ if (!indicator || indicator.formatter || indicator.dataFormat || typeof cell.value !== "number")
5246
5509
  return cell.formattedValue;
5247
5510
  if (indicator.cellType === "percent")
5248
5511
  return `${(cell.value * 100).toFixed(indicator.precision ?? 2)}%`;
@@ -5604,6 +5867,9 @@ function toCellAddress(cell) {
5604
5867
  indicatorKey: cell.indicatorKey
5605
5868
  };
5606
5869
  }
5870
+ function isBuiltInPivotAggregator(value) {
5871
+ return ["sum", "avg", "count", "distinctCount", "min", "max"].includes(value);
5872
+ }
5607
5873
  function isSameAddress(left, right) {
5608
5874
  return Boolean(
5609
5875
  left === right || left && right && left.rowNodeId === right.rowNodeId && left.columnNodeId === right.columnNodeId && left.indicatorKey === right.indicatorKey
@@ -5616,6 +5882,20 @@ function resolveNavigationDirection(key) {
5616
5882
  if (key === "ArrowRight") return { row: 0, column: 1 };
5617
5883
  return null;
5618
5884
  }
5885
+ function resolvePivotTableDensity(rowHeight) {
5886
+ if (rowHeight == null) return "auto";
5887
+ if (rowHeight <= 26) return "ultra-compact";
5888
+ if (rowHeight <= 32) return "compact";
5889
+ if (rowHeight >= 56) return "comfortable";
5890
+ return "middle";
5891
+ }
5892
+ function resolvePivotTableDensityRowHeight(density) {
5893
+ if (density === "comfortable") return 56;
5894
+ if (density === "middle") return 48;
5895
+ if (density === "compact") return 32;
5896
+ if (density === "ultra-compact") return 26;
5897
+ return void 0;
5898
+ }
5619
5899
  function resolveAdjacentAddress(layout, address, direction) {
5620
5900
  const indexes = resolveAddressIndexes3(layout, address);
5621
5901
  if (!indexes) return null;