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