@canvas-components/pivot-table 0.1.9 → 0.2.1

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
@@ -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, {
@@ -2880,29 +2878,127 @@ var defaultPivotTableTheme = {
2880
2878
  };
2881
2879
 
2882
2880
  // src/rendering/draw-scrollbars.ts
2883
- function drawPivotScrollbars(context, layout, borderColor) {
2881
+ function drawPivotScrollbars(context, layout, borderColor, verticalFrame) {
2884
2882
  context.save();
2883
+ if (layout.horizontal && verticalFrame) {
2884
+ drawHorizontalScrollbarMask(
2885
+ context,
2886
+ layout.horizontal,
2887
+ verticalFrame.bodyBackgroundColor
2888
+ );
2889
+ }
2890
+ if (layout.vertical && verticalFrame) {
2891
+ drawVerticalScrollbarFrame(
2892
+ context,
2893
+ layout.vertical,
2894
+ verticalFrame,
2895
+ borderColor
2896
+ );
2897
+ }
2885
2898
  if (layout.horizontal) {
2886
2899
  drawAxis(context, layout.horizontal, "horizontal", borderColor);
2887
2900
  }
2888
2901
  if (layout.vertical) {
2889
2902
  drawAxis(context, layout.vertical, "vertical", borderColor);
2890
2903
  }
2904
+ if (layout.horizontal) {
2905
+ drawHorizontalScrollbarTopBorder(
2906
+ context,
2907
+ layout.horizontal,
2908
+ layout.vertical,
2909
+ borderColor
2910
+ );
2911
+ }
2912
+ context.restore();
2913
+ }
2914
+ function drawHorizontalScrollbarMask(context, layout, backgroundColor) {
2915
+ const left = layout.decreaseButton.x;
2916
+ const right = layout.increaseButton.x + layout.increaseButton.width;
2917
+ const top = layout.decreaseButton.y;
2918
+ const height = layout.decreaseButton.height;
2919
+ if (right <= left || height <= 0) return;
2920
+ context.save();
2921
+ context.fillStyle = backgroundColor;
2922
+ context.fillRect(left, top, right - left, height);
2923
+ context.restore();
2924
+ }
2925
+ function drawHorizontalScrollbarTopBorder(context, horizontal, vertical, borderColor) {
2926
+ const left = Math.round(horizontal.decreaseButton.x) + 0.5;
2927
+ const horizontalRight = horizontal.increaseButton.x + horizontal.increaseButton.width;
2928
+ const verticalRight = vertical ? vertical.decreaseButton.x + vertical.decreaseButton.width : horizontalRight;
2929
+ const right = Math.round(Math.max(horizontalRight, verticalRight)) - 0.5;
2930
+ const top = Math.round(horizontal.decreaseButton.y) + 0.5;
2931
+ if (right <= left) return;
2932
+ context.save();
2933
+ context.strokeStyle = borderColor;
2934
+ context.lineWidth = 1;
2935
+ context.beginPath();
2936
+ context.moveTo(left, top);
2937
+ context.lineTo(right, top);
2938
+ context.stroke();
2891
2939
  context.restore();
2892
2940
  }
2893
2941
  function drawAxis(context, layout, axis, borderColor) {
2894
- drawTrack(context, layout.track, borderColor);
2895
2942
  drawButton(context, layout.decreaseButton, axis, "decrease");
2896
2943
  drawButton(context, layout.increaseButton, axis, "increase");
2897
2944
  drawThumb(context, layout.thumb);
2945
+ drawTrackBorder(context, layout.track, axis, borderColor);
2946
+ }
2947
+ function drawTrackBorder(context, rect, axis, borderColor) {
2948
+ const left = Math.round(rect.x) + 0.5;
2949
+ const right = Math.round(rect.x + rect.width) - 0.5;
2950
+ const top = Math.round(rect.y) + 0.5;
2951
+ const bottom = Math.round(rect.y + rect.height) - 0.5;
2952
+ if (right <= left || bottom <= top) return;
2953
+ context.save();
2954
+ context.strokeStyle = borderColor;
2955
+ context.lineWidth = 1;
2956
+ context.beginPath();
2957
+ if (axis === "horizontal") {
2958
+ context.moveTo(left, top);
2959
+ context.lineTo(right, top);
2960
+ } else {
2961
+ context.moveTo(left, top);
2962
+ context.lineTo(left, bottom);
2963
+ }
2964
+ context.stroke();
2965
+ context.restore();
2898
2966
  }
2899
- function drawTrack(context, rect, borderColor) {
2900
- context.fillStyle = "rgba(148, 163, 184, 0.16)";
2967
+ function drawVerticalScrollbarFrame(context, layout, frame, borderColor) {
2968
+ const left = Math.round(layout.decreaseButton.x) + 0.5;
2969
+ const top = Math.round(frame.top) + 0.5;
2970
+ const bottom = Math.round(frame.top + frame.height) - 0.5;
2971
+ const headerBottom = Math.round(frame.headerBottom) + 0.5;
2972
+ if (bottom <= top) return;
2973
+ context.save();
2974
+ context.fillStyle = frame.headerBackgroundColor;
2975
+ context.fillRect(
2976
+ layout.decreaseButton.x,
2977
+ frame.top,
2978
+ layout.decreaseButton.width,
2979
+ Math.max(frame.headerBottom - frame.top, 0)
2980
+ );
2981
+ context.fillStyle = frame.bodyBackgroundColor;
2982
+ context.fillRect(
2983
+ layout.decreaseButton.x,
2984
+ frame.headerBottom,
2985
+ layout.decreaseButton.width,
2986
+ Math.max(frame.top + frame.height - frame.headerBottom, 0)
2987
+ );
2901
2988
  context.strokeStyle = borderColor;
2902
2989
  context.lineWidth = 1;
2903
- roundRect(context, rect, rect.height / 2);
2904
- context.fill();
2990
+ context.beginPath();
2991
+ context.moveTo(left, top);
2992
+ context.lineTo(left, bottom);
2905
2993
  context.stroke();
2994
+ if (headerBottom > top && headerBottom < bottom) {
2995
+ const right = Math.round(layout.decreaseButton.x + layout.decreaseButton.width) - 0.5;
2996
+ context.beginPath();
2997
+ context.moveTo(left, headerBottom);
2998
+ context.lineTo(right, headerBottom);
2999
+ context.stroke();
3000
+ }
3001
+ context.restore();
2906
3002
  }
2907
3003
  function drawThumb(context, rect) {
2908
3004
  context.fillStyle = "rgba(100, 116, 139, 0.8)";
@@ -2910,28 +3006,25 @@ function drawThumb(context, rect) {
2910
3006
  context.fill();
2911
3007
  }
2912
3008
  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
3009
  context.fillStyle = "rgba(71, 85, 105, 0.88)";
2920
3010
  context.beginPath();
2921
3011
  const centerX = rect.x + rect.width / 2;
2922
3012
  const centerY = rect.y + rect.height / 2;
3013
+ const iconSize = Math.min(rect.width, rect.height) * 0.64;
3014
+ const directionOffset = iconSize * 0.4;
3015
+ const crossAxisOffset = iconSize / 2;
2923
3016
  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;
3017
+ const tipX = centerX + (direction === "decrease" ? -1 : 1) * directionOffset;
3018
+ const tailX = centerX + (direction === "decrease" ? 1 : -1) * directionOffset;
2926
3019
  context.moveTo(tipX, centerY);
2927
- context.lineTo(tailX, centerY - rect.height * 0.22);
2928
- context.lineTo(tailX, centerY + rect.height * 0.22);
3020
+ context.lineTo(tailX, centerY - crossAxisOffset);
3021
+ context.lineTo(tailX, centerY + crossAxisOffset);
2929
3022
  } 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;
3023
+ const tipY = centerY + (direction === "decrease" ? -1 : 1) * directionOffset;
3024
+ const tailY = centerY + (direction === "decrease" ? 1 : -1) * directionOffset;
2932
3025
  context.moveTo(centerX, tipY);
2933
- context.lineTo(centerX - rect.width * 0.22, tailY);
2934
- context.lineTo(centerX + rect.width * 0.22, tailY);
3026
+ context.lineTo(centerX - crossAxisOffset, tailY);
3027
+ context.lineTo(centerX + crossAxisOffset, tailY);
2935
3028
  }
2936
3029
  context.closePath();
2937
3030
  context.fill();
@@ -2984,25 +3077,22 @@ function drawPivotGridRect(context, rect, color, options) {
2984
3077
  context.stroke();
2985
3078
  context.restore();
2986
3079
  }
2987
- function drawPivotViewportRightBorder(context, rect, color) {
2988
- const x = rect.x + rect.width - 0.5;
2989
- context.save();
2990
- context.strokeStyle = color;
2991
- context.lineWidth = 1;
2992
- context.beginPath();
2993
- context.moveTo(x, rect.y);
2994
- context.lineTo(x, rect.y + rect.height);
2995
- context.stroke();
2996
- context.restore();
3080
+ function clipPivotViewport(context, rect, radius = 8) {
3081
+ appendRoundedRectPath(context, rect, radius);
3082
+ context.clip();
2997
3083
  }
2998
- function drawPivotViewportBottomBorder(context, rect, color) {
2999
- const y = rect.y + rect.height - 0.5;
3084
+ function drawPivotViewportBorder(context, rect, color, radius = 8) {
3085
+ const borderRect = {
3086
+ x: rect.x + 0.5,
3087
+ y: rect.y + 0.5,
3088
+ width: Math.max(rect.width - 1, 0),
3089
+ height: Math.max(rect.height - 1, 0)
3090
+ };
3091
+ if (borderRect.width <= 0 || borderRect.height <= 0) return;
3000
3092
  context.save();
3001
3093
  context.strokeStyle = color;
3002
3094
  context.lineWidth = 1;
3003
- context.beginPath();
3004
- context.moveTo(rect.x, y);
3005
- context.lineTo(rect.x + rect.width, y);
3095
+ appendRoundedRectPath(context, borderRect, radius);
3006
3096
  context.stroke();
3007
3097
  context.restore();
3008
3098
  }
@@ -3020,6 +3110,21 @@ function drawPivotHeaderBoundaries(context, options, color) {
3020
3110
  context.stroke();
3021
3111
  context.restore();
3022
3112
  }
3113
+ function appendRoundedRectPath(context, rect, radius) {
3114
+ const nextRadius = Math.max(
3115
+ Math.min(radius, rect.width / 2, rect.height / 2),
3116
+ 0
3117
+ );
3118
+ const right = rect.x + rect.width;
3119
+ const bottom = rect.y + rect.height;
3120
+ context.beginPath();
3121
+ context.moveTo(rect.x + nextRadius, rect.y);
3122
+ context.arcTo(right, rect.y, right, bottom, nextRadius);
3123
+ context.arcTo(right, bottom, rect.x, bottom, nextRadius);
3124
+ context.arcTo(rect.x, bottom, rect.x, rect.y, nextRadius);
3125
+ context.arcTo(rect.x, rect.y, right, rect.y, nextRadius);
3126
+ context.closePath();
3127
+ }
3023
3128
 
3024
3129
  // src/domain/layout/sticky-row-header.ts
3025
3130
  function resolveStickyRowHeaderContentRect(params) {
@@ -3056,7 +3161,15 @@ function renderPivotTable(params) {
3056
3161
  activeNodePathKey
3057
3162
  );
3058
3163
  const areaSelection = isPivotAreaSelection(layout, params.selection);
3164
+ const viewportWidth = resolvePivotViewportWidth(layout);
3059
3165
  context.clearRect(0, 0, layout.width, layout.height);
3166
+ context.save();
3167
+ clipPivotViewport(context, {
3168
+ x: 0,
3169
+ y: 0,
3170
+ width: viewportWidth,
3171
+ height: layout.height
3172
+ });
3060
3173
  context.font = `${theme.fontSize}px ${theme.fontFamily}`;
3061
3174
  context.textBaseline = "middle";
3062
3175
  withClip(context, layout.reportFilterRect, () => {
@@ -3170,17 +3283,13 @@ function renderPivotTable(params) {
3170
3283
  theme.borderColor
3171
3284
  );
3172
3285
  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(
3180
- context,
3181
- { x: 0, y: 0, width: layout.tableWidth, height: contentHeight },
3182
- theme.borderColor
3183
- );
3286
+ drawPivotScrollbars(context, layout.scrollbar, theme.borderColor, {
3287
+ top: 0,
3288
+ height: contentHeight,
3289
+ headerBottom: layout.bodyRect.y,
3290
+ headerBackgroundColor: theme.columnHeaderBackgroundColor,
3291
+ bodyBackgroundColor: theme.backgroundColor
3292
+ });
3184
3293
  const selectionRect = resolvePivotSelectionRect(layout, params.selection);
3185
3294
  if (selectionRect) {
3186
3295
  const selectionClipRect = resolvePivotSelectionClipRect(
@@ -3198,11 +3307,22 @@ function renderPivotTable(params) {
3198
3307
  if (params.resizeGuideX != null) {
3199
3308
  drawPivotResizeGuide(context, params.resizeGuideX, contentHeight);
3200
3309
  }
3310
+ context.restore();
3311
+ drawPivotViewportBorder(
3312
+ context,
3313
+ { x: 0, y: 0, width: viewportWidth, height: contentHeight },
3314
+ theme.borderColor
3315
+ );
3201
3316
  return {
3202
3317
  headerCellCount: layout.reportFilterCells.length + layout.rowFieldHeaderCells.length + layout.rowHeaderCells.length + layout.columnHeaderCells.length,
3203
3318
  bodyCellCount: layout.bodyCells.length
3204
3319
  };
3205
3320
  }
3321
+ function resolvePivotViewportWidth(layout) {
3322
+ const horizontalRight = layout.scrollbar.horizontal ? layout.scrollbar.horizontal.increaseButton.x + layout.scrollbar.horizontal.increaseButton.width : 0;
3323
+ const verticalRight = layout.scrollbar.vertical ? layout.scrollbar.vertical.decreaseButton.x + layout.scrollbar.vertical.decreaseButton.width : 0;
3324
+ return Math.max(layout.tableWidth, horizontalRight, verticalRight);
3325
+ }
3206
3326
  function drawPivotResizeGuide(context, x, height) {
3207
3327
  context.save();
3208
3328
  context.strokeStyle = "#1677ff";
@@ -3956,6 +4076,16 @@ function hitTestPivotScrollbar(layout, x, y) {
3956
4076
  }
3957
4077
  return null;
3958
4078
  }
4079
+ function resolvePivotScrollbarTrackScroll(params) {
4080
+ const movableLength = Math.max(params.trackLength - params.thumbLength, 0);
4081
+ if (movableLength === 0) return 0;
4082
+ const thumbStart = utils.clamp(
4083
+ params.pointerValue - params.thumbLength / 2,
4084
+ params.trackStart,
4085
+ params.trackStart + movableLength
4086
+ );
4087
+ return (thumbStart - params.trackStart) / movableLength * params.maxScroll;
4088
+ }
3959
4089
  function resolveRole(layout, x, y) {
3960
4090
  if (utils.isPointInRect(x, y, layout.thumb)) return "thumb";
3961
4091
  if (utils.isPointInRect(x, y, layout.decreaseButton)) return "decrease-button";
@@ -4122,6 +4252,8 @@ var PivotTable = class {
4122
4252
  __publicField(this, "wheelScrollTarget", null);
4123
4253
  __publicField(this, "wheelScrollFrameId", null);
4124
4254
  __publicField(this, "wheelScrollLastTime", 0);
4255
+ __publicField(this, "continuousScrollTimer", null);
4256
+ __publicField(this, "continuousScrollPointerId", null);
4125
4257
  __publicField(this, "resizeSession", null);
4126
4258
  __publicField(this, "resizeGuideX", null);
4127
4259
  __publicField(this, "suppressNextClick", false);
@@ -4200,6 +4332,7 @@ var PivotTable = class {
4200
4332
  this.scheduleRender();
4201
4333
  });
4202
4334
  __publicField(this, "handleClick", (event) => {
4335
+ this.options.ui?.onContextMenuRequest?.(null);
4203
4336
  if (this.suppressNextClick) {
4204
4337
  this.suppressNextClick = false;
4205
4338
  return;
@@ -4518,6 +4651,8 @@ var PivotTable = class {
4518
4651
  });
4519
4652
  __publicField(this, "handlePointerUp", (event) => {
4520
4653
  if (!this.host) return;
4654
+ const endedContinuousScroll = this.continuousScrollPointerId === event.pointerId;
4655
+ if (endedContinuousScroll) this.stopContinuousScrollbarScroll();
4521
4656
  const endedCellSelection = this.cellSelectionDrag?.pointerId === event.pointerId;
4522
4657
  if (endedCellSelection) this.cellSelectionDrag = null;
4523
4658
  if (this.textSelectionDrag?.pointerId === event.pointerId) {
@@ -4536,7 +4671,7 @@ var PivotTable = class {
4536
4671
  if (this.scrollbarDrag?.pointerId === event.pointerId) {
4537
4672
  this.scrollbarDrag = null;
4538
4673
  }
4539
- if (!this.resizeSession && !this.scrollbarDrag && !endedCellSelection && !endedRowHeaderSelection && !this.host.root.hasPointerCapture(event.pointerId))
4674
+ if (!this.resizeSession && !this.scrollbarDrag && !endedContinuousScroll && !endedCellSelection && !endedRowHeaderSelection && !this.host.root.hasPointerCapture(event.pointerId))
4540
4675
  return;
4541
4676
  if (this.host.root.hasPointerCapture(event.pointerId)) {
4542
4677
  this.host.root.releasePointerCapture(event.pointerId);
@@ -4782,6 +4917,7 @@ var PivotTable = class {
4782
4917
  if (this.frameId !== null) cancelAnimationFrame(this.frameId);
4783
4918
  this.frameId = null;
4784
4919
  this.stopWheelScrollSmoothing();
4920
+ this.stopContinuousScrollbarScroll();
4785
4921
  this.stopResizeObserver?.();
4786
4922
  this.stopResizeObserver = null;
4787
4923
  this.host?.root.removeEventListener("pointermove", this.handlePointerMove);
@@ -5150,17 +5286,27 @@ var PivotTable = class {
5150
5286
  event.preventDefault();
5151
5287
  this.suppressNextClick = true;
5152
5288
  if (hit.role === "decrease-button" || hit.role === "increase-button") {
5153
- this.stepScrollbar(hit.axis, hit.role === "decrease-button" ? -1 : 1);
5289
+ this.startContinuousScrollbarScroll(
5290
+ hit.axis,
5291
+ hit.role === "decrease-button" ? -1 : 1,
5292
+ event.pointerId
5293
+ );
5294
+ this.host.root.setPointerCapture(event.pointerId);
5154
5295
  return;
5155
5296
  }
5156
5297
  const rootRect = this.host.root.getBoundingClientRect();
5157
5298
  const pointer = hit.axis === "horizontal" ? event.clientX - rootRect.left : event.clientY - rootRect.top;
5158
5299
  if (hit.role === "track") {
5159
- const thumbStart2 = hit.axis === "horizontal" ? axisLayout.thumb.x : axisLayout.thumb.y;
5160
- const direction = pointer < thumbStart2 ? -1 : 1;
5300
+ const nextScroll = resolvePivotScrollbarTrackScroll({
5301
+ pointerValue: pointer,
5302
+ trackStart: hit.axis === "horizontal" ? axisLayout.track.x : axisLayout.track.y,
5303
+ trackLength: hit.axis === "horizontal" ? axisLayout.track.width : axisLayout.track.height,
5304
+ thumbLength: hit.axis === "horizontal" ? axisLayout.thumb.width : axisLayout.thumb.height,
5305
+ maxScroll: hit.axis === "horizontal" ? this.layout.viewport.maxScrollLeft : this.layout.viewport.maxScrollTop
5306
+ });
5161
5307
  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)
5308
+ hit.axis === "horizontal" ? nextScroll : this.scroll.left,
5309
+ hit.axis === "vertical" ? nextScroll : this.scroll.top
5164
5310
  );
5165
5311
  return;
5166
5312
  }
@@ -5204,6 +5350,21 @@ var PivotTable = class {
5204
5350
  this.scroll.top + (axis === "vertical" ? direction * this.layout.rowHeight : 0)
5205
5351
  );
5206
5352
  }
5353
+ startContinuousScrollbarScroll(axis, direction, pointerId) {
5354
+ this.stopContinuousScrollbarScroll();
5355
+ this.continuousScrollPointerId = pointerId;
5356
+ this.stepScrollbar(axis, direction);
5357
+ this.continuousScrollTimer = setInterval(() => {
5358
+ this.stepScrollbar(axis, direction);
5359
+ }, 80);
5360
+ }
5361
+ stopContinuousScrollbarScroll() {
5362
+ if (this.continuousScrollTimer !== null) {
5363
+ clearInterval(this.continuousScrollTimer);
5364
+ this.continuousScrollTimer = null;
5365
+ }
5366
+ this.continuousScrollPointerId = null;
5367
+ }
5207
5368
  applyScrollPosition(left, top) {
5208
5369
  if (!this.layout) return;
5209
5370
  const next = {