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