@odoo/o-spreadsheet 18.4.0-alpha.9 → 18.4.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.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.4.0-alpha.9
6
- * @date 2025-06-19T18:23:22.025Z
7
- * @hash 6d4d685
5
+ * @version 18.4.1
6
+ * @date 2025-06-27T09:13:01.303Z
7
+ * @hash 5cecc0e
8
8
  */
9
9
 
10
10
  'use strict';
@@ -164,6 +164,7 @@ const FROZEN_PANE_HEADER_BORDER_COLOR = "#BCBCBC";
164
164
  const FROZEN_PANE_BORDER_COLOR = "#DADFE8";
165
165
  const COMPOSER_ASSISTANT_COLOR = "#9B359B";
166
166
  const COLOR_TRANSPARENT = "#00000000";
167
+ const TABLE_HOVER_BACKGROUND_COLOR = "#017E8414";
167
168
  const CHART_WATERFALL_POSITIVE_COLOR = "#4EA7F2";
168
169
  const CHART_WATERFALL_NEGATIVE_COLOR = "#EA6175";
169
170
  const CHART_WATERFALL_SUBTOTAL_COLOR = "#AAAAAA";
@@ -6849,19 +6850,17 @@ function getDefaultContextFont(fontSize, bold = false, italic = false) {
6849
6850
  const textWidthCache = {};
6850
6851
  function computeTextWidth(context, text, style, fontUnit = "pt") {
6851
6852
  const font = computeTextFont(style, fontUnit);
6852
- context.save();
6853
- context.font = font;
6854
- const width = computeCachedTextWidth(context, text);
6855
- context.restore();
6856
- return width;
6853
+ return computeCachedTextWidth(context, text, font);
6857
6854
  }
6858
- function computeCachedTextWidth(context, text) {
6859
- const font = context.font;
6855
+ function computeCachedTextWidth(context, text, font) {
6860
6856
  if (!textWidthCache[font]) {
6861
6857
  textWidthCache[font] = {};
6862
6858
  }
6863
6859
  if (textWidthCache[font][text] === undefined) {
6860
+ const oldFont = context.font;
6861
+ context.font = font;
6864
6862
  textWidthCache[font][text] = context.measureText(text).width;
6863
+ context.font = oldFont;
6865
6864
  }
6866
6865
  return textWidthCache[font][text];
6867
6866
  }
@@ -7026,19 +7025,19 @@ function getContextFontSize(font) {
7026
7025
  }
7027
7026
  // Inspired from https://stackoverflow.com/a/10511598
7028
7027
  function clipTextWithEllipsis(ctx, text, maxWidth) {
7029
- let width = computeCachedTextWidth(ctx, text);
7028
+ let width = computeCachedTextWidth(ctx, text, ctx.font);
7030
7029
  if (width <= maxWidth) {
7031
7030
  return text;
7032
7031
  }
7033
7032
  const ellipsis = "…";
7034
- const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis);
7033
+ const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis, ctx.font);
7035
7034
  if (width <= ellipsisWidth) {
7036
7035
  return text;
7037
7036
  }
7038
7037
  let len = text.length;
7039
7038
  while (width >= maxWidth - ellipsisWidth && len-- > 0) {
7040
7039
  text = text.substring(0, len);
7041
- width = computeCachedTextWidth(ctx, text);
7040
+ width = computeCachedTextWidth(ctx, text, ctx.font);
7042
7041
  }
7043
7042
  return text + ellipsis;
7044
7043
  }
@@ -7252,6 +7251,63 @@ function parseOSClipboardContent(content, clipboardId) {
7252
7251
  };
7253
7252
  return osClipboardContent;
7254
7253
  }
7254
+ /**
7255
+ * Applies each clipboard handler to paste its corresponding data into the target.
7256
+ */
7257
+ const applyClipboardHandlersPaste = (handlers, copiedData, target, options) => {
7258
+ handlers.forEach(({ handlerName, handler }) => {
7259
+ const data = copiedData[handlerName];
7260
+ if (data) {
7261
+ handler.paste(target, data, options);
7262
+ }
7263
+ });
7264
+ };
7265
+ /**
7266
+ * Returns the paste target based on clipboard handlers.
7267
+ * Also includes the full affected zone and the list of pasted zones for selection.
7268
+ */
7269
+ function getPasteTargetFromHandlers(sheetId, zones, copiedData, handlers, options) {
7270
+ let zone = undefined;
7271
+ const selectedZones = [];
7272
+ const target = {
7273
+ sheetId,
7274
+ zones,
7275
+ };
7276
+ for (const { handlerName, handler } of handlers) {
7277
+ const handlerData = copiedData[handlerName];
7278
+ if (!handlerData) {
7279
+ continue;
7280
+ }
7281
+ const currentTarget = handler.getPasteTarget(sheetId, zones, handlerData, options);
7282
+ if (currentTarget.figureId) {
7283
+ target.figureId = currentTarget.figureId;
7284
+ }
7285
+ for (const targetZone of currentTarget.zones) {
7286
+ selectedZones.push(targetZone);
7287
+ if (zone === undefined) {
7288
+ zone = targetZone;
7289
+ continue;
7290
+ }
7291
+ zone = union(zone, targetZone);
7292
+ }
7293
+ }
7294
+ return {
7295
+ target,
7296
+ zone,
7297
+ selectedZones,
7298
+ };
7299
+ }
7300
+ /**
7301
+ * Updates the selection after a paste operation.
7302
+ */
7303
+ const selectPastedZone = (selection, sourceZones, pastedZones) => {
7304
+ const anchorCell = {
7305
+ col: sourceZones[0].left,
7306
+ row: sourceZones[0].top,
7307
+ };
7308
+ selection.getBackToDefault();
7309
+ selection.selectZone({ cell: anchorCell, zone: union(...pastedZones) }, { scrollIntoView: false });
7310
+ };
7255
7311
 
7256
7312
  class ClipboardHandler {
7257
7313
  getters;
@@ -9956,8 +10012,15 @@ function getDependencyContainer(env) {
9956
10012
  const ModelStore = createAbstractStore("Model");
9957
10013
 
9958
10014
  class RendererStore {
9959
- mutators = ["register", "unRegister", "drawLayer"];
10015
+ mutators = ["register", "unRegister", "draw", "startAnimation", "stopAnimation"];
9960
10016
  renderers = {};
10017
+ model;
10018
+ context = undefined;
10019
+ animationFrameId = null;
10020
+ registeredAnimations = new Set();
10021
+ constructor(get) {
10022
+ this.model = get(ModelStore);
10023
+ }
9961
10024
  register(renderer) {
9962
10025
  if (!renderer.renderingLayers.length) {
9963
10026
  return;
@@ -9974,17 +10037,54 @@ class RendererStore {
9974
10037
  this.renderers[layer] = this.renderers[layer].filter((r) => r !== renderer);
9975
10038
  }
9976
10039
  }
9977
- drawLayer(context, layer) {
10040
+ drawLayer(context, layer, timeStamp) {
9978
10041
  const renderers = this.renderers[layer];
9979
10042
  if (renderers) {
9980
10043
  for (const renderer of renderers) {
9981
10044
  context.ctx.save();
9982
- renderer.drawLayer(context, layer);
10045
+ renderer.drawLayer(context, layer, timeStamp);
9983
10046
  context.ctx.restore();
9984
10047
  }
9985
10048
  }
9986
10049
  return "noStateChange";
9987
10050
  }
10051
+ draw(context, timestamp) {
10052
+ context = context || this.context;
10053
+ if (!context) {
10054
+ throw new Error("Rendering context is not defined");
10055
+ }
10056
+ this.context = context;
10057
+ for (const layer of OrderedLayers()) {
10058
+ this.model.drawLayer(context, layer);
10059
+ this.drawLayer(context, layer, timestamp);
10060
+ }
10061
+ return "noStateChange";
10062
+ }
10063
+ startAnimation(animationId) {
10064
+ this.registeredAnimations.add(animationId);
10065
+ if (!this.animationFrameId) {
10066
+ const animationCallback = (timestamp) => {
10067
+ this.animationFrameId = requestAnimationFrame(animationCallback);
10068
+ this.draw(undefined, timestamp);
10069
+ };
10070
+ this.animationFrameId = requestAnimationFrame(animationCallback);
10071
+ }
10072
+ return "noStateChange";
10073
+ }
10074
+ stopAnimation(animationId) {
10075
+ this.registeredAnimations.delete(animationId);
10076
+ if (this.registeredAnimations.size === 0 && this.animationFrameId !== null) {
10077
+ cancelAnimationFrame(this.animationFrameId);
10078
+ this.animationFrameId = null;
10079
+ }
10080
+ return "noStateChange";
10081
+ }
10082
+ dispose() {
10083
+ if (this.animationFrameId) {
10084
+ cancelAnimationFrame(this.animationFrameId);
10085
+ this.animationFrameId = null;
10086
+ }
10087
+ }
9988
10088
  }
9989
10089
 
9990
10090
  class SpreadsheetStore extends DisposableStore {
@@ -10008,7 +10108,7 @@ class SpreadsheetStore extends DisposableStore {
10008
10108
  }
10009
10109
  handle(cmd) { }
10010
10110
  finalize() { }
10011
- drawLayer(ctx, layer) { }
10111
+ drawLayer(ctx, layer, timestamp) { }
10012
10112
  }
10013
10113
 
10014
10114
  const VOID_COMPOSER = {
@@ -23730,11 +23830,11 @@ function drawTitle(ctx, config) {
23730
23830
  function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
23731
23831
  const maxValue = runtime.maxValue;
23732
23832
  const minValue = runtime.minValue;
23733
- const gaugeValue = runtime.gaugeValue;
23833
+ const gaugeValue = getGaugeValue(runtime, "animated");
23734
23834
  const gaugeRect = getGaugeRect(boundingRect, runtime.title.text);
23735
23835
  const gaugeArcWidth = gaugeRect.width / 6;
23736
23836
  const gaugePercentage = gaugeValue
23737
- ? (gaugeValue.value - minValue.value) / (maxValue.value - minValue.value)
23837
+ ? (gaugeValue - minValue.value) / (maxValue.value - minValue.value)
23738
23838
  : 0;
23739
23839
  const gaugeValuePosition = {
23740
23840
  x: boundingRect.width / 2,
@@ -23747,7 +23847,7 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
23747
23847
  }
23748
23848
  // Scale down the font size if the text is too long
23749
23849
  const maxTextWidth = gaugeRect.width / 2;
23750
- const gaugeLabel = gaugeValue?.label || "-";
23850
+ const gaugeLabel = runtime.gaugeValue?.label || "-";
23751
23851
  if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) {
23752
23852
  gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
23753
23853
  }
@@ -23887,7 +23987,7 @@ function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
23887
23987
  return inflectionValues;
23888
23988
  }
23889
23989
  function getGaugeColor(runtime) {
23890
- const gaugeValue = runtime.gaugeValue?.value;
23990
+ const gaugeValue = getGaugeValue(runtime, "final");
23891
23991
  if (gaugeValue === undefined) {
23892
23992
  return GAUGE_BACKGROUND_COLOR;
23893
23993
  }
@@ -23975,6 +24075,11 @@ function getRectangleTangentToCircle(angle, radius, circleCenterX, circleCenterY
23975
24075
  };
23976
24076
  return { bottomLeft, bottomRight, topRight, topLeft };
23977
24077
  }
24078
+ function getGaugeValue(runtime, mode) {
24079
+ return mode === "animated" && runtime.animationValue !== undefined
24080
+ ? runtime.animationValue
24081
+ : runtime.gaugeValue?.value;
24082
+ }
23978
24083
 
23979
24084
  const CHART_COMMON_OPTIONS = {
23980
24085
  // https://www.chartjs.org/docs/latest/general/responsive.html
@@ -26329,6 +26434,365 @@ function createBarChartRuntime(chart, getters) {
26329
26434
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
26330
26435
  }
26331
26436
 
26437
+ const cellAnimationRegistry = new Registry();
26438
+ cellAnimationRegistry.add("animatedBackgroundColorChange", {
26439
+ id: "animatedBackgroundColorChange",
26440
+ easingFn: "easeOutCubic",
26441
+ hasAnimation: (oldBox, newBox) => {
26442
+ return oldBox?.style?.fillColor !== newBox?.style?.fillColor;
26443
+ },
26444
+ updateAnimation: function (progress, animatedBox, oldBox, newBox) {
26445
+ const colorScale = getColorScale([
26446
+ { value: 0, color: oldBox.style.fillColor || "#ffffff" },
26447
+ { value: 1, color: newBox.style.fillColor || "#ffffff" },
26448
+ ]);
26449
+ animatedBox.style.fillColor = colorScale(EASING_FN[this.easingFn](progress));
26450
+ },
26451
+ });
26452
+ cellAnimationRegistry.add("animatedTextColorChange", {
26453
+ id: "animatedTextColorChange",
26454
+ easingFn: "easeOutCubic",
26455
+ hasAnimation: (oldBox, newBox) => {
26456
+ return oldBox?.style?.textColor !== newBox?.style?.textColor;
26457
+ },
26458
+ updateAnimation: function (progress, animatedBox, oldBox, newBox) {
26459
+ const colorScale = getColorScale([
26460
+ { value: 0, color: oldBox.style.textColor || "#000000" },
26461
+ { value: 1, color: newBox.style.textColor || "#000000" },
26462
+ ]);
26463
+ animatedBox.style.textColor = colorScale(EASING_FN[this.easingFn](progress));
26464
+ },
26465
+ });
26466
+ cellAnimationRegistry.add("animatedDataBar", {
26467
+ id: "animatedDataBar",
26468
+ easingFn: "easeOutCubic",
26469
+ hasAnimation: (oldBox, newBox) => {
26470
+ return oldBox?.dataBarFill?.percentage !== newBox?.dataBarFill?.percentage;
26471
+ },
26472
+ updateAnimation: function (progress, animatedBox, oldBox, newBox) {
26473
+ const startingPercentage = oldBox?.dataBarFill?.percentage || 0;
26474
+ const endingPercentage = newBox?.dataBarFill?.percentage || 0;
26475
+ const value = EASING_FN[this.easingFn](progress);
26476
+ const percentage = startingPercentage + (endingPercentage - startingPercentage) * value;
26477
+ animatedBox.dataBarFill = {
26478
+ color: newBox.dataBarFill?.color || oldBox.dataBarFill?.color || "#ffffff",
26479
+ percentage: percentage,
26480
+ };
26481
+ },
26482
+ });
26483
+ cellAnimationRegistry.add("textFadeIn", {
26484
+ id: "textFadeIn",
26485
+ easingFn: "easeInCubic",
26486
+ hasAnimation: (oldBox, newBox) => {
26487
+ const oldText = oldBox?.content?.textLines?.join("\n");
26488
+ const newText = newBox?.content?.textLines?.join("\n");
26489
+ return Boolean(!oldText && newText);
26490
+ },
26491
+ updateAnimation: function (progress, animatedBox, oldBox, newBox) {
26492
+ animatedBox.textOpacity = EASING_FN[this.easingFn](progress);
26493
+ },
26494
+ });
26495
+ cellAnimationRegistry.add("textFadeOut", {
26496
+ id: "textFadeOut",
26497
+ easingFn: "easeOutCubic",
26498
+ hasAnimation: (oldBox, newBox) => {
26499
+ const oldText = oldBox?.content?.textLines?.join("\n");
26500
+ const newText = newBox?.content?.textLines?.join("\n");
26501
+ return Boolean(oldText && !newText);
26502
+ },
26503
+ updateAnimation: function (progress, animatedBox, oldBox, newBox) {
26504
+ const textOpacity = 1 - EASING_FN[this.easingFn](progress);
26505
+ const style = { ...oldBox.style };
26506
+ delete style.fillColor;
26507
+ animatedBox.textOpacity = textOpacity;
26508
+ animatedBox.content = oldBox.content;
26509
+ animatedBox.clipRect = oldBox.clipRect;
26510
+ Object.assign(animatedBox.style, style);
26511
+ },
26512
+ });
26513
+ cellAnimationRegistry.add("iconFadeIn", {
26514
+ id: "iconFadeIn",
26515
+ easingFn: "easeInCubic",
26516
+ hasAnimation: (oldBox, newBox) => {
26517
+ return Boolean((!oldBox?.icons?.left && newBox?.icons?.left) ||
26518
+ (!oldBox?.icons?.right && newBox?.icons?.right) ||
26519
+ (!oldBox?.icons?.center && newBox?.icons?.center));
26520
+ },
26521
+ updateAnimation: function (progress, animatedBox, oldBox, newBox) {
26522
+ const iconOpacity = EASING_FN[this.easingFn](progress);
26523
+ if (animatedBox.icons.left && newBox.icons.left && !oldBox.icons.left) {
26524
+ animatedBox.icons.left.opacity = iconOpacity;
26525
+ }
26526
+ if (animatedBox.icons.right && newBox.icons.right && !oldBox.icons.right) {
26527
+ animatedBox.icons.right.opacity = iconOpacity;
26528
+ }
26529
+ if (animatedBox.icons.center && newBox.icons.center && !oldBox.icons.center) {
26530
+ animatedBox.icons.center.opacity = iconOpacity;
26531
+ }
26532
+ },
26533
+ });
26534
+ cellAnimationRegistry.add("iconFadeOut", {
26535
+ id: "iconFadeOut",
26536
+ easingFn: "easeOutCubic",
26537
+ hasAnimation: (oldBox, newBox) => {
26538
+ return Boolean((oldBox?.icons?.left && !newBox?.icons?.left) ||
26539
+ (oldBox?.icons?.right && !newBox?.icons?.right) ||
26540
+ (oldBox?.icons?.center && !newBox?.icons?.center));
26541
+ },
26542
+ updateAnimation: function (progress, animatedBox, oldBox, newBox) {
26543
+ const iconOpacity = 1 - EASING_FN[this.easingFn](progress);
26544
+ if (!animatedBox.icons) {
26545
+ animatedBox.icons = {};
26546
+ }
26547
+ if (oldBox.icons.left && !newBox.icons.left) {
26548
+ animatedBox.icons.left = { ...oldBox.icons.left, opacity: iconOpacity };
26549
+ }
26550
+ if (oldBox.icons.right && !newBox.icons.right) {
26551
+ animatedBox.icons.right = { ...oldBox.icons.right, opacity: iconOpacity };
26552
+ }
26553
+ if (oldBox.icons.center && !newBox.icons.center) {
26554
+ animatedBox.icons.center = { ...oldBox.icons.center, opacity: iconOpacity };
26555
+ }
26556
+ },
26557
+ });
26558
+ cellAnimationRegistry.add("textChange", {
26559
+ id: "textChange",
26560
+ easingFn: "easeOutCubic",
26561
+ hasAnimation: (oldBox, newBox) => {
26562
+ const oldText = oldBox?.content?.textLines?.join("\n");
26563
+ const newText = newBox?.content?.textLines?.join("\n");
26564
+ // Note: here, we also animate changes to icons layout (margins/size change, or icon appearing/disappearing)
26565
+ // because a change to the icon layout will impact where the text is positioned.
26566
+ return Boolean(oldText && newText && (oldText !== newText || hasIconLayoutChange(newBox, oldBox)));
26567
+ },
26568
+ updateAnimation: function (progress, animatedBox, oldBox, newBox) {
26569
+ const value = EASING_FN[this.easingFn](progress);
26570
+ const slideInY = newBox.y + (value - 1) * newBox.height;
26571
+ const slideOutY = newBox.y + value * newBox.height;
26572
+ const iconLayoutChange = hasIconLayoutChange(newBox, oldBox);
26573
+ const slideInBox = {
26574
+ id: newBox.id + "-text-slide-in",
26575
+ x: newBox.x,
26576
+ y: slideInY,
26577
+ width: newBox.width,
26578
+ height: newBox.height,
26579
+ style: { ...newBox.style },
26580
+ skipCellGridLines: true,
26581
+ content: newBox.content ? { ...newBox.content } : undefined,
26582
+ clipRect: newBox.clipRect || {
26583
+ ...newBox,
26584
+ // large width to avoid clipping the text it it didn't have a clipRect before,
26585
+ // we mainly want to clip the Y for the animation
26586
+ x: Math.max(0, newBox.x - (newBox.content?.width || 0)),
26587
+ width: newBox.width + (newBox.content?.width || 0) * 2,
26588
+ },
26589
+ icons: iconLayoutChange
26590
+ ? addClipRectToIcons(newBox.icons, newBox)
26591
+ : makeIconsEmpty(newBox.icons),
26592
+ };
26593
+ const slideOutBox = {
26594
+ id: oldBox.id + "-text-slide-out",
26595
+ x: newBox.x,
26596
+ y: slideOutY,
26597
+ width: newBox.width,
26598
+ height: newBox.height,
26599
+ style: { ...oldBox.style },
26600
+ skipCellGridLines: true,
26601
+ content: oldBox.content ? { ...oldBox.content } : undefined,
26602
+ clipRect: oldBox.clipRect || {
26603
+ ...newBox,
26604
+ x: Math.max(0, newBox.x - (oldBox.content?.width || 0)),
26605
+ width: newBox.width + (oldBox.content?.width || 0) * 2,
26606
+ },
26607
+ icons: iconLayoutChange
26608
+ ? addClipRectToIcons(oldBox.icons, newBox)
26609
+ : makeIconsEmpty(oldBox.icons),
26610
+ };
26611
+ if (newBox.content && oldBox.content && slideInBox.content && slideOutBox.content) {
26612
+ const slideInContentY = newBox.content.y + (value - 1) * newBox.height;
26613
+ const slideOutContentY = newBox.content.y + value * newBox.height;
26614
+ slideInBox.content.y = slideInContentY;
26615
+ slideOutBox.content.y = slideOutContentY;
26616
+ }
26617
+ slideOutBox.style.fillColor = slideInBox.style.fillColor = undefined;
26618
+ animatedBox.content = undefined;
26619
+ animatedBox.icons = iconLayoutChange ? {} : animatedBox.icons;
26620
+ return { newBoxes: [slideInBox, slideOutBox] };
26621
+ },
26622
+ });
26623
+ cellAnimationRegistry.add("borderFadeIn", {
26624
+ id: "borderFadeIn",
26625
+ easingFn: "easeInCubic",
26626
+ hasAnimation: (oldBox, newBox) => {
26627
+ return Boolean((!oldBox?.border?.bottom && newBox?.border?.bottom) ||
26628
+ (!oldBox?.border?.top && newBox?.border?.top) ||
26629
+ (!oldBox?.border?.left && newBox?.border?.left) ||
26630
+ (!oldBox?.border?.right && newBox?.border?.right));
26631
+ },
26632
+ updateAnimation: function (progress, animatedBox, oldBox, newBox) {
26633
+ const borderOpacity = EASING_FN[this.easingFn](progress);
26634
+ if (animatedBox.border?.top && newBox.border?.top && !oldBox.border?.top) {
26635
+ animatedBox.border.top.opacity = borderOpacity;
26636
+ }
26637
+ if (animatedBox.border?.bottom && newBox.border?.bottom && !oldBox.border?.bottom) {
26638
+ animatedBox.border.bottom.opacity = borderOpacity;
26639
+ }
26640
+ if (animatedBox.border?.left && newBox.border?.left && !oldBox.border?.left) {
26641
+ animatedBox.border.left.opacity = borderOpacity;
26642
+ }
26643
+ if (animatedBox.border?.right && newBox.border?.right && !oldBox.border?.right) {
26644
+ animatedBox.border.right.opacity = borderOpacity;
26645
+ }
26646
+ },
26647
+ });
26648
+ cellAnimationRegistry.add("borderFadeOut", {
26649
+ id: "borderFadeOut",
26650
+ easingFn: "easeOutCubic",
26651
+ hasAnimation: (oldBox, newBox) => {
26652
+ return Boolean((oldBox?.border?.bottom && !newBox?.border?.bottom) ||
26653
+ (oldBox?.border?.top && !newBox?.border?.top) ||
26654
+ (oldBox?.border?.left && !newBox?.border?.left) ||
26655
+ (oldBox?.border?.right && !newBox?.border?.right));
26656
+ },
26657
+ updateAnimation: function (progress, animatedBox, oldBox, newBox) {
26658
+ const borderOpacity = 1 - EASING_FN[this.easingFn](progress);
26659
+ if (!animatedBox.border) {
26660
+ animatedBox.border = {};
26661
+ }
26662
+ if (oldBox.border?.top && !newBox.border?.top) {
26663
+ animatedBox.border.top = { ...oldBox.border.top, opacity: borderOpacity };
26664
+ }
26665
+ if (oldBox.border?.bottom && !newBox.border?.bottom) {
26666
+ animatedBox.border.bottom = { ...oldBox.border.bottom, opacity: borderOpacity };
26667
+ }
26668
+ if (oldBox.border?.left && !newBox.border?.left) {
26669
+ animatedBox.border.left = { ...oldBox.border.left, opacity: borderOpacity };
26670
+ }
26671
+ if (oldBox.border?.right && !newBox.border?.right) {
26672
+ animatedBox.border.right = { ...oldBox.border.right, opacity: borderOpacity };
26673
+ }
26674
+ },
26675
+ });
26676
+ cellAnimationRegistry.add("borderColorChange", {
26677
+ id: "borderColorChange",
26678
+ easingFn: "easeOutCubic",
26679
+ hasAnimation: (oldBox, newBox) => {
26680
+ const oldBorder = oldBox?.border;
26681
+ const newBorder = newBox?.border;
26682
+ if (!oldBorder || !newBorder) {
26683
+ return false;
26684
+ }
26685
+ return Boolean(oldBorder.bottom?.color !== newBorder.bottom?.color ||
26686
+ oldBorder.top?.color !== newBorder.top?.color ||
26687
+ oldBorder.left?.color !== newBorder.left?.color ||
26688
+ oldBorder.right?.color !== newBorder.right?.color);
26689
+ },
26690
+ updateAnimation: function (progress, animatedBox, oldBox, newBox) {
26691
+ const animateBorderColor = (side) => {
26692
+ const oldBorder = oldBox?.border?.[side];
26693
+ const newBorder = newBox?.border?.[side];
26694
+ const animatedBorder = animatedBox.border?.[side];
26695
+ if (oldBorder && newBorder && animatedBorder) {
26696
+ const colorScale = getColorScale([
26697
+ { value: 0, color: oldBorder.color || "#000000" },
26698
+ { value: 1, color: newBorder.color || "#000000" },
26699
+ ]);
26700
+ animatedBorder.color = colorScale(EASING_FN[this.easingFn](progress));
26701
+ }
26702
+ };
26703
+ animateBorderColor("top");
26704
+ animateBorderColor("bottom");
26705
+ animateBorderColor("left");
26706
+ animateBorderColor("right");
26707
+ },
26708
+ });
26709
+ cellAnimationRegistry.add("iconChange", {
26710
+ id: "iconChange",
26711
+ easingFn: "easeOutCubic",
26712
+ hasAnimation: (oldBox, newBox) => {
26713
+ return (!hasIconLayoutChange(newBox, oldBox) &&
26714
+ Boolean(oldBox?.icons?.center?.svg?.name !== newBox?.icons?.center?.svg?.name ||
26715
+ oldBox?.icons?.left?.svg?.name !== newBox?.icons?.left?.svg?.name ||
26716
+ oldBox?.icons?.right?.svg?.name !== newBox?.icons?.right?.svg?.name));
26717
+ },
26718
+ updateAnimation: function (progress, animatedBox, oldBox, newBox) {
26719
+ const value = EASING_FN[this.easingFn](progress);
26720
+ const slideInY = newBox.y + (value - 1) * newBox.height;
26721
+ const slideOutY = newBox.y + value * newBox.height;
26722
+ const newBoxes = [];
26723
+ const animateIconChange = (side) => {
26724
+ const oldIcon = oldBox.icons?.[side];
26725
+ const newIcon = newBox.icons?.[side];
26726
+ const slideInBox = {
26727
+ id: `${newBox.id}-icon-${side}-slide-in`,
26728
+ style: { verticalAlign: newBox.style.verticalAlign },
26729
+ x: newBox.x,
26730
+ y: slideInY,
26731
+ width: newBox.width,
26732
+ height: newBox.height,
26733
+ skipCellGridLines: true,
26734
+ icons: { [side]: { ...newIcon, clipRect: newBox } },
26735
+ };
26736
+ const slideOutBox = {
26737
+ id: `${newBox.id}-icon-${side}-slide-out`,
26738
+ style: { verticalAlign: oldBox.style.verticalAlign },
26739
+ x: newBox.x,
26740
+ y: slideOutY,
26741
+ width: newBox.width,
26742
+ height: newBox.height,
26743
+ skipCellGridLines: true,
26744
+ icons: { [side]: { ...oldIcon, clipRect: newBox } },
26745
+ };
26746
+ animatedBox.icons[side] = makeIconsEmpty(newBox.icons)[side];
26747
+ newBoxes.push(slideInBox, slideOutBox);
26748
+ };
26749
+ animateIconChange("left");
26750
+ animateIconChange("right");
26751
+ animateIconChange("center");
26752
+ return { newBoxes };
26753
+ },
26754
+ });
26755
+ const EASING_FN = {
26756
+ linear: (t) => t,
26757
+ easeInCubic: (t) => t * t * t,
26758
+ easeOutCubic: (t) => (t -= 1) * t * t + 1,
26759
+ easeInOutCubic: (t) => ((t /= 0.5) < 1 ? 0.5 * t * t * t : 0.5 * ((t -= 2) * t * t + 2)),
26760
+ easeOutQuart: (t) => -((t -= 1) * t * t * t - 1),
26761
+ };
26762
+ function makeIconsEmpty(icons) {
26763
+ return {
26764
+ left: icons.left ? { ...icons.left, svg: undefined } : undefined,
26765
+ right: icons.right ? { ...icons.right, svg: undefined } : undefined,
26766
+ center: icons.center ? { ...icons.center, svg: undefined } : undefined,
26767
+ };
26768
+ }
26769
+ function addClipRectToIcons(icons, clipRect) {
26770
+ return {
26771
+ left: icons.left ? { ...icons.left, clipRect } : undefined,
26772
+ right: icons.right ? { ...icons.right, clipRect } : undefined,
26773
+ center: icons.center ? { ...icons.center, clipRect } : undefined,
26774
+ };
26775
+ }
26776
+ /**
26777
+ * Check if the icons have appeared, disappeared or changed margin/size/align. Those changes affect where the text is positioned.
26778
+ */
26779
+ function hasIconLayoutChange(newBox, oldBox) {
26780
+ const hasLayoutChange = (newIcon, oldIcon) => {
26781
+ if (oldIcon && newIcon) {
26782
+ return !!(newIcon.horizontalAlign !== oldIcon.horizontalAlign ||
26783
+ newIcon.size !== oldIcon.size ||
26784
+ newIcon.margin !== oldIcon.margin ||
26785
+ (newIcon.svg && !oldIcon.svg) ||
26786
+ (!newIcon.svg && oldIcon.svg));
26787
+ }
26788
+ return !!((newIcon && !oldIcon) || (!newIcon && oldIcon));
26789
+ };
26790
+ return (hasLayoutChange(newBox?.icons.left, oldBox?.icons.left) ||
26791
+ hasLayoutChange(newBox?.icons.right, oldBox?.icons.right) ||
26792
+ hasLayoutChange(newBox?.icons.center, oldBox?.icons.center));
26793
+ }
26794
+
26795
+ const ANIMATION_DURATION = 1000;
26332
26796
  class GaugeChartComponent extends owl.Component {
26333
26797
  static template = "o-spreadsheet-GaugeChartComponent";
26334
26798
  static props = {
@@ -26336,16 +26800,101 @@ class GaugeChartComponent extends owl.Component {
26336
26800
  isFullScreen: { type: Boolean, optional: true },
26337
26801
  };
26338
26802
  canvas = owl.useRef("chartContainer");
26803
+ animationStore;
26339
26804
  get runtime() {
26340
26805
  return this.env.model.getters.getChartRuntime(this.props.figureUI.id);
26341
26806
  }
26342
26807
  setup() {
26343
- owl.useEffect(() => drawGaugeChart(this.canvas.el, this.runtime), () => {
26344
- const canvas = this.canvas.el;
26345
- const rect = canvas.getBoundingClientRect();
26808
+ if (this.env.model.getters.isDashboard()) {
26809
+ this.animationStore = useStore(ChartAnimationStore);
26810
+ }
26811
+ let animation = null;
26812
+ let lastRuntime = undefined;
26813
+ owl.useEffect(() => {
26814
+ if (this.env.isDashboard() &&
26815
+ lastRuntime === undefined && // first render
26816
+ this.animationStore?.animationPlayed[this.animationFigureId] !== "gauge") {
26817
+ animation = this.drawGaugeWithAnimation();
26818
+ this.animationStore?.disableAnimationForChart(this.animationFigureId, "gauge");
26819
+ }
26820
+ else if (this.env.isDashboard() &&
26821
+ lastRuntime !== undefined && // not first render
26822
+ !deepEquals(this.runtime, lastRuntime)) {
26823
+ animation = this.drawGaugeWithAnimation();
26824
+ this.animationStore?.disableAnimationForChart(this.animationFigureId, "gauge");
26825
+ }
26826
+ else {
26827
+ drawGaugeChart(this.canvasEl, this.runtime);
26828
+ }
26829
+ lastRuntime = this.runtime;
26830
+ return () => animation?.stop();
26831
+ }, () => {
26832
+ const rect = this.canvasEl.getBoundingClientRect();
26346
26833
  return [rect.width, rect.height, this.runtime, this.canvas.el, window.devicePixelRatio];
26347
26834
  });
26348
26835
  }
26836
+ drawGaugeWithAnimation() {
26837
+ drawGaugeChart(this.canvasEl, { ...this.runtime, animationValue: 0 });
26838
+ const gaugeValue = this.runtime.gaugeValue?.value || 0;
26839
+ const upperBound = this.runtime.maxValue.value;
26840
+ const finalValue = Math.sign(gaugeValue) * Math.min(Math.abs(gaugeValue), Math.abs(upperBound));
26841
+ if (finalValue === 0) {
26842
+ return null;
26843
+ }
26844
+ const lowerBound = this.runtime.minValue.value;
26845
+ const animation = new Animation(lowerBound, finalValue, ANIMATION_DURATION, (animationValue) => drawGaugeChart(this.canvasEl, { ...this.runtime, animationValue }));
26846
+ animation.start();
26847
+ return animation;
26848
+ }
26849
+ get canvasEl() {
26850
+ return this.canvas.el;
26851
+ }
26852
+ get animationFigureId() {
26853
+ return this.props.isFullScreen
26854
+ ? this.props.figureUI.id + "-fullscreen"
26855
+ : this.props.figureUI.id;
26856
+ }
26857
+ }
26858
+ /**
26859
+ * Animation interpolating values using the ease-out quartic curve function (chartJS default easing)
26860
+ */
26861
+ class Animation {
26862
+ startValue;
26863
+ endValue;
26864
+ duration;
26865
+ callback;
26866
+ startTime = undefined;
26867
+ animationFrameId = null;
26868
+ constructor(startValue, endValue, duration, callback) {
26869
+ this.startValue = startValue;
26870
+ this.endValue = endValue;
26871
+ this.duration = duration;
26872
+ this.callback = callback;
26873
+ }
26874
+ start() {
26875
+ this.animationFrameId = requestAnimationFrame(this.animate.bind(this));
26876
+ }
26877
+ stop() {
26878
+ if (this.animationFrameId) {
26879
+ cancelAnimationFrame(this.animationFrameId);
26880
+ this.animationFrameId = null;
26881
+ }
26882
+ }
26883
+ animate(timestamp) {
26884
+ if (!this.startTime) {
26885
+ this.startTime = timestamp;
26886
+ }
26887
+ const elapsed = timestamp - this.startTime;
26888
+ const progress = Math.min(elapsed / this.duration, 1);
26889
+ const currentValue = this.startValue + (this.endValue - this.startValue) * EASING_FN.easeOutQuart(progress);
26890
+ this.callback(currentValue);
26891
+ if (progress < 1) {
26892
+ this.animationFrameId = requestAnimationFrame(this.animate.bind(this));
26893
+ }
26894
+ else {
26895
+ this.stop();
26896
+ }
26897
+ }
26349
26898
  }
26350
26899
 
26351
26900
  class ComboChart extends AbstractChart {
@@ -32161,7 +32710,7 @@ class AbstractComposerStore extends SpreadsheetStore {
32161
32710
  }
32162
32711
  captureSelection(zone, col, row) {
32163
32712
  this.model.selection.capture(this, {
32164
- cell: { col: col || zone.left, row: row || zone.right },
32713
+ cell: { col: col ?? zone.left, row: row ?? zone.right },
32165
32714
  zone,
32166
32715
  }, {
32167
32716
  handleEvent: this.handleEvent.bind(this),
@@ -36538,6 +37087,7 @@ css /* scss */ `
36538
37087
  // We need here the svg of the icons that we need to convert to images for the renderer
36539
37088
  // -----------------------------------------------------------------------------
36540
37089
  const ARROW_DOWN = {
37090
+ name: "ARROW_DOWN",
36541
37091
  width: 448,
36542
37092
  height: 512,
36543
37093
  paths: [
@@ -36548,6 +37098,7 @@ const ARROW_DOWN = {
36548
37098
  ],
36549
37099
  };
36550
37100
  const ARROW_UP = {
37101
+ name: "ARROW_UP",
36551
37102
  width: 448,
36552
37103
  height: 512,
36553
37104
  paths: [
@@ -36558,6 +37109,7 @@ const ARROW_UP = {
36558
37109
  ],
36559
37110
  };
36560
37111
  const ARROW_RIGHT = {
37112
+ name: "ARROW_RIGHT",
36561
37113
  width: 448,
36562
37114
  height: 512,
36563
37115
  paths: [
@@ -36568,6 +37120,7 @@ const ARROW_RIGHT = {
36568
37120
  ],
36569
37121
  };
36570
37122
  const SMILE = {
37123
+ name: "SMILE",
36571
37124
  width: 496,
36572
37125
  height: 512,
36573
37126
  paths: [
@@ -36578,6 +37131,7 @@ const SMILE = {
36578
37131
  ],
36579
37132
  };
36580
37133
  const MEH = {
37134
+ name: "MEH",
36581
37135
  width: 496,
36582
37136
  height: 512,
36583
37137
  paths: [
@@ -36588,6 +37142,7 @@ const MEH = {
36588
37142
  ],
36589
37143
  };
36590
37144
  const FROWN = {
37145
+ name: "FROWN",
36591
37146
  width: 496,
36592
37147
  height: 512,
36593
37148
  paths: [
@@ -36599,22 +37154,26 @@ const FROWN = {
36599
37154
  };
36600
37155
  const DOT_PATH = "M256 9 a247 247 0 1 0.1 0 0";
36601
37156
  const GREEN_DOT = {
37157
+ name: "GREEN_DOT",
36602
37158
  width: 512,
36603
37159
  height: 512,
36604
37160
  paths: [{ fillColor: "#6AA84F", path: DOT_PATH }],
36605
37161
  };
36606
37162
  const YELLOW_DOT = {
37163
+ name: "YELLOW_DOT",
36607
37164
  width: 512,
36608
37165
  height: 512,
36609
37166
  paths: [{ fillColor: "#F0AD4E", path: DOT_PATH }],
36610
37167
  };
36611
37168
  const RED_DOT = {
37169
+ name: "RED_DOT",
36612
37170
  width: 512,
36613
37171
  height: 512,
36614
37172
  paths: [{ fillColor: "#E06666", path: DOT_PATH }],
36615
37173
  };
36616
37174
  function getCaretDownSvg(color) {
36617
37175
  return {
37176
+ name: "CARET_DOWN",
36618
37177
  width: 512,
36619
37178
  height: 512,
36620
37179
  paths: [{ fillColor: color.textColor || TEXT_BODY_MUTED, path: "M120 195 h270 l-135 130" }],
@@ -36622,6 +37181,7 @@ function getCaretDownSvg(color) {
36622
37181
  }
36623
37182
  function getHoveredCaretDownSvg(color) {
36624
37183
  return {
37184
+ name: "CARET_DOWN",
36625
37185
  width: 512,
36626
37186
  height: 512,
36627
37187
  paths: [
@@ -36633,6 +37193,7 @@ function getHoveredCaretDownSvg(color) {
36633
37193
  const CHIP_CARET_DOWN_PATH = "M40 185 h270 l-135 128";
36634
37194
  function getChipSvg(chipStyle) {
36635
37195
  return {
37196
+ name: "CHIP",
36636
37197
  width: 512,
36637
37198
  height: 512,
36638
37199
  paths: [{ fillColor: chipStyle.textColor || TEXT_BODY_MUTED, path: CHIP_CARET_DOWN_PATH }],
@@ -36640,6 +37201,7 @@ function getChipSvg(chipStyle) {
36640
37201
  }
36641
37202
  function getHoveredChipSvg(chipStyle) {
36642
37203
  return {
37204
+ name: "CHIP",
36643
37205
  width: 512,
36644
37206
  height: 512,
36645
37207
  paths: [
@@ -36652,16 +37214,19 @@ function getHoveredChipSvg(chipStyle) {
36652
37214
  };
36653
37215
  }
36654
37216
  const CHECKBOX_UNCHECKED = {
37217
+ name: "CHECKBOX_UNCHECKED",
36655
37218
  width: 512,
36656
37219
  height: 512,
36657
37220
  paths: [{ fillColor: GRAY_300, path: "M45,45 h422 v422 h-422 v-422 m30,30 v362 h362 v-362" }],
36658
37221
  };
36659
37222
  const CHECKBOX_UNCHECKED_HOVERED = {
37223
+ name: "CHECKBOX_UNCHECKED",
36660
37224
  width: 512,
36661
37225
  height: 512,
36662
37226
  paths: [{ fillColor: ACTION_COLOR, path: "M45,45 h422 v422 h-422 v-422 m30,30 v362 h362 v-362" }],
36663
37227
  };
36664
37228
  const CHECKBOX_CHECKED = {
37229
+ name: "CHECKBOX_CHECKED",
36665
37230
  width: 512,
36666
37231
  height: 512,
36667
37232
  paths: [
@@ -36674,6 +37239,7 @@ function getPivotIconSvg(isCollapsed, isHovered) {
36674
37239
  ? "M149,235 h213 v43 h-213 M235,149 h43 v213 h-43" // +
36675
37240
  : "M149,235 h213 v43 h-213"; // -
36676
37241
  return {
37242
+ name: "PIVOT_ICON",
36677
37243
  width: 512,
36678
37244
  height: 512,
36679
37245
  paths: [
@@ -36700,6 +37266,7 @@ function getDataFilterIcon(isActive, isHighContrast, isHovered) {
36700
37266
  colors.hoverBackgroundColor = "#fff";
36701
37267
  }
36702
37268
  return {
37269
+ name: "DATA_FILTER_ICON",
36703
37270
  width: isActive ? 24 : 850,
36704
37271
  height: isActive ? 24 : 850,
36705
37272
  paths: [
@@ -40895,6 +41462,23 @@ migrationStepRegistry
40895
41462
  }
40896
41463
  return data;
40897
41464
  },
41465
+ })
41466
+ .add("18.4.3", {
41467
+ migrate(data) {
41468
+ if (!data.pivots) {
41469
+ return data;
41470
+ }
41471
+ for (const pivotId in data.pivots) {
41472
+ const pivot = data.pivots[pivotId];
41473
+ if (pivot.sortedColumn) {
41474
+ const measure = pivot.measures.find((measure) => measure.fieldName === pivot.sortedColumn?.measure);
41475
+ if (measure) {
41476
+ pivot.sortedColumn.measure = measure.id;
41477
+ }
41478
+ }
41479
+ }
41480
+ return data;
41481
+ },
40898
41482
  });
40899
41483
  function fixOverlappingFilters(data) {
40900
41484
  for (const sheet of data.sheets || []) {
@@ -45961,15 +46545,16 @@ function useHoveredElement(ref) {
45961
46545
  return state;
45962
46546
  }
45963
46547
 
46548
+ const PAINT_FORMAT_HANDLER_KEYS = [
46549
+ "cell",
46550
+ "border",
46551
+ "table",
46552
+ "conditionalFormat",
46553
+ "merge",
46554
+ ];
45964
46555
  class PaintFormatStore extends SpreadsheetStore {
45965
46556
  mutators = ["activate", "cancel", "pasteFormat"];
45966
46557
  highlightStore = this.get(HighlightStore);
45967
- clipboardHandlers = [
45968
- new CellClipboardHandler(this.getters, this.model.dispatch),
45969
- new BorderClipboardHandler(this.getters, this.model.dispatch),
45970
- new TableClipboardHandler(this.getters, this.model.dispatch),
45971
- new ConditionalFormatClipboardHandler(this.getters, this.model.dispatch),
45972
- ];
45973
46558
  status = "inactive";
45974
46559
  copiedData;
45975
46560
  constructor(get) {
@@ -46000,24 +46585,38 @@ class PaintFormatStore extends SpreadsheetStore {
46000
46585
  get isActive() {
46001
46586
  return this.status !== "inactive";
46002
46587
  }
46588
+ get clipboardHandlers() {
46589
+ return PAINT_FORMAT_HANDLER_KEYS.map((handlerName) => {
46590
+ const HandlerClass = clipboardHandlersRegistries.cellHandlers.get(handlerName);
46591
+ return {
46592
+ handlerName,
46593
+ handler: new HandlerClass(this.getters, this.model.dispatch),
46594
+ };
46595
+ });
46596
+ }
46003
46597
  copyFormats() {
46004
46598
  const sheetId = this.getters.getActiveSheetId();
46005
46599
  const zones = this.getters.getSelectedZones();
46006
- const copiedData = {};
46007
- for (const handler of this.clipboardHandlers) {
46008
- Object.assign(copiedData, handler.copy(getClipboardDataPositions(sheetId, zones), false));
46600
+ const copiedData = { zones, sheetId };
46601
+ for (const { handlerName, handler } of this.clipboardHandlers) {
46602
+ const handlerResult = handler.copy(getClipboardDataPositions(sheetId, zones), false);
46603
+ if (handlerResult !== undefined) {
46604
+ copiedData[handlerName] = handlerResult;
46605
+ }
46009
46606
  }
46010
46607
  return copiedData;
46011
46608
  }
46012
46609
  paintFormat(sheetId, target) {
46013
- if (this.copiedData) {
46014
- for (const handler of this.clipboardHandlers) {
46015
- handler.paste({ zones: target, sheetId }, this.copiedData, {
46016
- isCutOperation: false,
46017
- pasteOption: "onlyFormat",
46018
- });
46019
- }
46610
+ if (!this.copiedData) {
46611
+ return;
46020
46612
  }
46613
+ const options = {
46614
+ isCutOperation: false,
46615
+ pasteOption: "onlyFormat",
46616
+ };
46617
+ const { target: pasteTarget, selectedZones } = getPasteTargetFromHandlers(sheetId, target, this.copiedData, this.clipboardHandlers, options);
46618
+ applyClipboardHandlersPaste(this.clipboardHandlers, this.copiedData, pasteTarget, options);
46619
+ selectPastedZone(this.model.selection, target, selectedZones);
46021
46620
  if (this.status === "oneOff") {
46022
46621
  this.cancel();
46023
46622
  }
@@ -46064,12 +46663,8 @@ class HoveredTableStore extends SpreadsheetStore {
46064
46663
  this.row = undefined;
46065
46664
  }
46066
46665
  computeOverlay() {
46067
- if (!this.getters.isDashboard()) {
46068
- return;
46069
- }
46070
46666
  this.overlayColors = new PositionMap();
46071
- const col = this.col;
46072
- const row = this.row;
46667
+ const { col, row } = this;
46073
46668
  if (col === undefined || row === undefined) {
46074
46669
  return;
46075
46670
  }
@@ -46078,9 +46673,16 @@ class HoveredTableStore extends SpreadsheetStore {
46078
46673
  if (!table) {
46079
46674
  return;
46080
46675
  }
46081
- const { left, right } = table.range.zone;
46082
- for (let c = left; c <= right; c++) {
46083
- this.overlayColors.set({ sheetId, col: c, row }, setColorAlpha("#017E84", 0.08));
46676
+ const { left, right, top } = table.range.zone;
46677
+ const isTableHeader = row < top + table.config.numberOfHeaders;
46678
+ const doesTableRowHaveContent = range(left, right + 1).some((col) => {
46679
+ return (!this.getters.isColHidden(sheetId, col) &&
46680
+ this.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
46681
+ });
46682
+ if (!isTableHeader && doesTableRowHaveContent) {
46683
+ for (let col = left; col <= right; col++) {
46684
+ this.overlayColors.set({ sheetId, col, row }, TABLE_HOVER_BACKGROUND_COLOR);
46685
+ }
46084
46686
  }
46085
46687
  }
46086
46688
  }
@@ -46328,7 +46930,10 @@ class GridOverlay extends owl.Component {
46328
46930
  }
46329
46931
  const icons = this.env.model.getters.getCellIcons(position);
46330
46932
  const icon = icons.find((icon) => {
46331
- return isPointInsideRect(x, y, this.env.model.getters.getCellIconRect(icon));
46933
+ const merge = this.env.model.getters.getMerge(position);
46934
+ const zone = merge || positionToZone(position);
46935
+ const cellRect = this.env.model.getters.getRect(zone);
46936
+ return isPointInsideRect(x, y, this.env.model.getters.getCellIconRect(icon, cellRect));
46332
46937
  });
46333
46938
  return icon?.onClick ? icon : undefined;
46334
46939
  }
@@ -47065,19 +47670,56 @@ class HeadersOverlay extends owl.Component {
47065
47670
  }
47066
47671
  }
47067
47672
 
47068
- class GridRenderer {
47069
- getters;
47070
- renderer;
47673
+ const CELL_ANIMATION_DURATION = 200;
47674
+ class GridRenderer extends SpreadsheetStore {
47071
47675
  fingerprints;
47072
47676
  hoveredTables;
47073
47677
  hoveredIcon;
47678
+ lastRenderBoxes = new Map();
47679
+ preventNewAnimationsInNextFrame = false;
47680
+ zonesWithPreventedAnimationsInNextFrame = [];
47681
+ animations = new Map();
47074
47682
  constructor(get) {
47683
+ super(get);
47075
47684
  this.getters = get(ModelStore).getters;
47076
- this.renderer = get(RendererStore);
47077
47685
  this.fingerprints = get(FormulaFingerprintStore);
47078
47686
  this.hoveredTables = get(HoveredTableStore);
47079
47687
  this.hoveredIcon = get(HoveredIconStore);
47080
- this.renderer.register(this);
47688
+ }
47689
+ handle(cmd) {
47690
+ switch (cmd.type) {
47691
+ case "START":
47692
+ case "ACTIVATE_SHEET":
47693
+ case "ADD_COLUMNS_ROWS":
47694
+ case "REMOVE_COLUMNS_ROWS":
47695
+ this.animations.clear();
47696
+ this.preventNewAnimationsInNextFrame = true;
47697
+ break;
47698
+ case "RESIZE_COLUMNS_ROWS":
47699
+ this.preventNewAnimationsInNextFrame = true;
47700
+ break;
47701
+ case "REDO":
47702
+ this.zonesWithPreventedAnimationsInNextFrame = [];
47703
+ break;
47704
+ case "UNDO":
47705
+ for (const command of cmd.commands) {
47706
+ if (command.type === "ADD_COLUMNS_ROWS" ||
47707
+ command.type === "REMOVE_COLUMNS_ROWS" ||
47708
+ command.type === "RESIZE_COLUMNS_ROWS") {
47709
+ this.animations.clear();
47710
+ this.preventNewAnimationsInNextFrame = true;
47711
+ break;
47712
+ }
47713
+ }
47714
+ break;
47715
+ case "PASTE":
47716
+ this.zonesWithPreventedAnimationsInNextFrame.push(...this.getters.getSelectedZones());
47717
+ break;
47718
+ case "UPDATE_CELL":
47719
+ const zones = this.getters.getCommandZones(cmd);
47720
+ this.zonesWithPreventedAnimationsInNextFrame.push(...zones);
47721
+ break;
47722
+ }
47081
47723
  }
47082
47724
  get renderingLayers() {
47083
47725
  return ["Background", "Headers"];
@@ -47085,17 +47727,20 @@ class GridRenderer {
47085
47727
  // ---------------------------------------------------------------------------
47086
47728
  // Grid rendering
47087
47729
  // ---------------------------------------------------------------------------
47088
- drawLayer(renderingContext, layer) {
47730
+ drawLayer(renderingContext, layer, timeStamp) {
47089
47731
  switch (layer) {
47090
47732
  case "Background":
47091
47733
  this.drawGlobalBackground(renderingContext);
47734
+ const oldBoxes = this.lastRenderBoxes;
47735
+ this.lastRenderBoxes = new Map();
47092
47736
  for (const { zone, rect } of this.getters.getAllActiveViewportsZonesAndRect()) {
47093
47737
  const { ctx } = renderingContext;
47094
47738
  ctx.save();
47095
47739
  ctx.beginPath();
47096
47740
  ctx.rect(rect.x, rect.y, rect.width, rect.height);
47097
47741
  ctx.clip();
47098
- const boxes = this.getGridBoxes(zone);
47742
+ const boxesWithoutAnimations = this.getGridBoxes(zone);
47743
+ const boxes = this.getBoxesWithAnimations(boxesWithoutAnimations, oldBoxes, timeStamp);
47099
47744
  this.drawBackground(renderingContext, boxes);
47100
47745
  this.drawOverflowingCellBackground(renderingContext, boxes);
47101
47746
  this.drawCellBackground(renderingContext, boxes);
@@ -47105,6 +47750,8 @@ class GridRenderer {
47105
47750
  ctx.restore();
47106
47751
  }
47107
47752
  this.drawFrozenPanes(renderingContext);
47753
+ this.preventNewAnimationsInNextFrame = false;
47754
+ this.zonesWithPreventedAnimationsInNextFrame = [];
47108
47755
  break;
47109
47756
  case "Headers":
47110
47757
  if (!this.getters.isDashboard()) {
@@ -47128,6 +47775,8 @@ class GridRenderer {
47128
47775
  const inset = areGridLinesVisible ? 0.1 * thinLineWidth : 0;
47129
47776
  if (areGridLinesVisible) {
47130
47777
  for (const box of boxes) {
47778
+ if (box.skipCellGridLines)
47779
+ continue;
47131
47780
  ctx.strokeStyle = CELL_BORDER_COLOR;
47132
47781
  ctx.lineWidth = thinLineWidth;
47133
47782
  ctx.strokeRect(box.x + inset, box.y + inset, box.width - 2 * inset, box.height - 2 * inset);
@@ -47235,7 +47884,8 @@ class GridRenderer {
47235
47884
  * each line and adding 1 pixel to the end of each line (depending on the direction of the
47236
47885
  * line).
47237
47886
  */
47238
- function drawBorder({ style, color }, x1, y1, x2, y2) {
47887
+ function drawBorder({ color, style, opacity }, x1, y1, x2, y2) {
47888
+ ctx.globalAlpha = opacity ?? 1;
47239
47889
  ctx.strokeStyle = color;
47240
47890
  switch (style) {
47241
47891
  case "medium":
@@ -47283,6 +47933,7 @@ class GridRenderer {
47283
47933
  ctx.stroke();
47284
47934
  ctx.lineWidth = 1;
47285
47935
  ctx.setLineDash([]);
47936
+ ctx.globalAlpha = 1;
47286
47937
  }
47287
47938
  }
47288
47939
  drawTexts(renderingContext, boxes) {
@@ -47291,6 +47942,7 @@ class GridRenderer {
47291
47942
  let currentFont;
47292
47943
  for (const box of boxes) {
47293
47944
  if (box.content) {
47945
+ ctx.globalAlpha = box.textOpacity ?? 1;
47294
47946
  const style = box.style || {};
47295
47947
  const align = box.content.align || "left";
47296
47948
  // compute font and textColor
@@ -47321,6 +47973,7 @@ class GridRenderer {
47321
47973
  if (box.clipRect) {
47322
47974
  ctx.restore();
47323
47975
  }
47976
+ ctx.globalAlpha = 1;
47324
47977
  }
47325
47978
  }
47326
47979
  }
@@ -47337,11 +47990,13 @@ class GridRenderer {
47337
47990
  continue;
47338
47991
  }
47339
47992
  ctx.save();
47993
+ ctx.globalAlpha = icon.opacity ?? 1;
47340
47994
  ctx.beginPath();
47341
- ctx.rect(box.x, box.y, box.width, box.height);
47995
+ const clipRect = icon.clipRect || box;
47996
+ ctx.rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
47342
47997
  ctx.clip();
47343
47998
  const iconSize = icon.size;
47344
- const { x, y } = this.getters.getCellIconRect(icon);
47999
+ const { x, y } = this.getters.getCellIconRect(icon, box);
47345
48000
  ctx.translate(x, y);
47346
48001
  ctx.scale(iconSize / svg.width, iconSize / svg.height);
47347
48002
  for (const path of svg.paths) {
@@ -47550,7 +48205,6 @@ class GridRenderer {
47550
48205
  const cell = this.getters.getEvaluatedCell(position);
47551
48206
  const showFormula = this.getters.shouldShowFormulas();
47552
48207
  const { x, y, width, height } = this.getters.getRect(zone);
47553
- const { verticalAlign } = this.getters.getCellStyle(position);
47554
48208
  const chipStyle = this.getters.getDataValidationChipStyle(position);
47555
48209
  let style = this.getters.getCellComputedStyle(position);
47556
48210
  if (this.fingerprints.isEnabled) {
@@ -47570,6 +48224,7 @@ class GridRenderer {
47570
48224
  center: iconsList.find((icon) => icon?.horizontalAlign === "center"),
47571
48225
  };
47572
48226
  const box = {
48227
+ id: zoneToXc(zone),
47573
48228
  x,
47574
48229
  y,
47575
48230
  width,
@@ -47577,11 +48232,11 @@ class GridRenderer {
47577
48232
  border: this.getters.getCellComputedBorder(position) || undefined,
47578
48233
  style,
47579
48234
  dataBarFill,
47580
- verticalAlign,
47581
48235
  overlayColor: this.hoveredTables.overlayColors.get(position),
47582
48236
  isError: (cell.type === CellValueType.error && !!cell.message) ||
47583
48237
  this.getters.isDataValidationInvalid(position),
47584
48238
  icons: cellIcons,
48239
+ disabledAnimation: this.zonesWithPreventedAnimationsInNextFrame.some((z) => isZoneInside(zone, z) || overlap(zone, z)),
47585
48240
  };
47586
48241
  const fontSizePX = computeTextFontSizeInPixels(box.style);
47587
48242
  if (cell.type === CellValueType.empty || box.icons.center) {
@@ -47751,6 +48406,77 @@ class GridRenderer {
47751
48406
  }
47752
48407
  return boxes;
47753
48408
  }
48409
+ getBoxesWithAnimations(boxes, oldBoxes, timeStamp) {
48410
+ this.updateAnimationsProgress(timeStamp);
48411
+ this.addNewAnimations(boxes, oldBoxes, timeStamp);
48412
+ if (this.animations.size > 0) {
48413
+ this.renderer.startAnimation("grid_renderer_animation");
48414
+ return this.updateBoxesWithAnimations(boxes);
48415
+ }
48416
+ else {
48417
+ this.renderer.stopAnimation("grid_renderer_animation");
48418
+ return boxes;
48419
+ }
48420
+ }
48421
+ updateBoxesWithAnimations(boxes) {
48422
+ const boxesWithAnimations = [];
48423
+ for (const box of boxes) {
48424
+ const animation = this.animations.get(box.id);
48425
+ if (!animation) {
48426
+ boxesWithAnimations.push(box);
48427
+ continue;
48428
+ }
48429
+ const animatedBox = deepCopy(box);
48430
+ boxesWithAnimations.push(animatedBox);
48431
+ for (const animationId of animation.animationTypes) {
48432
+ const animationItem = cellAnimationRegistry.get(animationId);
48433
+ const newBoxes = animationItem.updateAnimation(animation.progress, animatedBox, animation.oldBox, box);
48434
+ if (newBoxes) {
48435
+ boxesWithAnimations.push(...newBoxes.newBoxes);
48436
+ }
48437
+ }
48438
+ }
48439
+ return boxesWithAnimations;
48440
+ }
48441
+ updateAnimationsProgress(timeStamp) {
48442
+ if (timeStamp === undefined) {
48443
+ return;
48444
+ }
48445
+ for (const boxId of this.animations.keys()) {
48446
+ const animation = this.animations.get(boxId);
48447
+ if (animation.startTime === undefined) {
48448
+ animation.startTime = timeStamp;
48449
+ continue;
48450
+ }
48451
+ const elapsedTime = timeStamp - animation.startTime;
48452
+ const progress = Math.min(1, elapsedTime / CELL_ANIMATION_DURATION);
48453
+ if (progress >= 1) {
48454
+ this.animations.delete(boxId);
48455
+ }
48456
+ animation.progress = progress;
48457
+ }
48458
+ }
48459
+ addNewAnimations(boxes, oldBoxes, timeStamp) {
48460
+ for (const box of boxes) {
48461
+ this.lastRenderBoxes.set(box.id, box);
48462
+ const oldBox = oldBoxes.get(box.id);
48463
+ if (this.preventNewAnimationsInNextFrame || !oldBox || box.disabledAnimation) {
48464
+ continue;
48465
+ }
48466
+ const animationTypes = [];
48467
+ for (const animationItem of cellAnimationRegistry.getAll()) {
48468
+ if (animationItem.hasAnimation(oldBox, box)) {
48469
+ animationTypes.push(animationItem.id);
48470
+ }
48471
+ }
48472
+ const animation = animationTypes.length > 0
48473
+ ? { animationTypes, oldBox, progress: 0, startTime: timeStamp }
48474
+ : undefined;
48475
+ if (animation) {
48476
+ this.animations.set(box.id, animation);
48477
+ }
48478
+ }
48479
+ }
47754
48480
  }
47755
48481
 
47756
48482
  function useGridDrawing(refName, model, canvasSize) {
@@ -47785,10 +48511,7 @@ function useGridDrawing(refName, model, canvasSize) {
47785
48511
  // http://diveintohtml5.info/canvas.html#pixel-madness
47786
48512
  ctx.translate(-CANVAS_SHIFT, -CANVAS_SHIFT);
47787
48513
  ctx.scale(dpr, dpr);
47788
- for (const layer of OrderedLayers()) {
47789
- model.drawLayer(renderingContext, layer);
47790
- rendererStore.drawLayer(renderingContext, layer);
47791
- }
48514
+ rendererStore.draw(renderingContext);
47792
48515
  }
47793
48516
  }
47794
48517
 
@@ -51656,6 +52379,9 @@ class ConditionalFormattingPanel extends owl.Component {
51656
52379
  this.switchToList();
51657
52380
  }
51658
52381
  }
52382
+ else if (!this.editedCF) {
52383
+ this.switchToList();
52384
+ }
51659
52385
  });
51660
52386
  }
51661
52387
  get conditionalFormats() {
@@ -54883,7 +55609,7 @@ class SpreadsheetPivot {
54883
55609
  }
54884
55610
  getTypeFromZone(sheetId, zone) {
54885
55611
  const cells = this.getters.getEvaluatedCellsInZone(sheetId, zone);
54886
- const nonEmptyCells = cells.filter((cell) => cell.type !== CellValueType.empty);
55612
+ const nonEmptyCells = cells.filter((cell) => !(cell.type === CellValueType.empty || cell.value === ""));
54887
55613
  if (nonEmptyCells.length === 0) {
54888
55614
  return "integer";
54889
55615
  }
@@ -55491,7 +56217,7 @@ css /* scss */ `
55491
56217
  `;
55492
56218
  class SettingsPanel extends owl.Component {
55493
56219
  static template = "o-spreadsheet-SettingsPanel";
55494
- static components = { Section, ValidationMessages };
56220
+ static components = { Section, ValidationMessages, BadgeSelection };
55495
56221
  static props = { onCloseSidePanel: Function };
55496
56222
  loadedLocales = [];
55497
56223
  setup() {
@@ -56375,49 +57101,111 @@ class ScreenWidthStore {
56375
57101
  }
56376
57102
 
56377
57103
  const DEFAULT_SIDE_PANEL_SIZE = 350;
57104
+ const COLLAPSED_SIDE_PANEL_SIZE = 45;
56378
57105
  const MIN_SHEET_VIEW_WIDTH = 150;
56379
57106
  class SidePanelStore extends SpreadsheetStore {
56380
- mutators = ["open", "toggle", "close", "changePanelSize", "resetPanelSize"];
56381
- initialPanelProps = {};
56382
- componentTag = "";
56383
- panelSize = DEFAULT_SIDE_PANEL_SIZE;
57107
+ mutators = [
57108
+ "open",
57109
+ "toggle",
57110
+ "close",
57111
+ "changePanelSize",
57112
+ "resetPanelSize",
57113
+ "togglePinPanel",
57114
+ "closeMainPanel",
57115
+ "changeSpreadsheetWidth",
57116
+ "toggleCollapsePanel",
57117
+ ];
57118
+ mainPanel = undefined;
57119
+ secondaryPanel;
57120
+ availableWidth = 0;
56384
57121
  screenWidthStore = this.get(ScreenWidthStore);
56385
- get isOpen() {
56386
- if (!this.componentTag) {
56387
- return false;
56388
- }
56389
- return this.computeState(this.componentTag, this.initialPanelProps).isOpen;
57122
+ get isMainPanelOpen() {
57123
+ return this.mainPanel && this.mainPanel.componentTag
57124
+ ? this.computeState(this.mainPanel).isOpen
57125
+ : false;
57126
+ }
57127
+ get isSecondaryPanelOpen() {
57128
+ return this.secondaryPanel && this.secondaryPanel.componentTag
57129
+ ? this.computeState(this.secondaryPanel).isOpen
57130
+ : false;
56390
57131
  }
56391
- get panelProps() {
56392
- const state = this.computeState(this.componentTag, this.initialPanelProps);
57132
+ get mainPanelProps() {
57133
+ return this.mainPanel ? this.getPanelProps(this.mainPanel) : undefined;
57134
+ }
57135
+ get mainPanelKey() {
57136
+ return this.mainPanel ? this.getPanelKey(this.mainPanel) : undefined;
57137
+ }
57138
+ get secondaryPanelProps() {
57139
+ return this.secondaryPanel ? this.getPanelProps(this.secondaryPanel) : undefined;
57140
+ }
57141
+ get secondaryPanelKey() {
57142
+ return this.secondaryPanel ? this.getPanelKey(this.secondaryPanel) : undefined;
57143
+ }
57144
+ get totalPanelSize() {
57145
+ return (this.mainPanel?.size || 0) + (this.secondaryPanel?.size ?? 0);
57146
+ }
57147
+ getPanelProps(panelInfo) {
57148
+ const state = this.computeState(panelInfo);
56393
57149
  if (state.isOpen) {
56394
57150
  return state.props ?? {};
56395
57151
  }
56396
57152
  return {};
56397
57153
  }
56398
- get panelKey() {
56399
- const state = this.computeState(this.componentTag, this.initialPanelProps);
57154
+ getPanelKey(panelInfo) {
57155
+ const state = this.computeState(panelInfo);
56400
57156
  if (state.isOpen) {
56401
57157
  return state.key;
56402
57158
  }
56403
57159
  return undefined;
56404
57160
  }
56405
- open(componentTag, panelProps = {}) {
57161
+ open(componentTag, initialPanelProps = {}) {
56406
57162
  if (this.screenWidthStore.isSmall) {
56407
57163
  return;
56408
57164
  }
56409
- const state = this.computeState(componentTag, panelProps);
57165
+ const newPanelInfo = { initialPanelProps, componentTag, size: DEFAULT_SIDE_PANEL_SIZE };
57166
+ const state = this.computeState(newPanelInfo);
56410
57167
  if (!state.isOpen) {
56411
57168
  return;
56412
57169
  }
56413
- if (this.isOpen && componentTag !== this.componentTag) {
56414
- this.initialPanelProps?.onCloseSidePanel?.();
57170
+ const mainPanelKey = this.mainPanel ? this.getPanelKey(this.mainPanel) : undefined;
57171
+ if (!this.mainPanel || !this.mainPanel.isPinned || mainPanelKey === state.key) {
57172
+ this._openPanel("mainPanel", newPanelInfo, state);
57173
+ return;
57174
+ }
57175
+ // Try to open secondary panel if main panel is pinned
57176
+ const nonCollapsedPanelSize = this.mainPanel.isCollapsed
57177
+ ? DEFAULT_SIDE_PANEL_SIZE
57178
+ : this.mainPanel.size;
57179
+ if (!this.secondaryPanel &&
57180
+ nonCollapsedPanelSize + DEFAULT_SIDE_PANEL_SIZE > this.availableWidth) {
57181
+ this.get(NotificationStore).notifyUser({
57182
+ sticky: false,
57183
+ type: "warning",
57184
+ text: _t("The window is too small to display multiple side panels."),
57185
+ });
57186
+ return;
57187
+ }
57188
+ this._openPanel("secondaryPanel", newPanelInfo, state);
57189
+ }
57190
+ _openPanel(panel, newPanel, state) {
57191
+ const currentPanel = this[panel];
57192
+ if (currentPanel && newPanel.componentTag !== currentPanel.componentTag) {
57193
+ currentPanel.initialPanelProps?.onCloseSidePanel?.();
57194
+ }
57195
+ this[panel] = {
57196
+ initialPanelProps: state.props ?? {},
57197
+ componentTag: newPanel.componentTag,
57198
+ size: currentPanel?.size || DEFAULT_SIDE_PANEL_SIZE,
57199
+ isCollapsed: currentPanel?.isCollapsed || false,
57200
+ isPinned: currentPanel && "isPinned" in currentPanel ? currentPanel.isPinned : false,
57201
+ };
57202
+ if (this[panel].isCollapsed) {
57203
+ this.toggleCollapsePanel(panel);
56415
57204
  }
56416
- this.componentTag = componentTag;
56417
- this.initialPanelProps = state.props ?? {};
56418
57205
  }
56419
57206
  toggle(componentTag, panelProps) {
56420
- if (this.isOpen && componentTag === this.componentTag) {
57207
+ const panel = this.mainPanel?.isPinned ? this.secondaryPanel : this.mainPanel;
57208
+ if (panel && componentTag === panel.componentTag) {
56421
57209
  this.close();
56422
57210
  }
56423
57211
  else {
@@ -56425,34 +57213,85 @@ class SidePanelStore extends SpreadsheetStore {
56425
57213
  }
56426
57214
  }
56427
57215
  close() {
56428
- this.initialPanelProps.onCloseSidePanel?.();
56429
- this.initialPanelProps = {};
56430
- this.componentTag = "";
57216
+ if (this.mainPanel?.isPinned) {
57217
+ if (this.secondaryPanel) {
57218
+ this.secondaryPanel.initialPanelProps.onCloseSidePanel?.();
57219
+ this.secondaryPanel = undefined;
57220
+ }
57221
+ return;
57222
+ }
57223
+ this.mainPanel?.initialPanelProps.onCloseSidePanel?.();
57224
+ this.mainPanel = undefined;
56431
57225
  }
56432
- changePanelSize(size, spreadsheetElWidth) {
56433
- if (size < DEFAULT_SIDE_PANEL_SIZE) {
56434
- this.panelSize = DEFAULT_SIDE_PANEL_SIZE;
57226
+ closeMainPanel() {
57227
+ this.mainPanel?.initialPanelProps.onCloseSidePanel?.();
57228
+ this.mainPanel = this.secondaryPanel || undefined;
57229
+ this.secondaryPanel = undefined;
57230
+ }
57231
+ changePanelSize(panel, size) {
57232
+ const panelInfo = this[panel];
57233
+ if (!panelInfo || ("isCollapsed" in panelInfo && panelInfo.isCollapsed)) {
57234
+ return;
56435
57235
  }
56436
- else if (size > spreadsheetElWidth - MIN_SHEET_VIEW_WIDTH) {
56437
- this.panelSize = Math.max(spreadsheetElWidth - MIN_SHEET_VIEW_WIDTH, DEFAULT_SIDE_PANEL_SIZE);
57236
+ size = Math.max(size, DEFAULT_SIDE_PANEL_SIZE);
57237
+ let otherPanelSize = panel === "mainPanel" ? this.secondaryPanel?.size || 0 : this.mainPanel?.size || 0;
57238
+ if (size > this.availableWidth - otherPanelSize) {
57239
+ if (panel === "mainPanel" && this.secondaryPanel) {
57240
+ // reduce the secondary panel size to fit the main panel
57241
+ this.secondaryPanel.size = Math.max(this.availableWidth - size, DEFAULT_SIDE_PANEL_SIZE);
57242
+ otherPanelSize = this.secondaryPanel.size;
57243
+ }
57244
+ size = Math.max(this.availableWidth - otherPanelSize, DEFAULT_SIDE_PANEL_SIZE);
56438
57245
  }
56439
- else {
56440
- this.panelSize = size;
57246
+ panelInfo.size = size;
57247
+ }
57248
+ resetPanelSize(panel) {
57249
+ const panelInfo = this[panel];
57250
+ if (!panelInfo) {
57251
+ return;
56441
57252
  }
57253
+ panelInfo.size = DEFAULT_SIDE_PANEL_SIZE;
56442
57254
  }
56443
- resetPanelSize() {
56444
- this.panelSize = DEFAULT_SIDE_PANEL_SIZE;
57255
+ togglePinPanel() {
57256
+ if (!this.mainPanel) {
57257
+ return;
57258
+ }
57259
+ this.mainPanel.isPinned = !this.mainPanel.isPinned;
57260
+ if (!this.mainPanel.isPinned && this.secondaryPanel) {
57261
+ this.secondaryPanel?.initialPanelProps.onCloseSidePanel?.();
57262
+ this.mainPanel = this.secondaryPanel;
57263
+ this.secondaryPanel = undefined;
57264
+ }
56445
57265
  }
56446
- computeState(componentTag, panelProps) {
56447
- const customComputeState = sidePanelRegistry.get(componentTag).computeState;
56448
- if (!customComputeState) {
56449
- return {
56450
- isOpen: true,
56451
- props: panelProps,
56452
- };
57266
+ toggleCollapsePanel(panel) {
57267
+ const panelInfo = this[panel];
57268
+ if (!panelInfo) {
57269
+ return;
57270
+ }
57271
+ if (panelInfo.isCollapsed) {
57272
+ panelInfo.isCollapsed = false;
57273
+ this.changePanelSize(panel, DEFAULT_SIDE_PANEL_SIZE);
56453
57274
  }
56454
57275
  else {
56455
- return customComputeState(this.getters, panelProps);
57276
+ panelInfo.isCollapsed = true;
57277
+ panelInfo.size = COLLAPSED_SIDE_PANEL_SIZE;
57278
+ }
57279
+ }
57280
+ computeState({ componentTag, initialPanelProps }) {
57281
+ const customComputeState = sidePanelRegistry.get(componentTag).computeState;
57282
+ const state = customComputeState
57283
+ ? customComputeState(this.getters, initialPanelProps)
57284
+ : { isOpen: true, props: initialPanelProps };
57285
+ return state.isOpen ? { ...state, key: state.key || componentTag } : state;
57286
+ }
57287
+ changeSpreadsheetWidth(width) {
57288
+ this.availableWidth = width - MIN_SHEET_VIEW_WIDTH;
57289
+ if (this.secondaryPanel && width - this.totalPanelSize < MIN_SHEET_VIEW_WIDTH) {
57290
+ this.secondaryPanel?.initialPanelProps.onCloseSidePanel?.();
57291
+ this.secondaryPanel = undefined;
57292
+ }
57293
+ if (this.mainPanel && width - this.totalPanelSize < MIN_SHEET_VIEW_WIDTH) {
57294
+ this.mainPanel.size = Math.max(width - MIN_SHEET_VIEW_WIDTH, DEFAULT_SIDE_PANEL_SIZE);
56456
57295
  }
56457
57296
  }
56458
57297
  }
@@ -56600,11 +57439,11 @@ class Grid extends owl.Component {
56600
57439
  this.hoveredCell.clear();
56601
57440
  });
56602
57441
  this.cellPopovers = useStore(CellPopoverStore);
56603
- owl.useEffect(() => {
56604
- if (!this.sidePanel.isOpen) {
57442
+ owl.useEffect((isMainPanelOpen, isSecondaryPanelOpen) => {
57443
+ if (!isMainPanelOpen && !isSecondaryPanelOpen) {
56605
57444
  this.DOMFocusableElementStore.focus();
56606
57445
  }
56607
- }, () => [this.sidePanel.isOpen]);
57446
+ }, () => [this.sidePanel.isMainPanelOpen, this.sidePanel.isSecondaryPanelOpen]);
56608
57447
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
56609
57448
  const { scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
56610
57449
  return scrollY > 0;
@@ -59696,7 +60535,7 @@ class DataValidationPlugin extends CorePlugin {
59696
60535
  else if (newRule.criterion.type === "isValueInList") {
59697
60536
  newRule.criterion.values = Array.from(new Set(newRule.criterion.values));
59698
60537
  }
59699
- const adaptedRules = this.removeRangesFromRules(sheetId, newRule.ranges, rules);
60538
+ const adaptedRules = this.removeRangesFromRules(sheetId, newRule.ranges, rules, newRule.id);
59700
60539
  const ruleIndex = adaptedRules.findIndex((rule) => rule.id === newRule.id);
59701
60540
  if (ruleIndex !== -1) {
59702
60541
  adaptedRules[ruleIndex] = newRule;
@@ -59706,9 +60545,12 @@ class DataValidationPlugin extends CorePlugin {
59706
60545
  this.history.update("rules", sheetId, [...adaptedRules, newRule]);
59707
60546
  }
59708
60547
  }
59709
- removeRangesFromRules(sheetId, ranges, rules) {
60548
+ removeRangesFromRules(sheetId, ranges, rules, editingRuleId) {
59710
60549
  rules = deepCopy(rules);
59711
60550
  for (const rule of rules) {
60551
+ if (rule.id === editingRuleId) {
60552
+ continue; // Skip the rule being edited to preserve its place in the list
60553
+ }
59712
60554
  rule.ranges = this.getters.recomputeRanges(rule.ranges, ranges);
59713
60555
  }
59714
60556
  return rules.filter((rule) => rule.ranges.length > 0);
@@ -63116,7 +63958,7 @@ class PivotCorePlugin extends CorePlugin {
63116
63958
  break;
63117
63959
  }
63118
63960
  case "UPDATE_PIVOT": {
63119
- this.history.update("pivots", cmd.pivotId, "definition", this.repairSortedColumn(deepCopy(cmd.pivot)));
63961
+ this.history.update("pivots", cmd.pivotId, "definition", deepCopy(cmd.pivot));
63120
63962
  this.compileCalculatedMeasures(cmd.pivot.measures);
63121
63963
  break;
63122
63964
  }
@@ -63187,10 +64029,7 @@ class PivotCorePlugin extends CorePlugin {
63187
64029
  // Private
63188
64030
  // -------------------------------------------------------------------------
63189
64031
  addPivot(pivotId, pivot, formulaId = this.nextFormulaId.toString()) {
63190
- this.history.update("pivots", pivotId, {
63191
- definition: this.repairSortedColumn(deepCopy(pivot)),
63192
- formulaId,
63193
- });
64032
+ this.history.update("pivots", pivotId, { definition: deepCopy(pivot), formulaId });
63194
64033
  this.compileCalculatedMeasures(pivot.measures);
63195
64034
  this.history.update("formulaIds", formulaId, pivotId);
63196
64035
  this.history.update("nextFormulaId", this.nextFormulaId + 1);
@@ -63279,7 +64118,6 @@ class PivotCorePlugin extends CorePlugin {
63279
64118
  }
63280
64119
  }
63281
64120
  checkSortedColumnInMeasures(definition) {
63282
- definition = this.repairSortedColumn(definition);
63283
64121
  const measures = definition.measures.map((measure) => measure.id);
63284
64122
  if (definition.sortedColumn && !measures.includes(definition.sortedColumn.measure)) {
63285
64123
  return "InvalidDefinition" /* CommandResult.InvalidDefinition */;
@@ -63293,26 +64131,6 @@ class PivotCorePlugin extends CorePlugin {
63293
64131
  }
63294
64132
  return "Success" /* CommandResult.Success */;
63295
64133
  }
63296
- repairSortedColumn(definition) {
63297
- if (definition.sortedColumn) {
63298
- // Fix for an upgrade issue: the sortedColumn measure was not updated
63299
- // from using fieldName to using id. If the sortedColumn measure matches
63300
- // a measure fieldName in the definition, update it to use the measure's id instead
63301
- // of its fieldName.
63302
- // TODO: add an upgrade step to fix this in master and remove this code
63303
- const sortedMeasure = definition.measures.find((measure) => measure.fieldName === definition.sortedColumn?.measure);
63304
- if (sortedMeasure) {
63305
- return {
63306
- ...definition,
63307
- sortedColumn: {
63308
- ...definition.sortedColumn,
63309
- measure: sortedMeasure.id,
63310
- },
63311
- };
63312
- }
63313
- }
63314
- return definition;
63315
- }
63316
64134
  // ---------------------------------------------------------------------
63317
64135
  // Import/Export
63318
64136
  // ---------------------------------------------------------------------
@@ -63394,9 +64212,7 @@ class SettingsPlugin extends CorePlugin {
63394
64212
  this.locale = data.settings?.locale ?? DEFAULT_LOCALE;
63395
64213
  }
63396
64214
  export(data) {
63397
- data.settings = {
63398
- locale: this.locale,
63399
- };
64215
+ data.settings = { locale: this.locale };
63400
64216
  }
63401
64217
  }
63402
64218
 
@@ -66431,11 +67247,8 @@ class CellIconPlugin extends CoreViewPlugin {
66431
67247
  }
66432
67248
  return this.cellIconsCache[position.sheetId][position.col][position.row];
66433
67249
  }
66434
- getCellIconRect(icon) {
67250
+ getCellIconRect(icon, cellRect) {
66435
67251
  const cellPosition = icon.position;
66436
- const merge = this.getters.getMerge(cellPosition);
66437
- const zone = merge || positionToZone(cellPosition);
66438
- const cellRect = this.getters.getRect(zone);
66439
67252
  const cell = this.getters.getCell(cellPosition);
66440
67253
  const x = this.getIconHorizontalPosition(cellRect, icon.horizontalAlign, icon);
66441
67254
  const y = this.getters.computeTextYCoordinate(cellRect, icon.size, cell?.style?.verticalAlign);
@@ -72268,49 +73081,17 @@ class ClipboardPlugin extends UIPlugin {
72268
73081
  if (!copiedData) {
72269
73082
  return;
72270
73083
  }
72271
- let zone = undefined;
72272
- const selectedZones = [];
72273
73084
  const sheetId = this.getters.getActiveSheetId();
72274
- const target = {
72275
- sheetId,
72276
- zones,
72277
- };
72278
73085
  const handlers = this.selectClipboardHandlers(copiedData);
72279
- for (const { handlerName, handler } of handlers) {
72280
- const handlerData = copiedData[handlerName];
72281
- if (!handlerData) {
72282
- continue;
72283
- }
72284
- const currentTarget = handler.getPasteTarget(sheetId, zones, handlerData, options);
72285
- if (currentTarget.figureId) {
72286
- target.figureId = currentTarget.figureId;
72287
- }
72288
- for (const targetZone of currentTarget.zones) {
72289
- selectedZones.push(targetZone);
72290
- if (zone === undefined) {
72291
- zone = targetZone;
72292
- continue;
72293
- }
72294
- zone = union(zone, targetZone);
72295
- }
72296
- }
73086
+ const { target, zone, selectedZones } = getPasteTargetFromHandlers(sheetId, zones, copiedData, handlers, options);
72297
73087
  if (zone !== undefined) {
72298
- this.addMissingDimensions(this.getters.getActiveSheetId(), zone.right - zone.left + 1, zone.bottom - zone.top + 1, zone.left, zone.top);
73088
+ this.addMissingDimensions(sheetId, zone.right - zone.left + 1, zone.bottom - zone.top + 1, zone.left, zone.top);
72299
73089
  }
72300
- handlers.forEach(({ handlerName, handler }) => {
72301
- const handlerData = copiedData[handlerName];
72302
- if (handlerData) {
72303
- handler.paste(target, handlerData, options);
72304
- }
72305
- });
73090
+ applyClipboardHandlersPaste(handlers, copiedData, target, options);
72306
73091
  if (!options?.selectTarget) {
72307
73092
  return;
72308
73093
  }
72309
- const selection = zones[0];
72310
- const col = selection.left;
72311
- const row = selection.top;
72312
- this.selection.getBackToDefault();
72313
- this.selection.selectZone({ cell: { col, row }, zone: union(...selectedZones) }, { scrollIntoView: false });
73094
+ selectPastedZone(this.selection, zones, selectedZones);
72314
73095
  }
72315
73096
  /**
72316
73097
  * Add columns and/or rows to ensure that col + width and row + height are still
@@ -75139,6 +75920,17 @@ clickableCellRegistry.add("link", {
75139
75920
  return !!getters.getEvaluatedCell(position).link;
75140
75921
  },
75141
75922
  execute: (position, env, isMiddleClick) => openLink(env.model.getters.getEvaluatedCell(position).link, env, isMiddleClick),
75923
+ title: (position, getters) => {
75924
+ const link = getters.getEvaluatedCell(position).link;
75925
+ if (!link)
75926
+ return "";
75927
+ if (link.isExternal) {
75928
+ return _t("Go to url: %(url)s", { url: link.url });
75929
+ }
75930
+ else {
75931
+ return _t("Go to %(label)s", { label: link.label });
75932
+ }
75933
+ },
75142
75934
  sequence: 5,
75143
75935
  });
75144
75936
 
@@ -76850,12 +77642,13 @@ class ClickableCellsStore extends SpreadsheetStore {
76850
77642
  if (!item) {
76851
77643
  continue;
76852
77644
  }
77645
+ const title = typeof item.title === "function" ? item.title(position, getters) : item.title;
76853
77646
  const zone = getters.expandZone(sheetId, positionToZone(position));
76854
77647
  cells.push({
76855
77648
  coordinates: getters.getVisibleRect(zone),
76856
77649
  position,
76857
77650
  action: item.execute,
76858
- title: item.title || "",
77651
+ title: title || "",
76859
77652
  });
76860
77653
  }
76861
77654
  return cells;
@@ -77240,24 +78033,36 @@ css /* scss */ `
77240
78033
  user-select: none;
77241
78034
  color: ${TEXT_BODY};
77242
78035
 
78036
+ &.collapsed {
78037
+ padding: 8px;
78038
+ cursor: pointer;
78039
+
78040
+ .o-sidePanelTitle {
78041
+ writing-mode: vertical-rl;
78042
+ text-orientation: mixed;
78043
+ }
78044
+ }
78045
+
77243
78046
  .o-sidePanelTitle {
77244
78047
  line-height: 20px;
77245
78048
  font-size: 16px;
77246
78049
  }
77247
78050
 
77248
78051
  .o-sidePanelHeader {
77249
- padding: 8px 16px;
77250
- display: flex;
77251
- align-items: center;
77252
- justify-content: space-between;
78052
+ padding: 8px;
77253
78053
  border-bottom: 1px solid ${GRAY_300};
78054
+ }
77254
78055
 
77255
- .o-sidePanelClose {
77256
- padding: 5px 10px;
77257
- cursor: pointer;
77258
- &:hover {
77259
- background-color: WhiteSmoke;
77260
- }
78056
+ .o-sidePanelAction {
78057
+ padding: 5px 10px;
78058
+ cursor: pointer;
78059
+
78060
+ &.active {
78061
+ background-color: ${BUTTON_ACTIVE_BG};
78062
+ }
78063
+
78064
+ &:hover {
78065
+ background-color: ${BUTTON_HOVER_BG};
77261
78066
  }
77262
78067
  }
77263
78068
  .o-sidePanelBody-container {
@@ -77334,43 +78139,114 @@ css /* scss */ `
77334
78139
  `;
77335
78140
  class SidePanel extends owl.Component {
77336
78141
  static template = "o-spreadsheet-SidePanel";
78142
+ static props = {
78143
+ panelContent: Object,
78144
+ panelProps: Object,
78145
+ onCloseSidePanel: Function,
78146
+ onStartHandleDrag: Function,
78147
+ onResetPanelSize: Function,
78148
+ isPinned: { type: Boolean, optional: true },
78149
+ onTogglePinPanel: { type: Function, optional: true },
78150
+ onToggleCollapsePanel: { type: Function, optional: true },
78151
+ isCollapsed: { type: Boolean, optional: true },
78152
+ };
78153
+ spreadsheetRect = useSpreadsheetRect();
78154
+ getTitle() {
78155
+ const panel = this.props.panelContent;
78156
+ return typeof panel.title === "function"
78157
+ ? panel.title(this.env, this.props.panelProps)
78158
+ : panel.title;
78159
+ }
78160
+ get pinInfoMessage() {
78161
+ return _t("Pin this panel to allow to open another side panel beside it.");
78162
+ }
78163
+ }
78164
+
78165
+ class SidePanels extends owl.Component {
78166
+ static template = "o-spreadsheet-SidePanels";
77337
78167
  static props = {};
78168
+ static components = { SidePanel };
77338
78169
  sidePanelStore;
77339
78170
  spreadsheetRect = useSpreadsheetRect();
77340
78171
  setup() {
77341
78172
  this.sidePanelStore = useStore(SidePanelStore);
77342
- owl.useEffect((isOpen) => {
77343
- if (!isOpen) {
78173
+ owl.useEffect(() => {
78174
+ if (this.sidePanelStore.mainPanel && !this.sidePanelStore.isMainPanelOpen) {
78175
+ this.sidePanelStore.closeMainPanel();
78176
+ }
78177
+ if (this.sidePanelStore.secondaryPanel && !this.sidePanelStore.isSecondaryPanelOpen) {
77344
78178
  this.sidePanelStore.close();
77345
78179
  }
77346
- }, () => [this.sidePanelStore.isOpen]);
77347
- }
77348
- get panel() {
77349
- return sidePanelRegistry.get(this.sidePanelStore.componentTag);
77350
- }
77351
- close() {
77352
- this.sidePanelStore.close();
78180
+ }, () => [this.sidePanelStore.isMainPanelOpen, this.sidePanelStore.isSecondaryPanelOpen]);
77353
78181
  }
77354
- getTitle() {
77355
- const panel = this.panel;
77356
- return typeof panel.title === "function"
77357
- ? panel.title(this.env, this.sidePanelStore.panelProps)
77358
- : panel.title;
77359
- }
77360
- startHandleDrag(ev) {
78182
+ startHandleDrag(panel, ev) {
77361
78183
  const startingCursor = document.body.style.cursor;
77362
- const startSize = this.sidePanelStore.panelSize;
78184
+ const panelInfo = panel === "mainPanel" ? this.sidePanelStore.mainPanel : this.sidePanelStore.secondaryPanel;
78185
+ if (!panelInfo) {
78186
+ return;
78187
+ }
78188
+ const startSize = panelInfo.size;
77363
78189
  const startPosition = ev.clientX;
77364
78190
  const onMouseMove = (ev) => {
77365
78191
  document.body.style.cursor = "col-resize";
77366
78192
  const newSize = startSize + startPosition - ev.clientX;
77367
- this.sidePanelStore.changePanelSize(newSize, this.spreadsheetRect.width);
78193
+ this.sidePanelStore.changePanelSize(panel, newSize);
77368
78194
  };
77369
78195
  const cleanUp = () => {
77370
78196
  document.body.style.cursor = startingCursor;
77371
78197
  };
77372
78198
  startDnd(onMouseMove, cleanUp);
77373
78199
  }
78200
+ get mainPanelProps() {
78201
+ const panelProps = this.sidePanelStore.mainPanelProps;
78202
+ if (!this.sidePanelStore.mainPanel || !panelProps) {
78203
+ return undefined;
78204
+ }
78205
+ return {
78206
+ panelContent: sidePanelRegistry.get(this.sidePanelStore.mainPanel.componentTag),
78207
+ panelProps,
78208
+ onCloseSidePanel: () => this.sidePanelStore.closeMainPanel(),
78209
+ onTogglePinPanel: () => this.sidePanelStore.togglePinPanel(),
78210
+ onStartHandleDrag: (ev) => this.startHandleDrag("mainPanel", ev),
78211
+ onResetPanelSize: () => this.sidePanelStore.resetPanelSize("mainPanel"),
78212
+ isPinned: this.sidePanelStore.mainPanel?.isPinned,
78213
+ onToggleCollapsePanel: () => this.sidePanelStore.toggleCollapsePanel("mainPanel"),
78214
+ isCollapsed: this.sidePanelStore.mainPanel?.isCollapsed,
78215
+ };
78216
+ }
78217
+ get secondaryPanelProps() {
78218
+ const panelProps = this.sidePanelStore.secondaryPanelProps;
78219
+ if (!this.sidePanelStore.secondaryPanel || !panelProps) {
78220
+ return undefined;
78221
+ }
78222
+ return {
78223
+ panelContent: sidePanelRegistry.get(this.sidePanelStore.secondaryPanel.componentTag),
78224
+ panelProps,
78225
+ onCloseSidePanel: () => this.sidePanelStore.close(),
78226
+ onStartHandleDrag: (ev) => this.startHandleDrag("secondaryPanel", ev),
78227
+ onResetPanelSize: () => this.sidePanelStore.resetPanelSize("secondaryPanel"),
78228
+ onToggleCollapsePanel: () => this.sidePanelStore.toggleCollapsePanel("secondaryPanel"),
78229
+ isCollapsed: this.sidePanelStore.secondaryPanel?.isCollapsed,
78230
+ };
78231
+ }
78232
+ get panelList() {
78233
+ return [
78234
+ {
78235
+ key: this.sidePanelStore.secondaryPanelKey,
78236
+ props: this.secondaryPanelProps,
78237
+ style: this.sidePanelStore.secondaryPanel
78238
+ ? cssPropertiesToCss({ width: `${this.sidePanelStore.secondaryPanel.size}px` })
78239
+ : "",
78240
+ },
78241
+ {
78242
+ key: this.sidePanelStore.mainPanelKey,
78243
+ props: this.mainPanelProps,
78244
+ style: this.sidePanelStore.mainPanel
78245
+ ? cssPropertiesToCss({ width: `${this.sidePanelStore.mainPanel.size}px` })
78246
+ : "",
78247
+ },
78248
+ ].filter((panel) => panel.key && panel.props);
78249
+ }
77374
78250
  }
77375
78251
 
77376
78252
  class RibbonMenu extends owl.Component {
@@ -78932,7 +79808,7 @@ class Spreadsheet extends owl.Component {
78932
79808
  Grid,
78933
79809
  BottomBar,
78934
79810
  SmallBottomBar,
78935
- SidePanel,
79811
+ SidePanels,
78936
79812
  SpreadsheetDashboard,
78937
79813
  HeaderGroupContainer,
78938
79814
  FullScreenChart,
@@ -78955,7 +79831,9 @@ class Spreadsheet extends owl.Component {
78955
79831
  else {
78956
79832
  properties["grid-template-rows"] = `min-content auto min-content`;
78957
79833
  }
78958
- const columnWidth = this.sidePanel.isOpen ? `${this.sidePanel.panelSize}px` : "auto";
79834
+ const columnWidth = this.sidePanel.mainPanel
79835
+ ? `${this.sidePanel.totalPanelSize || DEFAULT_SIDE_PANEL_SIZE}px`
79836
+ : "auto";
78959
79837
  properties["grid-template-columns"] = `auto ${columnWidth}`;
78960
79838
  return cssPropertiesToCss(properties);
78961
79839
  }
@@ -79039,7 +79917,7 @@ class Spreadsheet extends owl.Component {
79039
79917
  this.checkViewportSize();
79040
79918
  });
79041
79919
  const resizeObserver = new ResizeObserver(() => {
79042
- this.sidePanel.changePanelSize(this.sidePanel.panelSize, this.spreadsheetRect.width);
79920
+ this.sidePanel.changeSpreadsheetWidth(this.spreadsheetRect.width);
79043
79921
  });
79044
79922
  }
79045
79923
  bindModelEvents() {
@@ -80154,26 +81032,28 @@ class SelectionStreamProcessorImpl {
80154
81032
  bottom: Math.min(this.getters.getNumberRows(sheetId) - 1, bottom),
80155
81033
  };
80156
81034
  };
80157
- const { col: refCol, row: refRow } = this.getReferencePosition();
81035
+ const { cell: refCell, zone: refZone } = this.getReferenceAnchor();
81036
+ const { col: refCol, row: refRow } = refCell;
80158
81037
  // check if we can shrink selection
80159
81038
  let n = 0;
80160
81039
  while (result !== null) {
80161
81040
  n++;
80162
81041
  if (deltaCol < 0) {
80163
81042
  const newRight = this.getNextAvailableCol(deltaCol, right - (n - 1), refRow);
80164
- result = refCol <= right - n ? expand({ top, left, bottom, right: newRight }) : null;
81043
+ result = refZone.right <= right - n ? expand({ top, left, bottom, right: newRight }) : null;
80165
81044
  }
80166
81045
  if (deltaCol > 0) {
80167
81046
  const newLeft = this.getNextAvailableCol(deltaCol, left + (n - 1), refRow);
80168
- result = left + n <= refCol ? expand({ top, left: newLeft, bottom, right }) : null;
81047
+ result = left + n <= refZone.left ? expand({ top, left: newLeft, bottom, right }) : null;
80169
81048
  }
80170
81049
  if (deltaRow < 0) {
80171
81050
  const newBottom = this.getNextAvailableRow(deltaRow, refCol, bottom - (n - 1));
80172
- result = refRow <= bottom - n ? expand({ top, left, bottom: newBottom, right }) : null;
81051
+ result =
81052
+ refZone.bottom <= bottom - n ? expand({ top, left, bottom: newBottom, right }) : null;
80173
81053
  }
80174
81054
  if (deltaRow > 0) {
80175
81055
  const newTop = this.getNextAvailableRow(deltaRow, refCol, top + (n - 1));
80176
- result = top + n <= refRow ? expand({ top: newTop, left, bottom, right }) : null;
81056
+ result = top + n <= refZone.top ? expand({ top: newTop, left, bottom, right }) : null;
80177
81057
  }
80178
81058
  result = result ? reorderZone(result) : result;
80179
81059
  if (result && !isEqual(result, anchor.zone)) {
@@ -80403,18 +81283,26 @@ class SelectionStreamProcessorImpl {
80403
81283
  * If the anchor is hidden, browses from left to right and top to bottom to
80404
81284
  * find a visible cell.
80405
81285
  */
80406
- getReferencePosition() {
81286
+ getReferenceAnchor() {
80407
81287
  const sheetId = this.getters.getActiveSheetId();
80408
81288
  const anchor = this.anchor;
80409
81289
  const { left, right, top, bottom } = anchor.zone;
80410
81290
  const { col: anchorCol, row: anchorRow } = anchor.cell;
81291
+ const col = this.getters.isColHidden(sheetId, anchorCol)
81292
+ ? this.getters.findVisibleHeader(sheetId, "COL", left, right) || anchorCol
81293
+ : anchorCol;
81294
+ const row = this.getters.isRowHidden(sheetId, anchorRow)
81295
+ ? this.getters.findVisibleHeader(sheetId, "ROW", top, bottom) || anchorRow
81296
+ : anchorRow;
81297
+ const zone = this.getters.expandZone(sheetId, {
81298
+ left: col,
81299
+ right: col,
81300
+ top: row,
81301
+ bottom: row,
81302
+ });
80411
81303
  return {
80412
- col: this.getters.isColHidden(sheetId, anchorCol)
80413
- ? this.getters.findVisibleHeader(sheetId, "COL", left, right) || anchorCol
80414
- : anchorCol,
80415
- row: this.getters.isRowHidden(sheetId, anchorRow)
80416
- ? this.getters.findVisibleHeader(sheetId, "ROW", top, bottom) || anchorRow
80417
- : anchorRow,
81304
+ cell: { col, row },
81305
+ zone,
80418
81306
  };
80419
81307
  }
80420
81308
  deltaToTarget(position, direction, step) {
@@ -83636,6 +84524,6 @@ exports.tokenColors = tokenColors;
83636
84524
  exports.tokenize = tokenize;
83637
84525
 
83638
84526
 
83639
- __info__.version = "18.4.0-alpha.9";
83640
- __info__.date = "2025-06-19T18:23:22.025Z";
83641
- __info__.hash = "6d4d685";
84527
+ __info__.version = "18.4.1";
84528
+ __info__.date = "2025-06-27T09:13:01.303Z";
84529
+ __info__.hash = "5cecc0e";