@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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { stringifyDataIndex, getValueByDataIndex, dividePreciseNumber, addPreciseNumbers, clamp, serializeCsvRows, isPointInRect, copyTextSync, copyText } from '@canvas-components/utils';
1
+ import { stringifyDataIndex, getValueByDataIndex, dividePreciseNumber, addPreciseNumbers, clamp, serializeCsvRows, isPointInRect, copyTextSync, copyText, formatCustomNumberPattern, formatNumberValue, formatPercentValue, formatScientificValue, formatChineseNumber, parseChineseNumber, formatDateTimeValue } from '@canvas-components/utils';
2
2
  export { getValueByDataIndex, isSameDataIndex, stringifyDataIndex } from '@canvas-components/utils';
3
3
  import * as XLSX from 'xlsx';
4
4
 
@@ -411,6 +411,101 @@ function resolvePivotIndicatorAggregator(indicator, registry) {
411
411
  }
412
412
  return aggregator;
413
413
  }
414
+ var DATE_PATTERNS = {
415
+ dateSlash: "YYYY/MM/DD",
416
+ dateDash: "YYYY-MM-DD",
417
+ dateFullCN: "YYYY\u5E74M\u6708D\u65E5",
418
+ time: "HH:mm:ss",
419
+ timeShort: "HH:mm",
420
+ datetime: "YYYY/MM/DD HH:mm:ss",
421
+ datetime12: "YYYY/MM/DD Ah:mm"
422
+ };
423
+ function formatPivotIndicatorValue(value, dataFormat) {
424
+ if (typeof dataFormat === "object") {
425
+ if (dataFormat.type === "custom" && dataFormat.pattern) {
426
+ return formatCustomNumberPattern(value, dataFormat.pattern);
427
+ }
428
+ return formatPivotIndicatorValueWithOptions(value, dataFormat);
429
+ }
430
+ if (dataFormat === "default" || dataFormat === "text") {
431
+ return String(value ?? "");
432
+ }
433
+ if (dataFormat === "number") {
434
+ return formatNumberValue(value, {
435
+ decimalPlaces: "auto",
436
+ thousandsSeparator: false
437
+ });
438
+ }
439
+ if (dataFormat === "thousands") {
440
+ return formatNumberValue(value, {
441
+ decimalPlaces: "auto",
442
+ thousandsSeparator: true
443
+ });
444
+ }
445
+ if (dataFormat === "decimal") {
446
+ return formatNumberValue(value, {
447
+ decimalPlaces: 2,
448
+ thousandsSeparator: true
449
+ });
450
+ }
451
+ if (dataFormat === "percent" || dataFormat === "percentDecimal") {
452
+ return formatPercentValue(value, dataFormat === "percentDecimal" ? 2 : 0);
453
+ }
454
+ if (dataFormat === "scientific") return formatScientificValue(value);
455
+ if (dataFormat === "numberToChineseUpper") {
456
+ return formatChineseNumber(value, "financial") ?? String(value ?? "");
457
+ }
458
+ if (dataFormat === "chineseUpperToNumber") {
459
+ const parsed = parseChineseNumber(value);
460
+ return parsed == null ? String(value ?? "") : String(parsed);
461
+ }
462
+ if (dataFormat === "cny" || dataFormat === "cnyDecimal") {
463
+ return formatNumberValue(value, {
464
+ decimalPlaces: dataFormat === "cnyDecimal" ? 2 : 0,
465
+ thousandsSeparator: true,
466
+ symbol: "\xA5"
467
+ });
468
+ }
469
+ if (dataFormat === "usd" || dataFormat === "usdDecimal") {
470
+ return formatNumberValue(value, {
471
+ decimalPlaces: dataFormat === "usdDecimal" ? 2 : 0,
472
+ thousandsSeparator: true,
473
+ symbol: "$"
474
+ });
475
+ }
476
+ const pattern = DATE_PATTERNS[dataFormat];
477
+ return pattern ? formatDateTimeValue(value, pattern) ?? String(value ?? "") : String(value ?? "");
478
+ }
479
+ function formatPivotIndicatorValueWithOptions(value, dataFormat) {
480
+ if (dataFormat.pattern && dataFormat.type !== "custom") {
481
+ const formatted = formatDateTimeValue(value, dataFormat.pattern);
482
+ if (formatted != null) return formatted;
483
+ }
484
+ const decimalPlaces = Math.max(
485
+ 0,
486
+ Math.min(20, dataFormat.decimalPlaces ?? 2)
487
+ );
488
+ if (dataFormat.type === "percent" || dataFormat.type === "percentDecimal") {
489
+ return formatPercentValue(value, decimalPlaces);
490
+ }
491
+ if (dataFormat.type === "scientific") {
492
+ return formatScientificValue(value, decimalPlaces);
493
+ }
494
+ if (dataFormat.type === "cny" || dataFormat.type === "cnyDecimal" || dataFormat.type === "usd" || dataFormat.type === "usdDecimal") {
495
+ return formatNumberValue(value, {
496
+ decimalPlaces,
497
+ thousandsSeparator: dataFormat.useGrouping ?? true,
498
+ symbol: dataFormat.symbol ?? (dataFormat.type.startsWith("usd") ? "$" : "\xA5")
499
+ });
500
+ }
501
+ if (dataFormat.type === "number" || dataFormat.type === "thousands" || dataFormat.type === "decimal") {
502
+ return formatNumberValue(value, {
503
+ decimalPlaces,
504
+ thousandsSeparator: dataFormat.useGrouping ?? dataFormat.type !== "number"
505
+ });
506
+ }
507
+ return formatPivotIndicatorValue(value, dataFormat.type);
508
+ }
414
509
 
415
510
  // src/domain/aggregation/aggregate-records.ts
416
511
  function aggregatePivotRecords(params) {
@@ -475,7 +570,7 @@ function aggregatePivotRecords(params) {
475
570
  recordCount: item.recordIndexes.length,
476
571
  rowPathKey: item.rowPathKey,
477
572
  columnPathKey: item.columnPathKey
478
- }) ?? formatAggregateValue(value, item.indicator.precision),
573
+ }) ?? (item.indicator.dataFormat ? formatPivotIndicatorValue(value, item.indicator.dataFormat) : formatAggregateValue(value, item.indicator.precision)),
479
574
  recordCount: item.recordIndexes.length,
480
575
  recordIndexes: item.recordIndexes
481
576
  });
@@ -1025,7 +1120,6 @@ function resolveThumbOffset(scroll, maxScroll, track, thumb) {
1025
1120
  // src/domain/layout/pivot-layout.ts
1026
1121
  var DEFAULT_ROW_HEIGHT = 40;
1027
1122
  var DEFAULT_COLUMN_WIDTH = 120;
1028
- var DEFAULT_HEADER_SIZE = 40;
1029
1123
  var DEFAULT_ROW_DIMENSION_WIDTH2 = 140;
1030
1124
  var SCROLLBAR_THICKNESS2 = 10;
1031
1125
  function buildPivotLayoutSnapshot(params) {
@@ -1057,7 +1151,7 @@ function buildPivotLayoutSnapshot(params) {
1057
1151
  options.columns.length + (indicatorLayout === "columns" && hasIndicators ? 1 : 0),
1058
1152
  1
1059
1153
  );
1060
- const columnHeaderHeight = columnHeaderLevels * DEFAULT_HEADER_SIZE;
1154
+ const columnHeaderHeight = columnHeaderLevels * rowHeight;
1061
1155
  const reportFilterHeight = (options.filterDimensions?.length ?? 0) * rowHeight;
1062
1156
  const tableHeaderHeight = reportFilterHeight + columnHeaderHeight;
1063
1157
  const rawBodyWidth = Math.max(width - rowHeaderWidth, 0);
@@ -1191,6 +1285,7 @@ function buildPivotLayoutSnapshot(params) {
1191
1285
  rowHeaderWidth,
1192
1286
  scrollLeft: scroll.left,
1193
1287
  levels: columnHeaderLevels,
1288
+ headerSize: rowHeight,
1194
1289
  headerTop: reportFilterHeight,
1195
1290
  expandedNodeIds: params.expandedColumnNodeIds
1196
1291
  });
@@ -1591,7 +1686,7 @@ function buildColumnHeaderCells(params) {
1591
1686
  x,
1592
1687
  y: params.headerTop,
1593
1688
  width: columnWidth,
1594
- height: DEFAULT_HEADER_SIZE * (params.levels - (slot.indicatorKey ? 1 : 0))
1689
+ height: params.headerSize * (params.levels - (slot.indicatorKey ? 1 : 0))
1595
1690
  }
1596
1691
  });
1597
1692
  } else {
@@ -1631,9 +1726,9 @@ function buildColumnHeaderCells(params) {
1631
1726
  ),
1632
1727
  rect: {
1633
1728
  x,
1634
- y: params.headerTop + level * DEFAULT_HEADER_SIZE,
1729
+ y: params.headerTop + level * params.headerSize,
1635
1730
  width: columnWidth,
1636
- height: DEFAULT_HEADER_SIZE
1731
+ height: params.headerSize
1637
1732
  }
1638
1733
  });
1639
1734
  });
@@ -1660,9 +1755,9 @@ function buildColumnHeaderCells(params) {
1660
1755
  sortDirection: null,
1661
1756
  rect: {
1662
1757
  x,
1663
- y: params.headerTop + (params.levels - 1) * DEFAULT_HEADER_SIZE,
1758
+ y: params.headerTop + (params.levels - 1) * params.headerSize,
1664
1759
  width: columnWidth,
1665
- height: DEFAULT_HEADER_SIZE
1760
+ height: params.headerSize
1666
1761
  }
1667
1762
  });
1668
1763
  }
@@ -2699,6 +2794,15 @@ function resolvePivotSelectionRect(layout, selection) {
2699
2794
  const visibleStartY = Math.max(startY, clipRect.y);
2700
2795
  const visibleEndY = Math.min(endY, clipRect.y + clipRect.height);
2701
2796
  if (visibleStartX >= visibleEndX || visibleStartY >= visibleEndY) return null;
2797
+ const singleCell = kind === "cell" && indexes.startRowIndex === indexes.endRowIndex && indexes.startColumnIndex === indexes.endColumnIndex;
2798
+ if (singleCell) {
2799
+ return {
2800
+ x: startX,
2801
+ y: startY,
2802
+ width: endX - startX,
2803
+ height: endY - startY
2804
+ };
2805
+ }
2702
2806
  return {
2703
2807
  x: visibleStartX,
2704
2808
  y: visibleStartY,
@@ -3251,8 +3355,16 @@ function renderPivotTable(params) {
3251
3355
  );
3252
3356
  layout.columnHeaderCells.forEach(
3253
3357
  (cell) => drawPivotGridRect(context, cell.rect, theme.borderColor, {
3254
- skipTop: cell.rect.y > 0,
3255
- skipLeft: true,
3358
+ skipTop: hasColumnHeaderCellAbove(
3359
+ layout.columnHeaderCells,
3360
+ cell,
3361
+ layout.columnHeaderRect.y
3362
+ ),
3363
+ skipLeft: hasColumnHeaderCellLeft(
3364
+ layout.columnHeaderCells,
3365
+ cell,
3366
+ layout.columnHeaderRect.x
3367
+ ),
3256
3368
  skipRight: isSameCoordinate2(
3257
3369
  cell.rect.x + cell.rect.width,
3258
3370
  layout.tableWidth
@@ -3322,10 +3434,12 @@ function renderPivotTable(params) {
3322
3434
  drawPivotResizeGuide(context, params.resizeGuideX, contentHeight);
3323
3435
  }
3324
3436
  context.restore();
3325
- context.save();
3326
- clipPivotViewport(context, tableViewportRect);
3327
- drawPivotViewportRightBorder(context, tableViewportRect, theme.borderColor);
3328
- context.restore();
3437
+ if (!layout.scrollbar.vertical) {
3438
+ context.save();
3439
+ clipPivotViewport(context, tableViewportRect);
3440
+ drawPivotViewportRightBorder(context, tableViewportRect, theme.borderColor);
3441
+ context.restore();
3442
+ }
3329
3443
  drawPivotViewportBorder(
3330
3444
  context,
3331
3445
  { x: 0, y: 0, width: viewportWidth, height: contentHeight },
@@ -3354,6 +3468,34 @@ function renderPivotTable(params) {
3354
3468
  bodyCellCount: layout.bodyCells.length
3355
3469
  };
3356
3470
  }
3471
+ function hasColumnHeaderCellAbove(cells, cell, headerTop) {
3472
+ if (isSameCoordinate2(cell.rect.y, headerTop)) return headerTop > 0;
3473
+ const left = cell.rect.x;
3474
+ const right = left + cell.rect.width;
3475
+ return cells.some((candidate) => {
3476
+ if (candidate === cell) return false;
3477
+ const candidateBottom = candidate.rect.y + candidate.rect.height;
3478
+ if (!isSameCoordinate2(candidateBottom, cell.rect.y)) return false;
3479
+ const candidateLeft = candidate.rect.x;
3480
+ const candidateRight = candidateLeft + candidate.rect.width;
3481
+ return candidateLeft < right && candidateRight > left;
3482
+ });
3483
+ }
3484
+ function hasColumnHeaderCellLeft(cells, cell, headerLeft) {
3485
+ if (cell.rect.x <= headerLeft + 0.01) {
3486
+ return true;
3487
+ }
3488
+ const top = cell.rect.y;
3489
+ const bottom = top + cell.rect.height;
3490
+ return cells.some((candidate) => {
3491
+ if (candidate === cell) return false;
3492
+ const candidateRight = candidate.rect.x + candidate.rect.width;
3493
+ if (!isSameCoordinate2(candidateRight, cell.rect.x)) return false;
3494
+ const candidateTop = candidate.rect.y;
3495
+ const candidateBottom = candidateTop + candidate.rect.height;
3496
+ return candidateTop < bottom && candidateBottom > top;
3497
+ });
3498
+ }
3357
3499
  function resolvePivotViewportWidth(layout) {
3358
3500
  const horizontalRight = layout.scrollbar.horizontal ? layout.scrollbar.horizontal.increaseButton.x + layout.scrollbar.horizontal.increaseButton.width : 0;
3359
3501
  const verticalRight = layout.scrollbar.vertical ? layout.scrollbar.vertical.decreaseButton.x + layout.scrollbar.vertical.decreaseButton.width : 0;
@@ -3656,7 +3798,7 @@ function isSameCoordinate2(first, second) {
3656
3798
  return Math.abs(first - second) < 0.01;
3657
3799
  }
3658
3800
  function resolveBodyDisplayText(cell, indicator) {
3659
- if (!indicator || indicator.formatter || typeof cell.value !== "number") {
3801
+ if (!indicator || indicator.formatter || indicator.dataFormat || typeof cell.value !== "number") {
3660
3802
  return cell.formattedValue;
3661
3803
  }
3662
3804
  if (indicator.cellType === "percent") {
@@ -3793,10 +3935,18 @@ function drawSortIcon(context, rect, direction, rightPadding) {
3793
3935
  function drawText(context, text, rect, color, padding, align) {
3794
3936
  const availableWidth = Math.max(rect.width - padding * 2, 0);
3795
3937
  const value = truncateText(context, text, availableWidth);
3938
+ context.textBaseline = "alphabetic";
3939
+ const metrics = context.measureText(value);
3940
+ const ascent = metrics.actualBoundingBoxAscent ?? 0;
3941
+ const descent = metrics.actualBoundingBoxDescent ?? 0;
3942
+ const hasGlyphBounds = ascent > 0 || descent > 0;
3943
+ const centerY = rect.y + rect.height / 2;
3796
3944
  context.fillStyle = color;
3797
3945
  context.textAlign = align;
3946
+ context.textBaseline = hasGlyphBounds ? "alphabetic" : "middle";
3798
3947
  const x = align === "right" ? rect.x + rect.width - padding : align === "center" ? rect.x + rect.width / 2 : rect.x + padding;
3799
- context.fillText(value, x, rect.y + rect.height / 2, availableWidth);
3948
+ const y = hasGlyphBounds ? centerY + (ascent - descent) / 2 : centerY;
3949
+ context.fillText(value, x, y, availableWidth);
3800
3950
  }
3801
3951
  function truncateText(context, text, maxWidth) {
3802
3952
  if (context.measureText(text).width <= maxWidth) return text;
@@ -3844,7 +3994,7 @@ function hitTestPivotLayout(layout, x, y) {
3844
3994
  onSortIcon: false
3845
3995
  };
3846
3996
  }
3847
- const bodyCell = findLastContaining(layout.bodyCells, x, y);
3997
+ const bodyCell = contains(layout.bodyRect, x, y) ? findLastContaining(layout.bodyCells, x, y) : void 0;
3848
3998
  if (bodyCell) {
3849
3999
  return {
3850
4000
  region: "body",
@@ -3854,7 +4004,7 @@ function hitTestPivotLayout(layout, x, y) {
3854
4004
  onSortIcon: false
3855
4005
  };
3856
4006
  }
3857
- const rowHeaderCell = findLastContaining(layout.rowHeaderCells, x, y);
4007
+ const rowHeaderCell = contains(layout.rowHeaderRect, x, y) ? findLastContaining(layout.rowHeaderCells, x, y) : void 0;
3858
4008
  if (rowHeaderCell) {
3859
4009
  return {
3860
4010
  region: "rowHeader",
@@ -3864,9 +4014,7 @@ function hitTestPivotLayout(layout, x, y) {
3864
4014
  onSortIcon: isSortIconHit(rowHeaderCell, x)
3865
4015
  };
3866
4016
  }
3867
- const columnHeaderCell = layout.columnHeaderCells.find(
3868
- (cell) => contains(cell.rect, x, y)
3869
- );
4017
+ const columnHeaderCell = contains(layout.columnHeaderRect, x, y) ? layout.columnHeaderCells.find((cell) => contains(cell.rect, x, y)) : void 0;
3870
4018
  if (columnHeaderCell) {
3871
4019
  return {
3872
4020
  region: "columnHeader",
@@ -4382,7 +4530,7 @@ var PivotTable = class {
4382
4530
  }
4383
4531
  if (hit.reportFilterCell) return;
4384
4532
  if (hit.bodyCell) {
4385
- this.selectCell(toCellAddress(hit.bodyCell), event.shiftKey);
4533
+ this.selectPointerCell(toCellAddress(hit.bodyCell), event.shiftKey);
4386
4534
  return;
4387
4535
  }
4388
4536
  const header = hit.headerCell;
@@ -4484,7 +4632,76 @@ var PivotTable = class {
4484
4632
  });
4485
4633
  __publicField(this, "handleContextMenu", (event) => {
4486
4634
  const hit = this.resolveHit(event);
4487
- if (!hit?.bodyCell || !this.core || !this.layout) return;
4635
+ if (!hit || !this.core || !this.layout) return;
4636
+ if (hit.reportFilterCell) {
4637
+ const cell = hit.reportFilterCell;
4638
+ event.preventDefault();
4639
+ this.options.ui?.onContextMenuRequest?.({
4640
+ x: event.clientX,
4641
+ y: event.clientY,
4642
+ target: "header",
4643
+ title: cell.label,
4644
+ axis: "filter",
4645
+ onCopy: () => this.copyPivotText(`${cell.label} ${cell.valueLabel}`),
4646
+ onOpenFilter: () => this.openReportFilterPanel(cell),
4647
+ ...this.createContextMenuSettings(),
4648
+ onExportCsv: () => downloadBlob(
4649
+ new Blob([this.exportCsv()], { type: "text/csv;charset=utf-8" }),
4650
+ "pivot-table.csv"
4651
+ ),
4652
+ onExportXlsx: () => downloadBlob(
4653
+ new Blob([toArrayBuffer(this.exportXlsx())], {
4654
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
4655
+ }),
4656
+ "pivot-table.xlsx"
4657
+ ),
4658
+ onClose: () => this.options.ui?.onContextMenuRequest?.(null)
4659
+ });
4660
+ return;
4661
+ }
4662
+ if (hit.headerCell) {
4663
+ const header = hit.headerCell;
4664
+ const model2 = this.core.getPivotModel();
4665
+ const node = header.nodeId ? (header.axis === "row" ? model2?.rowNodes : model2?.columnNodes)?.get(
4666
+ header.nodeId
4667
+ ) : void 0;
4668
+ const dimensionKey = header.dimensionKey ?? node?.dimensionKey;
4669
+ event.preventDefault();
4670
+ this.options.ui?.onContextMenuRequest?.({
4671
+ x: event.clientX,
4672
+ y: event.clientY,
4673
+ target: "header",
4674
+ title: header.label,
4675
+ axis: header.axis,
4676
+ nodeKind: node?.kind ?? header.kind,
4677
+ indicatorKey: header.indicatorKey ?? void 0,
4678
+ onCopy: () => this.copyPivotText(header.label),
4679
+ sortDirection: header.sortDirection,
4680
+ onSortAsc: header.sortable && dimensionKey ? () => this.setDimensionSort(header.axis, dimensionKey, "asc") : void 0,
4681
+ onSortDesc: header.sortable && dimensionKey ? () => this.setDimensionSort(header.axis, dimensionKey, "desc") : void 0,
4682
+ onClearSort: header.sortable && dimensionKey ? () => this.setDimensionSort(header.axis, dimensionKey, null) : void 0,
4683
+ onOpenFilter: header.filterable ? () => this.openFilterPanel(hit) : void 0,
4684
+ expanded: header.expandable ? header.expanded : void 0,
4685
+ onExpand: header.expandable && header.nodeId ? () => header.axis === "row" ? this.expandRowNode(header.nodeId) : this.expandColumnNode(header.nodeId) : void 0,
4686
+ onCollapse: header.expandable && header.nodeId ? () => header.axis === "row" ? this.collapseRowNode(header.nodeId) : this.collapseColumnNode(header.nodeId) : void 0,
4687
+ onExpandAll: () => this.expandAll(header.axis),
4688
+ onCollapseAll: () => this.collapseAll(header.axis),
4689
+ ...this.createContextMenuSettings(header.indicatorKey ?? void 0),
4690
+ onExportCsv: () => downloadBlob(
4691
+ new Blob([this.exportCsv()], { type: "text/csv;charset=utf-8" }),
4692
+ "pivot-table.csv"
4693
+ ),
4694
+ onExportXlsx: () => downloadBlob(
4695
+ new Blob([toArrayBuffer(this.exportXlsx())], {
4696
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
4697
+ }),
4698
+ "pivot-table.xlsx"
4699
+ ),
4700
+ onClose: () => this.options.ui?.onContextMenuRequest?.(null)
4701
+ });
4702
+ return;
4703
+ }
4704
+ if (!hit.bodyCell) return;
4488
4705
  event.preventDefault();
4489
4706
  const address = toCellAddress(hit.bodyCell);
4490
4707
  const currentRange = resolvePivotSelectionRange(
@@ -4494,7 +4711,7 @@ var PivotTable = class {
4494
4711
  const insideCurrentRange = Boolean(
4495
4712
  currentRange && hit.bodyCell.rowIndex >= currentRange.startRowIndex && hit.bodyCell.rowIndex <= currentRange.endRowIndex && hit.bodyCell.columnIndex >= currentRange.startColumnIndex && hit.bodyCell.columnIndex <= currentRange.endColumnIndex
4496
4713
  );
4497
- if (!insideCurrentRange) this.selectCell(address);
4714
+ if (!insideCurrentRange) this.selectPointerCell(address);
4498
4715
  const selectionRange = resolvePivotSelectionRange(
4499
4716
  this.layout,
4500
4717
  this.core.getSelection()
@@ -4504,15 +4721,28 @@ var PivotTable = class {
4504
4721
  columnPathKey: hit.bodyCell.columnPathKey,
4505
4722
  indicatorKey: hit.bodyCell.indicatorKey
4506
4723
  });
4724
+ const model = this.core.getPivotModel();
4725
+ const rowNode = hit.bodyCell.rowNodeId ? model?.rowNodes.get(hit.bodyCell.rowNodeId) : void 0;
4726
+ const summary = rowNode?.kind === "subtotal" || rowNode?.kind === "grandTotal";
4727
+ const indicator = this.options.indicators.find(
4728
+ (item) => item.key === hit.bodyCell?.indicatorKey
4729
+ );
4730
+ const summaryAggregator = typeof indicator?.aggregator === "string" && isBuiltInPivotAggregator(indicator.aggregator) ? indicator.aggregator : "sum";
4507
4731
  this.options.ui?.onContextMenuRequest?.({
4508
4732
  x: event.clientX,
4509
4733
  y: event.clientY,
4734
+ target: summary ? "summary" : "body",
4735
+ title: summary ? rowNode.label : void 0,
4736
+ nodeKind: rowNode?.kind,
4737
+ indicatorKey: hit.bodyCell.indicatorKey,
4510
4738
  recordCount: records.length,
4739
+ summaryAggregator: summary ? summaryAggregator : void 0,
4740
+ summaryPrecision: summary ? indicator?.precision : void 0,
4511
4741
  onCopy: () => {
4512
- const model = this.core?.getPivotModel();
4513
- const text = model ? createPivotRangeSelectionText({
4742
+ const model2 = this.core?.getPivotModel();
4743
+ const text = model2 ? createPivotRangeSelectionText({
4514
4744
  layout: this.layout,
4515
- model,
4745
+ model: model2,
4516
4746
  startRowIndex: hit.bodyCell.rowIndex,
4517
4747
  endRowIndex: hit.bodyCell.rowIndex,
4518
4748
  startColumnIndex: hit.bodyCell.columnIndex,
@@ -4537,11 +4767,11 @@ var PivotTable = class {
4537
4767
  columnCount: this.layout.columnSlots.length
4538
4768
  },
4539
4769
  onCopyCustomSelection: (range) => {
4540
- const model = this.core?.getPivotModel();
4541
- if (!model || !this.layout) return;
4770
+ const model2 = this.core?.getPivotModel();
4771
+ if (!model2 || !this.layout) return;
4542
4772
  const text = createPivotRangeSelectionText({
4543
4773
  layout: this.layout,
4544
- model,
4774
+ model: model2,
4545
4775
  startRowIndex: range.startRow - 1,
4546
4776
  endRowIndex: range.endRow - 1,
4547
4777
  startColumnIndex: range.startColumn - 1,
@@ -4551,6 +4781,7 @@ var PivotTable = class {
4551
4781
  this.copyPivotText(text);
4552
4782
  }
4553
4783
  },
4784
+ ...this.createContextMenuSettings(hit.bodyCell.indicatorKey),
4554
4785
  onExportCsv: () => downloadBlob(
4555
4786
  new Blob([this.exportCsv()], { type: "text/csv;charset=utf-8" }),
4556
4787
  "pivot-table.csv"
@@ -4566,9 +4797,9 @@ var PivotTable = class {
4566
4797
  });
4567
4798
  __publicField(this, "handlePointerDown", (event) => {
4568
4799
  if (!this.layout || !this.host) return;
4569
- this.stopWheelScrollSmoothing();
4570
4800
  const scrollbarHit = this.resolveScrollbarHit(event);
4571
4801
  if (scrollbarHit && event.button === 0) {
4802
+ this.stopWheelScrollSmoothing();
4572
4803
  this.beginScrollbarInteraction(event, scrollbarHit);
4573
4804
  return;
4574
4805
  }
@@ -4579,7 +4810,7 @@ var PivotTable = class {
4579
4810
  if (cellHit?.bodyCell) {
4580
4811
  const address = toCellAddress(cellHit.bodyCell);
4581
4812
  this.host.root.focus({ preventScroll: true });
4582
- const selection = this.selectCell(address, event.shiftKey);
4813
+ const selection = this.selectPointerCell(address, event.shiftKey);
4583
4814
  const text = this.resolveBodyText(cellHit.bodyCell);
4584
4815
  const textOriginX = this.resolveBodyTextOriginX(cellHit.bodyCell, text);
4585
4816
  const anchorOffset = text ? resolvePivotTextOffset({
@@ -4665,8 +4896,12 @@ var PivotTable = class {
4665
4896
  if (selection) this.setSelection(selection);
4666
4897
  this.host.root.setPointerCapture(event.pointerId);
4667
4898
  }
4899
+ if (cellHit?.headerCell || cellHit?.reportFilterCell) {
4900
+ event.preventDefault();
4901
+ }
4668
4902
  return;
4669
4903
  }
4904
+ this.stopWheelScrollSmoothing();
4670
4905
  event.preventDefault();
4671
4906
  const startWidth = hit.kind === "rowDimension" ? this.layout.rowDimensionWidths[hit.index] ?? 140 : this.layout.columnWidths[hit.index] ?? this.layout.columnWidth;
4672
4907
  this.resizeSession = {
@@ -4856,9 +5091,16 @@ var PivotTable = class {
4856
5091
  }
4857
5092
  /** 选中指定值单元格。 */
4858
5093
  selectCell(address, extend = false) {
5094
+ return this.commitCellSelection(address, extend, true);
5095
+ }
5096
+ /** 鼠标命中的单元格已在视口内,选中时不再校正滚动位置。 */
5097
+ selectPointerCell(address, extend = false) {
5098
+ return this.commitCellSelection(address, extend, false);
5099
+ }
5100
+ commitCellSelection(address, extend, reveal) {
4859
5101
  const selection = this.core?.selectCell(address, extend) ?? null;
4860
5102
  if (selection) {
4861
- this.scrollToCell(address);
5103
+ if (reveal) this.scrollToCell(address);
4862
5104
  this.scheduleRender();
4863
5105
  }
4864
5106
  return selection;
@@ -5016,6 +5258,27 @@ var PivotTable = class {
5016
5258
  this.advanceWheelScrollSmoothing(nextTime);
5017
5259
  });
5018
5260
  }
5261
+ createContextMenuSettings(indicatorKey) {
5262
+ const indicator = indicatorKey ? this.options.indicators.find((item) => item.key === indicatorKey) : void 0;
5263
+ return {
5264
+ density: resolvePivotTableDensity(this.options.rowHeight),
5265
+ onChangeDensity: (density) => {
5266
+ this.updateOptions({
5267
+ ...this.options,
5268
+ rowHeight: resolvePivotTableDensityRowHeight(density)
5269
+ });
5270
+ },
5271
+ dataFormat: indicator?.dataFormat ?? "default",
5272
+ onChangeDataFormat: indicator ? (dataFormat) => {
5273
+ this.updateOptions({
5274
+ ...this.options,
5275
+ indicators: this.options.indicators.map(
5276
+ (item) => item.key === indicator.key ? { ...item, dataFormat } : item
5277
+ )
5278
+ });
5279
+ } : void 0
5280
+ };
5281
+ }
5019
5282
  updateTooltip(hit, event) {
5020
5283
  if (!this.host) return;
5021
5284
  const cell = hit?.bodyCell ?? hit?.headerCell;
@@ -5221,7 +5484,7 @@ var PivotTable = class {
5221
5484
  const indicator = this.options.indicators.find(
5222
5485
  (item) => item.key === cell.indicatorKey
5223
5486
  );
5224
- if (!indicator || indicator.formatter || typeof cell.value !== "number")
5487
+ if (!indicator || indicator.formatter || indicator.dataFormat || typeof cell.value !== "number")
5225
5488
  return cell.formattedValue;
5226
5489
  if (indicator.cellType === "percent")
5227
5490
  return `${(cell.value * 100).toFixed(indicator.precision ?? 2)}%`;
@@ -5583,6 +5846,9 @@ function toCellAddress(cell) {
5583
5846
  indicatorKey: cell.indicatorKey
5584
5847
  };
5585
5848
  }
5849
+ function isBuiltInPivotAggregator(value) {
5850
+ return ["sum", "avg", "count", "distinctCount", "min", "max"].includes(value);
5851
+ }
5586
5852
  function isSameAddress(left, right) {
5587
5853
  return Boolean(
5588
5854
  left === right || left && right && left.rowNodeId === right.rowNodeId && left.columnNodeId === right.columnNodeId && left.indicatorKey === right.indicatorKey
@@ -5595,6 +5861,20 @@ function resolveNavigationDirection(key) {
5595
5861
  if (key === "ArrowRight") return { row: 0, column: 1 };
5596
5862
  return null;
5597
5863
  }
5864
+ function resolvePivotTableDensity(rowHeight) {
5865
+ if (rowHeight == null) return "auto";
5866
+ if (rowHeight <= 26) return "ultra-compact";
5867
+ if (rowHeight <= 32) return "compact";
5868
+ if (rowHeight >= 56) return "comfortable";
5869
+ return "middle";
5870
+ }
5871
+ function resolvePivotTableDensityRowHeight(density) {
5872
+ if (density === "comfortable") return 56;
5873
+ if (density === "middle") return 48;
5874
+ if (density === "compact") return 32;
5875
+ if (density === "ultra-compact") return 26;
5876
+ return void 0;
5877
+ }
5598
5878
  function resolveAdjacentAddress(layout, address, direction) {
5599
5879
  const indexes = resolveAddressIndexes3(layout, address);
5600
5880
  if (!indexes) return null;