@canvas-components/pivot-table 0.1.9 → 0.2.2

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
@@ -330,8 +330,6 @@ function indexPivotNodes(root) {
330
330
  visit(root);
331
331
  return nodeMap;
332
332
  }
333
-
334
- // src/domain/aggregation/built-in-aggregators.ts
335
333
  function createBuiltInPivotAggregators() {
336
334
  return {
337
335
  sum: createNumericAggregator("sum"),
@@ -362,14 +360,14 @@ function createNumericAggregator(type) {
362
360
  return;
363
361
  }
364
362
  state.numericCount += 1;
365
- state.sum += numericValue;
363
+ state.sum = utils.addPreciseNumbers(state.sum, numericValue) ?? state.sum;
366
364
  state.min = state.min == null ? numericValue : Math.min(state.min, numericValue);
367
365
  state.max = state.max == null ? numericValue : Math.max(state.max, numericValue);
368
366
  },
369
367
  merge(state, source) {
370
368
  state.count += source.count;
371
369
  state.numericCount += source.numericCount;
372
- state.sum += source.sum;
370
+ state.sum = utils.addPreciseNumbers(state.sum, source.sum) ?? state.sum;
373
371
  state.min = mergeMinimum(state.min, source.min);
374
372
  state.max = mergeMaximum(state.max, source.max);
375
373
  source.distinctValues.forEach((value) => state.distinctValues.add(value));
@@ -377,9 +375,11 @@ function createNumericAggregator(type) {
377
375
  finalize(state) {
378
376
  if (type === "count") return state.count;
379
377
  if (type === "distinctCount") return state.distinctValues.size;
380
- if (type === "sum") return state.sum;
378
+ if (type === "sum") return Number(state.sum);
381
379
  if (type === "avg") {
382
- return state.numericCount === 0 ? null : state.sum / state.numericCount;
380
+ if (state.numericCount === 0) return null;
381
+ const average = utils.dividePreciseNumber(state.sum, state.numericCount);
382
+ return average == null ? null : Number(average);
383
383
  }
384
384
  if (type === "min") return state.min;
385
385
  return state.max;
@@ -390,7 +390,7 @@ function createAccumulator() {
390
390
  return {
391
391
  count: 0,
392
392
  numericCount: 0,
393
- sum: 0,
393
+ sum: "0",
394
394
  min: null,
395
395
  max: null,
396
396
  distinctValues: /* @__PURE__ */ new Set()
@@ -749,14 +749,11 @@ function matchesSearchKeyword(value, filter) {
749
749
  return filter.searchExact ? text === keyword : text.includes(keyword);
750
750
  }
751
751
  function matchesFilterConditions(value, filter) {
752
- const first = filter.condition;
753
- const second = filter.secondCondition;
754
- const firstActive = first && first.operator !== "none";
755
- const secondActive = second && second.operator !== "none";
756
- if (!firstActive && !secondActive) return true;
757
- if (!firstActive) return evaluateCondition(value, second);
758
- if (!secondActive) return evaluateCondition(value, first);
759
- return filter.conditionRelation === "or" ? evaluateCondition(value, first) || evaluateCondition(value, second) : evaluateCondition(value, first) && evaluateCondition(value, second);
752
+ const conditions = (filter.conditions?.length ? filter.conditions : [filter.condition, filter.secondCondition]).filter(
753
+ (condition) => condition != null && condition.operator !== "none"
754
+ );
755
+ if (conditions.length === 0) return true;
756
+ return filter.conditionRelation === "or" ? conditions.some((condition) => evaluateCondition(value, condition)) : conditions.every((condition) => evaluateCondition(value, condition));
760
757
  }
761
758
  function evaluateCondition(value, condition) {
762
759
  const text = value == null ? "" : String(value);
@@ -2652,6 +2649,7 @@ function createPivotDomHost(container) {
2652
2649
  right: "0",
2653
2650
  height: "100%",
2654
2651
  overflow: "hidden",
2652
+ borderRadius: "8px",
2655
2653
  touchAction: "none"
2656
2654
  });
2657
2655
  Object.assign(canvas.style, {
@@ -2771,18 +2769,50 @@ function resolvePivotSelectionKind(selection) {
2771
2769
  }
2772
2770
  return selection?.activeNode?.axis ?? "cell";
2773
2771
  }
2774
- function drawPivotSelectionBorder(context, rect, color) {
2772
+ function drawPivotSelectionBorder(context, rect, color, options) {
2775
2773
  context.save();
2776
2774
  context.strokeStyle = color;
2777
2775
  context.lineWidth = 1;
2778
- context.strokeRect(
2779
- rect.x + 0.5,
2780
- rect.y + 0.5,
2781
- Math.max(rect.width - 1, 0),
2782
- Math.max(rect.height - 1, 0)
2783
- );
2776
+ const left = rect.x + 0.5;
2777
+ const top = rect.y + 0.5;
2778
+ const width = Math.max(rect.width - 1, 0);
2779
+ const height = Math.max(rect.height - 1, 0);
2780
+ const right = left + width;
2781
+ const bottom = top + height;
2782
+ const viewport = options?.viewportRect;
2783
+ const radius = Math.min(options?.radius ?? 8, width / 2, height / 2);
2784
+ const topLeft = viewport && isSameCoordinate(rect.x, viewport.x) && isSameCoordinate(rect.y, viewport.y) ? radius : 0;
2785
+ const topRight = viewport && isSameCoordinate(rect.x + rect.width, viewport.x + viewport.width) && isSameCoordinate(rect.y, viewport.y) ? radius : 0;
2786
+ const bottomRight = viewport && isSameCoordinate(rect.x + rect.width, viewport.x + viewport.width) && isSameCoordinate(rect.y + rect.height, viewport.y + viewport.height) ? radius : 0;
2787
+ const bottomLeft = viewport && isSameCoordinate(rect.x, viewport.x) && isSameCoordinate(rect.y + rect.height, viewport.y + viewport.height) ? radius : 0;
2788
+ if (!topLeft && !topRight && !bottomRight && !bottomLeft) {
2789
+ context.strokeRect(left, top, width, height);
2790
+ context.restore();
2791
+ return;
2792
+ }
2793
+ context.beginPath();
2794
+ context.moveTo(left + topLeft, top);
2795
+ context.lineTo(right - topRight, top);
2796
+ if (topRight) context.arcTo(right, top, right, top + topRight, topRight);
2797
+ else context.lineTo(right, top);
2798
+ context.lineTo(right, bottom - bottomRight);
2799
+ if (bottomRight) {
2800
+ context.arcTo(right, bottom, right - bottomRight, bottom, bottomRight);
2801
+ } else context.lineTo(right, bottom);
2802
+ context.lineTo(left + bottomLeft, bottom);
2803
+ if (bottomLeft) {
2804
+ context.arcTo(left, bottom, left, bottom - bottomLeft, bottomLeft);
2805
+ } else context.lineTo(left, bottom);
2806
+ context.lineTo(left, top + topLeft);
2807
+ if (topLeft) context.arcTo(left, top, left + topLeft, top, topLeft);
2808
+ else context.lineTo(left, top);
2809
+ context.closePath();
2810
+ context.stroke();
2784
2811
  context.restore();
2785
2812
  }
2813
+ function isSameCoordinate(first, second) {
2814
+ return Math.abs(first - second) < 0.01;
2815
+ }
2786
2816
  function resolveSelectionIndexes(layout, selection) {
2787
2817
  const range = selection?.ranges[0];
2788
2818
  if (range) {
@@ -2880,29 +2910,127 @@ var defaultPivotTableTheme = {
2880
2910
  };
2881
2911
 
2882
2912
  // src/rendering/draw-scrollbars.ts
2883
- function drawPivotScrollbars(context, layout, borderColor) {
2913
+ function drawPivotScrollbars(context, layout, borderColor, verticalFrame) {
2884
2914
  context.save();
2915
+ if (layout.horizontal && verticalFrame) {
2916
+ drawHorizontalScrollbarMask(
2917
+ context,
2918
+ layout.horizontal,
2919
+ verticalFrame.bodyBackgroundColor
2920
+ );
2921
+ }
2922
+ if (layout.vertical && verticalFrame) {
2923
+ drawVerticalScrollbarFrame(
2924
+ context,
2925
+ layout.vertical,
2926
+ verticalFrame,
2927
+ borderColor
2928
+ );
2929
+ }
2885
2930
  if (layout.horizontal) {
2886
2931
  drawAxis(context, layout.horizontal, "horizontal", borderColor);
2887
2932
  }
2888
2933
  if (layout.vertical) {
2889
2934
  drawAxis(context, layout.vertical, "vertical", borderColor);
2890
2935
  }
2936
+ if (layout.horizontal) {
2937
+ drawHorizontalScrollbarTopBorder(
2938
+ context,
2939
+ layout.horizontal,
2940
+ layout.vertical,
2941
+ borderColor
2942
+ );
2943
+ }
2944
+ context.restore();
2945
+ }
2946
+ function drawHorizontalScrollbarMask(context, layout, backgroundColor) {
2947
+ const left = layout.decreaseButton.x;
2948
+ const right = layout.increaseButton.x + layout.increaseButton.width;
2949
+ const top = layout.decreaseButton.y;
2950
+ const height = layout.decreaseButton.height;
2951
+ if (right <= left || height <= 0) return;
2952
+ context.save();
2953
+ context.fillStyle = backgroundColor;
2954
+ context.fillRect(left, top, right - left, height);
2955
+ context.restore();
2956
+ }
2957
+ function drawHorizontalScrollbarTopBorder(context, horizontal, vertical, borderColor) {
2958
+ const left = Math.round(horizontal.decreaseButton.x) + 0.5;
2959
+ const horizontalRight = horizontal.increaseButton.x + horizontal.increaseButton.width;
2960
+ const verticalRight = vertical ? vertical.decreaseButton.x + vertical.decreaseButton.width : horizontalRight;
2961
+ const right = Math.round(Math.max(horizontalRight, verticalRight)) - 0.5;
2962
+ const top = Math.round(horizontal.decreaseButton.y) + 0.5;
2963
+ if (right <= left) return;
2964
+ context.save();
2965
+ context.strokeStyle = borderColor;
2966
+ context.lineWidth = 1;
2967
+ context.beginPath();
2968
+ context.moveTo(left, top);
2969
+ context.lineTo(right, top);
2970
+ context.stroke();
2891
2971
  context.restore();
2892
2972
  }
2893
2973
  function drawAxis(context, layout, axis, borderColor) {
2894
- drawTrack(context, layout.track, borderColor);
2895
2974
  drawButton(context, layout.decreaseButton, axis, "decrease");
2896
2975
  drawButton(context, layout.increaseButton, axis, "increase");
2897
2976
  drawThumb(context, layout.thumb);
2977
+ drawTrackBorder(context, layout.track, axis, borderColor);
2978
+ }
2979
+ function drawTrackBorder(context, rect, axis, borderColor) {
2980
+ const left = Math.round(rect.x) + 0.5;
2981
+ const right = Math.round(rect.x + rect.width) - 0.5;
2982
+ const top = Math.round(rect.y) + 0.5;
2983
+ const bottom = Math.round(rect.y + rect.height) - 0.5;
2984
+ if (right <= left || bottom <= top) return;
2985
+ context.save();
2986
+ context.strokeStyle = borderColor;
2987
+ context.lineWidth = 1;
2988
+ context.beginPath();
2989
+ if (axis === "horizontal") {
2990
+ context.moveTo(left, top);
2991
+ context.lineTo(right, top);
2992
+ } else {
2993
+ context.moveTo(left, top);
2994
+ context.lineTo(left, bottom);
2995
+ }
2996
+ context.stroke();
2997
+ context.restore();
2898
2998
  }
2899
- function drawTrack(context, rect, borderColor) {
2900
- context.fillStyle = "rgba(148, 163, 184, 0.16)";
2999
+ function drawVerticalScrollbarFrame(context, layout, frame, borderColor) {
3000
+ const left = Math.round(layout.decreaseButton.x) + 0.5;
3001
+ const top = Math.round(frame.top) + 0.5;
3002
+ const bottom = Math.round(frame.top + frame.height) - 0.5;
3003
+ const headerBottom = Math.round(frame.headerBottom) + 0.5;
3004
+ if (bottom <= top) return;
3005
+ context.save();
3006
+ context.fillStyle = frame.headerBackgroundColor;
3007
+ context.fillRect(
3008
+ layout.decreaseButton.x,
3009
+ frame.top,
3010
+ layout.decreaseButton.width,
3011
+ Math.max(frame.headerBottom - frame.top, 0)
3012
+ );
3013
+ context.fillStyle = frame.bodyBackgroundColor;
3014
+ context.fillRect(
3015
+ layout.decreaseButton.x,
3016
+ frame.headerBottom,
3017
+ layout.decreaseButton.width,
3018
+ Math.max(frame.top + frame.height - frame.headerBottom, 0)
3019
+ );
2901
3020
  context.strokeStyle = borderColor;
2902
3021
  context.lineWidth = 1;
2903
- roundRect(context, rect, rect.height / 2);
2904
- context.fill();
3022
+ context.beginPath();
3023
+ context.moveTo(left, top);
3024
+ context.lineTo(left, bottom);
2905
3025
  context.stroke();
3026
+ if (headerBottom > top && headerBottom < bottom) {
3027
+ const right = Math.round(layout.decreaseButton.x + layout.decreaseButton.width) - 0.5;
3028
+ context.beginPath();
3029
+ context.moveTo(left, headerBottom);
3030
+ context.lineTo(right, headerBottom);
3031
+ context.stroke();
3032
+ }
3033
+ context.restore();
2906
3034
  }
2907
3035
  function drawThumb(context, rect) {
2908
3036
  context.fillStyle = "rgba(100, 116, 139, 0.8)";
@@ -2910,28 +3038,25 @@ function drawThumb(context, rect) {
2910
3038
  context.fill();
2911
3039
  }
2912
3040
  function drawButton(context, rect, axis, direction) {
2913
- context.fillStyle = "rgba(226, 232, 240, 0.92)";
2914
- context.strokeStyle = "rgba(148, 163, 184, 0.65)";
2915
- context.lineWidth = 1;
2916
- roundRect(context, rect, rect.height / 2);
2917
- context.fill();
2918
- context.stroke();
2919
3041
  context.fillStyle = "rgba(71, 85, 105, 0.88)";
2920
3042
  context.beginPath();
2921
3043
  const centerX = rect.x + rect.width / 2;
2922
3044
  const centerY = rect.y + rect.height / 2;
3045
+ const iconSize = Math.min(rect.width, rect.height) * 0.64;
3046
+ const directionOffset = iconSize * 0.4;
3047
+ const crossAxisOffset = iconSize / 2;
2923
3048
  if (axis === "horizontal") {
2924
- const tipX = centerX + (direction === "decrease" ? -1 : 1) * rect.width * 0.14;
2925
- const tailX = centerX + (direction === "decrease" ? 1 : -1) * rect.width * 0.14;
3049
+ const tipX = centerX + (direction === "decrease" ? -1 : 1) * directionOffset;
3050
+ const tailX = centerX + (direction === "decrease" ? 1 : -1) * directionOffset;
2926
3051
  context.moveTo(tipX, centerY);
2927
- context.lineTo(tailX, centerY - rect.height * 0.22);
2928
- context.lineTo(tailX, centerY + rect.height * 0.22);
3052
+ context.lineTo(tailX, centerY - crossAxisOffset);
3053
+ context.lineTo(tailX, centerY + crossAxisOffset);
2929
3054
  } else {
2930
- const tipY = centerY + (direction === "decrease" ? -1 : 1) * rect.height * 0.14;
2931
- const tailY = centerY + (direction === "decrease" ? 1 : -1) * rect.height * 0.14;
3055
+ const tipY = centerY + (direction === "decrease" ? -1 : 1) * directionOffset;
3056
+ const tailY = centerY + (direction === "decrease" ? 1 : -1) * directionOffset;
2932
3057
  context.moveTo(centerX, tipY);
2933
- context.lineTo(centerX - rect.width * 0.22, tailY);
2934
- context.lineTo(centerX + rect.width * 0.22, tailY);
3058
+ context.lineTo(centerX - crossAxisOffset, tailY);
3059
+ context.lineTo(centerX + crossAxisOffset, tailY);
2935
3060
  }
2936
3061
  context.closePath();
2937
3062
  context.fill();
@@ -2995,14 +3120,22 @@ function drawPivotViewportRightBorder(context, rect, color) {
2995
3120
  context.stroke();
2996
3121
  context.restore();
2997
3122
  }
2998
- function drawPivotViewportBottomBorder(context, rect, color) {
2999
- const y = rect.y + rect.height - 0.5;
3123
+ function clipPivotViewport(context, rect, radius = 8) {
3124
+ appendRoundedRectPath(context, rect, radius);
3125
+ context.clip();
3126
+ }
3127
+ function drawPivotViewportBorder(context, rect, color, radius = 8) {
3128
+ const borderRect = {
3129
+ x: rect.x + 0.5,
3130
+ y: rect.y + 0.5,
3131
+ width: Math.max(rect.width - 1, 0),
3132
+ height: Math.max(rect.height - 1, 0)
3133
+ };
3134
+ if (borderRect.width <= 0 || borderRect.height <= 0) return;
3000
3135
  context.save();
3001
3136
  context.strokeStyle = color;
3002
3137
  context.lineWidth = 1;
3003
- context.beginPath();
3004
- context.moveTo(rect.x, y);
3005
- context.lineTo(rect.x + rect.width, y);
3138
+ appendRoundedRectPath(context, borderRect, radius);
3006
3139
  context.stroke();
3007
3140
  context.restore();
3008
3141
  }
@@ -3020,6 +3153,21 @@ function drawPivotHeaderBoundaries(context, options, color) {
3020
3153
  context.stroke();
3021
3154
  context.restore();
3022
3155
  }
3156
+ function appendRoundedRectPath(context, rect, radius) {
3157
+ const nextRadius = Math.max(
3158
+ Math.min(radius, rect.width / 2, rect.height / 2),
3159
+ 0
3160
+ );
3161
+ const right = rect.x + rect.width;
3162
+ const bottom = rect.y + rect.height;
3163
+ context.beginPath();
3164
+ context.moveTo(rect.x + nextRadius, rect.y);
3165
+ context.arcTo(right, rect.y, right, bottom, nextRadius);
3166
+ context.arcTo(right, bottom, rect.x, bottom, nextRadius);
3167
+ context.arcTo(rect.x, bottom, rect.x, rect.y, nextRadius);
3168
+ context.arcTo(rect.x, rect.y, right, rect.y, nextRadius);
3169
+ context.closePath();
3170
+ }
3023
3171
 
3024
3172
  // src/domain/layout/sticky-row-header.ts
3025
3173
  function resolveStickyRowHeaderContentRect(params) {
@@ -3056,7 +3204,21 @@ function renderPivotTable(params) {
3056
3204
  activeNodePathKey
3057
3205
  );
3058
3206
  const areaSelection = isPivotAreaSelection(layout, params.selection);
3207
+ const viewportWidth = resolvePivotViewportWidth(layout);
3208
+ const tableViewportRect = {
3209
+ x: 0,
3210
+ y: 0,
3211
+ width: layout.tableWidth,
3212
+ height: layout.height
3213
+ };
3059
3214
  context.clearRect(0, 0, layout.width, layout.height);
3215
+ context.save();
3216
+ clipPivotViewport(context, {
3217
+ x: 0,
3218
+ y: 0,
3219
+ width: viewportWidth,
3220
+ height: layout.height
3221
+ });
3060
3222
  context.font = `${theme.fontSize}px ${theme.fontFamily}`;
3061
3223
  context.textBaseline = "middle";
3062
3224
  withClip(context, layout.reportFilterRect, () => {
@@ -3089,7 +3251,7 @@ function renderPivotTable(params) {
3089
3251
  (cell) => drawPivotGridRect(context, cell.rect, theme.borderColor, {
3090
3252
  skipTop: true,
3091
3253
  skipLeft: cell.rect.x > 0,
3092
- skipRight: isSameCoordinate(
3254
+ skipRight: isSameCoordinate2(
3093
3255
  cell.rect.x + cell.rect.width,
3094
3256
  layout.rowHeaderRect.x + layout.rowHeaderRect.width
3095
3257
  )
@@ -3112,11 +3274,11 @@ function renderPivotTable(params) {
3112
3274
  (cell) => drawPivotGridRect(context, cell.rect, theme.borderColor, {
3113
3275
  skipTop: cell.rect.y > 0,
3114
3276
  skipLeft: true,
3115
- skipRight: isSameCoordinate(
3277
+ skipRight: isSameCoordinate2(
3116
3278
  cell.rect.x + cell.rect.width,
3117
3279
  layout.tableWidth
3118
3280
  ),
3119
- skipBottom: isSameCoordinate(
3281
+ skipBottom: isSameCoordinate2(
3120
3282
  cell.rect.y + cell.rect.height,
3121
3283
  layout.columnHeaderRect.y + layout.columnHeaderRect.height
3122
3284
  )
@@ -3148,7 +3310,7 @@ function renderPivotTable(params) {
3148
3310
  (cell) => drawPivotGridRect(context, cell.rect, theme.borderColor, {
3149
3311
  skipTop: true,
3150
3312
  skipLeft: true,
3151
- skipRight: isSameCoordinate(
3313
+ skipRight: isSameCoordinate2(
3152
3314
  cell.rect.x + cell.rect.width,
3153
3315
  layout.tableWidth
3154
3316
  )
@@ -3170,15 +3332,24 @@ function renderPivotTable(params) {
3170
3332
  theme.borderColor
3171
3333
  );
3172
3334
  if (options.debug) drawDebugOverlay(context, layout, theme);
3173
- drawPivotScrollbars(context, layout.scrollbar, theme.borderColor);
3174
- drawPivotViewportRightBorder(
3175
- context,
3176
- { x: 0, y: 0, width: layout.tableWidth, height: contentHeight },
3177
- theme.borderColor
3178
- );
3179
- drawPivotViewportBottomBorder(
3335
+ drawPivotScrollbars(context, layout.scrollbar, theme.borderColor, {
3336
+ top: 0,
3337
+ height: contentHeight,
3338
+ headerBottom: layout.bodyRect.y,
3339
+ headerBackgroundColor: theme.columnHeaderBackgroundColor,
3340
+ bodyBackgroundColor: theme.backgroundColor
3341
+ });
3342
+ if (params.resizeGuideX != null) {
3343
+ drawPivotResizeGuide(context, params.resizeGuideX, contentHeight);
3344
+ }
3345
+ context.restore();
3346
+ context.save();
3347
+ clipPivotViewport(context, tableViewportRect);
3348
+ drawPivotViewportRightBorder(context, tableViewportRect, theme.borderColor);
3349
+ context.restore();
3350
+ drawPivotViewportBorder(
3180
3351
  context,
3181
- { x: 0, y: 0, width: layout.tableWidth, height: contentHeight },
3352
+ { x: 0, y: 0, width: viewportWidth, height: contentHeight },
3182
3353
  theme.borderColor
3183
3354
  );
3184
3355
  const selectionRect = resolvePivotSelectionRect(layout, params.selection);
@@ -3187,22 +3358,28 @@ function renderPivotTable(params) {
3187
3358
  layout,
3188
3359
  params.selection
3189
3360
  );
3361
+ context.save();
3362
+ clipPivotViewport(context, tableViewportRect);
3190
3363
  withClip(context, selectionClipRect, () => {
3191
3364
  drawPivotSelectionBorder(
3192
3365
  context,
3193
3366
  selectionRect,
3194
- theme.selectionBorderColor
3367
+ theme.selectionBorderColor,
3368
+ { viewportRect: tableViewportRect }
3195
3369
  );
3196
3370
  });
3197
- }
3198
- if (params.resizeGuideX != null) {
3199
- drawPivotResizeGuide(context, params.resizeGuideX, contentHeight);
3371
+ context.restore();
3200
3372
  }
3201
3373
  return {
3202
3374
  headerCellCount: layout.reportFilterCells.length + layout.rowFieldHeaderCells.length + layout.rowHeaderCells.length + layout.columnHeaderCells.length,
3203
3375
  bodyCellCount: layout.bodyCells.length
3204
3376
  };
3205
3377
  }
3378
+ function resolvePivotViewportWidth(layout) {
3379
+ const horizontalRight = layout.scrollbar.horizontal ? layout.scrollbar.horizontal.increaseButton.x + layout.scrollbar.horizontal.increaseButton.width : 0;
3380
+ const verticalRight = layout.scrollbar.vertical ? layout.scrollbar.vertical.decreaseButton.x + layout.scrollbar.vertical.decreaseButton.width : 0;
3381
+ return Math.max(layout.tableWidth, horizontalRight, verticalRight);
3382
+ }
3206
3383
  function drawPivotResizeGuide(context, x, height) {
3207
3384
  context.save();
3208
3385
  context.strokeStyle = "#1677ff";
@@ -3237,7 +3414,7 @@ function drawDebugOverlay(context, layout, theme) {
3237
3414
  layout.bodyRect
3238
3415
  ].forEach((rect) => strokeRect(context, rect, "#f04438"));
3239
3416
  context.setLineDash([]);
3240
- context.font = `12px ${theme.fontFamily}`;
3417
+ context.font = `${theme.fontSize}px ${theme.fontFamily}`;
3241
3418
  context.textAlign = "left";
3242
3419
  context.textBaseline = "top";
3243
3420
  context.fillStyle = "#f04438";
@@ -3496,7 +3673,7 @@ function resolveSlotPathKey(slots, nodeId, indicatorKey) {
3496
3673
  (slot) => (slot.nodeId ?? "") === nodeId && (!slot.indicatorKey || slot.indicatorKey === indicatorKey)
3497
3674
  )?.pathKey ?? null;
3498
3675
  }
3499
- function isSameCoordinate(first, second) {
3676
+ function isSameCoordinate2(first, second) {
3500
3677
  return Math.abs(first - second) < 0.01;
3501
3678
  }
3502
3679
  function resolveBodyDisplayText(cell, indicator) {
@@ -3956,6 +4133,16 @@ function hitTestPivotScrollbar(layout, x, y) {
3956
4133
  }
3957
4134
  return null;
3958
4135
  }
4136
+ function resolvePivotScrollbarTrackScroll(params) {
4137
+ const movableLength = Math.max(params.trackLength - params.thumbLength, 0);
4138
+ if (movableLength === 0) return 0;
4139
+ const thumbStart = utils.clamp(
4140
+ params.pointerValue - params.thumbLength / 2,
4141
+ params.trackStart,
4142
+ params.trackStart + movableLength
4143
+ );
4144
+ return (thumbStart - params.trackStart) / movableLength * params.maxScroll;
4145
+ }
3959
4146
  function resolveRole(layout, x, y) {
3960
4147
  if (utils.isPointInRect(x, y, layout.thumb)) return "thumb";
3961
4148
  if (utils.isPointInRect(x, y, layout.decreaseButton)) return "decrease-button";
@@ -4122,6 +4309,8 @@ var PivotTable = class {
4122
4309
  __publicField(this, "wheelScrollTarget", null);
4123
4310
  __publicField(this, "wheelScrollFrameId", null);
4124
4311
  __publicField(this, "wheelScrollLastTime", 0);
4312
+ __publicField(this, "continuousScrollTimer", null);
4313
+ __publicField(this, "continuousScrollPointerId", null);
4125
4314
  __publicField(this, "resizeSession", null);
4126
4315
  __publicField(this, "resizeGuideX", null);
4127
4316
  __publicField(this, "suppressNextClick", false);
@@ -4200,6 +4389,7 @@ var PivotTable = class {
4200
4389
  this.scheduleRender();
4201
4390
  });
4202
4391
  __publicField(this, "handleClick", (event) => {
4392
+ this.options.ui?.onContextMenuRequest?.(null);
4203
4393
  if (this.suppressNextClick) {
4204
4394
  this.suppressNextClick = false;
4205
4395
  return;
@@ -4518,6 +4708,8 @@ var PivotTable = class {
4518
4708
  });
4519
4709
  __publicField(this, "handlePointerUp", (event) => {
4520
4710
  if (!this.host) return;
4711
+ const endedContinuousScroll = this.continuousScrollPointerId === event.pointerId;
4712
+ if (endedContinuousScroll) this.stopContinuousScrollbarScroll();
4521
4713
  const endedCellSelection = this.cellSelectionDrag?.pointerId === event.pointerId;
4522
4714
  if (endedCellSelection) this.cellSelectionDrag = null;
4523
4715
  if (this.textSelectionDrag?.pointerId === event.pointerId) {
@@ -4536,7 +4728,7 @@ var PivotTable = class {
4536
4728
  if (this.scrollbarDrag?.pointerId === event.pointerId) {
4537
4729
  this.scrollbarDrag = null;
4538
4730
  }
4539
- if (!this.resizeSession && !this.scrollbarDrag && !endedCellSelection && !endedRowHeaderSelection && !this.host.root.hasPointerCapture(event.pointerId))
4731
+ if (!this.resizeSession && !this.scrollbarDrag && !endedContinuousScroll && !endedCellSelection && !endedRowHeaderSelection && !this.host.root.hasPointerCapture(event.pointerId))
4540
4732
  return;
4541
4733
  if (this.host.root.hasPointerCapture(event.pointerId)) {
4542
4734
  this.host.root.releasePointerCapture(event.pointerId);
@@ -4782,6 +4974,7 @@ var PivotTable = class {
4782
4974
  if (this.frameId !== null) cancelAnimationFrame(this.frameId);
4783
4975
  this.frameId = null;
4784
4976
  this.stopWheelScrollSmoothing();
4977
+ this.stopContinuousScrollbarScroll();
4785
4978
  this.stopResizeObserver?.();
4786
4979
  this.stopResizeObserver = null;
4787
4980
  this.host?.root.removeEventListener("pointermove", this.handlePointerMove);
@@ -5087,7 +5280,7 @@ var PivotTable = class {
5087
5280
  measurePivotText(text) {
5088
5281
  const context = this.host?.canvas.getContext("2d");
5089
5282
  if (!context) return text.length * 8;
5090
- context.font = `${this.options.theme?.fontSize ?? 13}px ${this.options.theme?.fontFamily ?? "sans-serif"}`;
5283
+ context.font = `${this.options.theme?.fontSize ?? 14}px ${this.options.theme?.fontFamily ?? "sans-serif"}`;
5091
5284
  return context.measureText(text).width;
5092
5285
  }
5093
5286
  updateRowHeaderSelectionDrag(event) {
@@ -5150,17 +5343,27 @@ var PivotTable = class {
5150
5343
  event.preventDefault();
5151
5344
  this.suppressNextClick = true;
5152
5345
  if (hit.role === "decrease-button" || hit.role === "increase-button") {
5153
- this.stepScrollbar(hit.axis, hit.role === "decrease-button" ? -1 : 1);
5346
+ this.startContinuousScrollbarScroll(
5347
+ hit.axis,
5348
+ hit.role === "decrease-button" ? -1 : 1,
5349
+ event.pointerId
5350
+ );
5351
+ this.host.root.setPointerCapture(event.pointerId);
5154
5352
  return;
5155
5353
  }
5156
5354
  const rootRect = this.host.root.getBoundingClientRect();
5157
5355
  const pointer = hit.axis === "horizontal" ? event.clientX - rootRect.left : event.clientY - rootRect.top;
5158
5356
  if (hit.role === "track") {
5159
- const thumbStart2 = hit.axis === "horizontal" ? axisLayout.thumb.x : axisLayout.thumb.y;
5160
- const direction = pointer < thumbStart2 ? -1 : 1;
5357
+ const nextScroll = resolvePivotScrollbarTrackScroll({
5358
+ pointerValue: pointer,
5359
+ trackStart: hit.axis === "horizontal" ? axisLayout.track.x : axisLayout.track.y,
5360
+ trackLength: hit.axis === "horizontal" ? axisLayout.track.width : axisLayout.track.height,
5361
+ thumbLength: hit.axis === "horizontal" ? axisLayout.thumb.width : axisLayout.thumb.height,
5362
+ maxScroll: hit.axis === "horizontal" ? this.layout.viewport.maxScrollLeft : this.layout.viewport.maxScrollTop
5363
+ });
5161
5364
  this.applyScrollPosition(
5162
- this.scroll.left + (hit.axis === "horizontal" ? direction * this.layout.bodyRect.width : 0),
5163
- this.scroll.top + (hit.axis === "vertical" ? direction * this.layout.bodyRect.height : 0)
5365
+ hit.axis === "horizontal" ? nextScroll : this.scroll.left,
5366
+ hit.axis === "vertical" ? nextScroll : this.scroll.top
5164
5367
  );
5165
5368
  return;
5166
5369
  }
@@ -5204,6 +5407,21 @@ var PivotTable = class {
5204
5407
  this.scroll.top + (axis === "vertical" ? direction * this.layout.rowHeight : 0)
5205
5408
  );
5206
5409
  }
5410
+ startContinuousScrollbarScroll(axis, direction, pointerId) {
5411
+ this.stopContinuousScrollbarScroll();
5412
+ this.continuousScrollPointerId = pointerId;
5413
+ this.stepScrollbar(axis, direction);
5414
+ this.continuousScrollTimer = setInterval(() => {
5415
+ this.stepScrollbar(axis, direction);
5416
+ }, 80);
5417
+ }
5418
+ stopContinuousScrollbarScroll() {
5419
+ if (this.continuousScrollTimer !== null) {
5420
+ clearInterval(this.continuousScrollTimer);
5421
+ this.continuousScrollTimer = null;
5422
+ }
5423
+ this.continuousScrollPointerId = null;
5424
+ }
5207
5425
  applyScrollPosition(left, top) {
5208
5426
  if (!this.layout) return;
5209
5427
  const next = {
@@ -5448,6 +5666,90 @@ function toArrayBuffer(value) {
5448
5666
  return buffer;
5449
5667
  }
5450
5668
 
5669
+ // src/adapters/storage/indexeddb-field-layout-storage.ts
5670
+ var DATABASE_NAME = "canvas_components_pivot_table_config";
5671
+ var STORE_NAME = "field_layout";
5672
+ var DB_VERSION = 1;
5673
+ var dbPromise = null;
5674
+ async function savePivotFieldLayout(key, layout) {
5675
+ try {
5676
+ const db = await openDatabase();
5677
+ await requestToPromise(
5678
+ db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).put(cloneLayout(layout), key)
5679
+ );
5680
+ } catch (error) {
5681
+ console.error("[PivotTable] \u5B57\u6BB5\u5E03\u5C40\u4FDD\u5B58\u5931\u8D25\uFF1A", error);
5682
+ }
5683
+ }
5684
+ async function loadPivotFieldLayout(key) {
5685
+ try {
5686
+ const db = await openDatabase();
5687
+ const value = await requestToPromise(
5688
+ db.transaction(STORE_NAME, "readonly").objectStore(STORE_NAME).get(key)
5689
+ );
5690
+ return isPivotFieldLayout(value) ? cloneLayout(value) : void 0;
5691
+ } catch (error) {
5692
+ console.error("[PivotTable] \u5B57\u6BB5\u5E03\u5C40\u8BFB\u53D6\u5931\u8D25\uFF1A", error);
5693
+ return void 0;
5694
+ }
5695
+ }
5696
+ async function clearPivotFieldLayout(key) {
5697
+ try {
5698
+ const db = await openDatabase();
5699
+ await requestToPromise(
5700
+ db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).delete(key)
5701
+ );
5702
+ } catch (error) {
5703
+ console.error("[PivotTable] \u5B57\u6BB5\u5E03\u5C40\u6E05\u9664\u5931\u8D25\uFF1A", error);
5704
+ }
5705
+ }
5706
+ function openDatabase() {
5707
+ if (dbPromise) return dbPromise;
5708
+ dbPromise = new Promise((resolve, reject) => {
5709
+ if (typeof indexedDB === "undefined") {
5710
+ reject(new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 IndexedDB"));
5711
+ return;
5712
+ }
5713
+ const request = indexedDB.open(DATABASE_NAME, DB_VERSION);
5714
+ request.onupgradeneeded = () => {
5715
+ const db = request.result;
5716
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
5717
+ db.createObjectStore(STORE_NAME);
5718
+ }
5719
+ };
5720
+ request.onsuccess = () => resolve(request.result);
5721
+ request.onerror = () => reject(request.error);
5722
+ });
5723
+ dbPromise.catch(() => {
5724
+ dbPromise = null;
5725
+ });
5726
+ return dbPromise;
5727
+ }
5728
+ function requestToPromise(request) {
5729
+ return new Promise((resolve, reject) => {
5730
+ request.onsuccess = () => resolve(request.result);
5731
+ request.onerror = () => reject(request.error);
5732
+ });
5733
+ }
5734
+ function isPivotFieldLayout(value) {
5735
+ if (!value || typeof value !== "object") return false;
5736
+ const layout = value;
5737
+ return ["filters", "columns", "rows", "values"].every(
5738
+ (key) => isStringArray(layout[key])
5739
+ );
5740
+ }
5741
+ function isStringArray(value) {
5742
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
5743
+ }
5744
+ function cloneLayout(layout) {
5745
+ return {
5746
+ filters: [...layout.filters],
5747
+ columns: [...layout.columns],
5748
+ rows: [...layout.rows],
5749
+ values: [...layout.values]
5750
+ };
5751
+ }
5752
+
5451
5753
  // src/domain/field/pivot-field-layout.ts
5452
5754
  function createEmptyPivotFieldLayout() {
5453
5755
  return { filters: [], columns: [], rows: [], values: [] };
@@ -5611,6 +5913,7 @@ exports.PivotTable = PivotTable;
5611
5913
  exports.aggregatePivotRecords = aggregatePivotRecords;
5612
5914
  exports.buildPivotDimensionTree = buildPivotDimensionTree;
5613
5915
  exports.buildPivotLayoutSnapshot = buildPivotLayoutSnapshot;
5916
+ exports.clearPivotFieldLayout = clearPivotFieldLayout;
5614
5917
  exports.computePivotScrollbarLayout = computePivotScrollbarLayout;
5615
5918
  exports.createBuiltInPivotAggregators = createBuiltInPivotAggregators;
5616
5919
  exports.createEmptyPivotFieldLayout = createEmptyPivotFieldLayout;
@@ -5634,6 +5937,7 @@ exports.getPivotDimensionFilterOptions = getPivotDimensionFilterOptions;
5634
5937
  exports.hitTestPivotLayout = hitTestPivotLayout;
5635
5938
  exports.hitTestPivotResize = hitTestPivotResize;
5636
5939
  exports.hitTestPivotScrollbar = hitTestPivotScrollbar;
5940
+ exports.loadPivotFieldLayout = loadPivotFieldLayout;
5637
5941
  exports.movePivotField = movePivotField;
5638
5942
  exports.removePivotField = removePivotField;
5639
5943
  exports.renderPivotTable = renderPivotTable;
@@ -5645,5 +5949,6 @@ exports.resolvePivotDimensionWidth = resolvePivotDimensionWidth;
5645
5949
  exports.resolvePivotFieldLayoutOptions = resolvePivotFieldLayoutOptions;
5646
5950
  exports.resolvePivotIndicatorAggregator = resolvePivotIndicatorAggregator;
5647
5951
  exports.resolvePivotValueFieldTitle = resolvePivotValueFieldTitle;
5952
+ exports.savePivotFieldLayout = savePivotFieldLayout;
5648
5953
  //# sourceMappingURL=index.cjs.map
5649
5954
  //# sourceMappingURL=index.cjs.map