@odoo/o-spreadsheet 19.1.0-alpha.11 → 19.1.0-alpha.13

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.
@@ -3,8 +3,8 @@
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
5
  * @version 19.1.0-alpha.3
6
- * @date 2025-11-03T12:33:46.742Z
7
- * @hash d9230f3
6
+ * @date 2025-11-24T07:52:34.809Z
7
+ * @hash e232982
8
8
  */
9
9
 
10
10
  (function (exports) {
@@ -637,6 +637,7 @@
637
637
  fontSize: 10,
638
638
  fillColor: "",
639
639
  textColor: "",
640
+ rotation: 0,
640
641
  };
641
642
  const DEFAULT_VERTICAL_ALIGN = DEFAULT_STYLE.verticalAlign;
642
643
  // Fonts
@@ -682,7 +683,7 @@
682
683
  };
683
684
  const PIVOT_INDENT = 15;
684
685
  const PIVOT_COLLAPSE_ICON_SIZE = 12;
685
- const PIVOT_MAX_NUMBER_OF_CELLS = 1e5;
686
+ const PIVOT_MAX_NUMBER_OF_CELLS = 5e5;
686
687
 
687
688
  //------------------------------------------------------------------------------
688
689
  // Miscellaneous
@@ -3648,6 +3649,30 @@
3648
3649
  WRAPROWS: WRAPROWS
3649
3650
  });
3650
3651
 
3652
+ const DEFAULT_LOCALES = [
3653
+ {
3654
+ name: "English (US)",
3655
+ code: "en_US",
3656
+ thousandsSeparator: ",",
3657
+ decimalSeparator: ".",
3658
+ weekStart: 7, // Sunday
3659
+ dateFormat: "m/d/yyyy",
3660
+ timeFormat: "hh:mm:ss a",
3661
+ formulaArgSeparator: ",",
3662
+ },
3663
+ {
3664
+ name: "French",
3665
+ code: "fr_FR",
3666
+ thousandsSeparator: " ",
3667
+ decimalSeparator: ",",
3668
+ weekStart: 1, // Monday
3669
+ dateFormat: "dd/mm/yyyy",
3670
+ timeFormat: "hh:mm:ss",
3671
+ formulaArgSeparator: ";",
3672
+ },
3673
+ ];
3674
+ const DEFAULT_LOCALE = DEFAULT_LOCALES[0];
3675
+
3651
3676
  function tokenizeFormat(str) {
3652
3677
  const chars = new TokenizingChars(str);
3653
3678
  const result = [];
@@ -3932,7 +3957,7 @@
3932
3957
  }
3933
3958
  function parseDateFormatTokens(tokens) {
3934
3959
  const internalFormat = tokens && areValidDateFormatTokens(tokens) ? { type: "date", tokens } : undefined;
3935
- if (!internalFormat) {
3960
+ if (!internalFormat || !tokens?.some((token) => token.type === "DATE_PART")) {
3936
3961
  return undefined;
3937
3962
  }
3938
3963
  if (internalFormat.tokens.length &&
@@ -4169,6 +4194,9 @@
4169
4194
  return prefix + repeatedChar.repeat(timesToRepeat) + padding + suffix;
4170
4195
  }
4171
4196
  function applyInternalNumberFormat(value, format, locale) {
4197
+ if (!format.integerPart.length && !format.decimalPart?.length) {
4198
+ return "";
4199
+ }
4172
4200
  if (value === Infinity) {
4173
4201
  return "∞" + (format.percentSymbols ? "%" : "");
4174
4202
  }
@@ -4775,7 +4803,7 @@
4775
4803
  function setXcToFixedReferenceType(xc, referenceType) {
4776
4804
  let sheetName;
4777
4805
  ({ sheetName, xc } = splitReference(xc));
4778
- sheetName = sheetName ? sheetName + "!" : "";
4806
+ sheetName = sheetName ? getCanonicalSymbolName(sheetName) + "!" : "";
4779
4807
  xc = xc.replace(/\$/g, "");
4780
4808
  const splitIndex = xc.indexOf(":");
4781
4809
  if (splitIndex >= 0) {
@@ -5765,7 +5793,10 @@
5765
5793
  * Check if a zone is inside another
5766
5794
  */
5767
5795
  function isZoneInside(smallZone, biggerZone) {
5768
- return isEqual(union(biggerZone, smallZone), biggerZone);
5796
+ return (smallZone.left >= biggerZone.left &&
5797
+ smallZone.right <= biggerZone.right &&
5798
+ smallZone.top >= biggerZone.top &&
5799
+ smallZone.bottom <= biggerZone.bottom);
5769
5800
  }
5770
5801
  function zoneToDimension(zone) {
5771
5802
  return {
@@ -7649,7 +7680,7 @@
7649
7680
  if (!acceptHiddenCells && this.getters.isRowHiddenByUser(sheetId, row))
7650
7681
  continue;
7651
7682
  for (let col = left; col <= right; col++) {
7652
- const cell = this.getters.getCell({ sheetId, col, row });
7683
+ const cell = this.getters.getCorrespondingFormulaCell({ sheetId, col, row });
7653
7684
  if (!cell || !isSubtotalCell(cell)) {
7654
7685
  evaluatedCellToKeep.push(this.getters.getEvaluatedCell({ sheetId, col, row }));
7655
7686
  }
@@ -9544,30 +9575,6 @@
9544
9575
  DVARP: DVARP
9545
9576
  });
9546
9577
 
9547
- const DEFAULT_LOCALES = [
9548
- {
9549
- name: "English (US)",
9550
- code: "en_US",
9551
- thousandsSeparator: ",",
9552
- decimalSeparator: ".",
9553
- weekStart: 7, // Sunday
9554
- dateFormat: "m/d/yyyy",
9555
- timeFormat: "hh:mm:ss a",
9556
- formulaArgSeparator: ",",
9557
- },
9558
- {
9559
- name: "French",
9560
- code: "fr_FR",
9561
- thousandsSeparator: " ",
9562
- decimalSeparator: ",",
9563
- weekStart: 1, // Monday
9564
- dateFormat: "dd/mm/yyyy",
9565
- timeFormat: "hh:mm:ss",
9566
- formulaArgSeparator: ";",
9567
- },
9568
- ];
9569
- const DEFAULT_LOCALE = DEFAULT_LOCALES[0];
9570
-
9571
9578
  /**
9572
9579
  * Tokenizer
9573
9580
  *
@@ -13972,18 +13979,17 @@
13972
13979
  ["GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */]: _t("The lower inflection point value must be a number"),
13973
13980
  ["GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */]: _t("The upper inflection point value must be a number"),
13974
13981
  },
13975
- GeoChart: {
13976
- ColorScales: {
13977
- blues: _t("Blues"),
13978
- cividis: _t("Cividis"),
13979
- greens: _t("Greens"),
13980
- greys: _t("Greys"),
13981
- oranges: _t("Oranges"),
13982
- purples: _t("Purples"),
13983
- rainbow: _t("Rainbow"),
13984
- reds: _t("Reds"),
13985
- viridis: _t("Viridis"),
13986
- },
13982
+ ColorScales: {
13983
+ blues: _t("Blues"),
13984
+ cividis: _t("Cividis"),
13985
+ custom: _t("Custom"),
13986
+ greens: _t("Greens"),
13987
+ greys: _t("Greys"),
13988
+ oranges: _t("Oranges"),
13989
+ purples: _t("Purples"),
13990
+ rainbow: _t("Rainbow"),
13991
+ reds: _t("Reds"),
13992
+ viridis: _t("Viridis"),
13987
13993
  },
13988
13994
  });
13989
13995
  ({
@@ -17909,7 +17915,7 @@
17909
17915
  let sheetName = "";
17910
17916
  if (prefixSheet) {
17911
17917
  if (range.invalidSheetName) {
17912
- sheetName = range.invalidSheetName;
17918
+ sheetName = getCanonicalSymbolName(range.invalidSheetName);
17913
17919
  }
17914
17920
  else {
17915
17921
  sheetName = getCanonicalSymbolName(getSheetName(range.sheetId));
@@ -18818,6 +18824,17 @@
18818
18824
  return this.colors[id];
18819
18825
  }
18820
18826
  }
18827
+ const COLORSCHEMES = {
18828
+ greys: ["#ffffff", "#808080", "#000000"],
18829
+ blues: ["#f7fbff", "#6aaed6", "#08306b"],
18830
+ reds: ["#fff5f0", "#fb694a", "#67000d"],
18831
+ greens: ["#f7fcf5", "#73c476", "#00441b"],
18832
+ oranges: ["#fff5eb", "#fd8c3b", "#7f2704"],
18833
+ purples: ["#fcfbfd", "#9e9ac8", "#3f007d"],
18834
+ viridis: ["#440154", "#21918c", "#fde725"],
18835
+ cividis: ["#00224e", "#7d7c78", "#fee838"],
18836
+ rainbow: ["#B41DB4", "#FFFF00", "#00FFFF"],
18837
+ };
18821
18838
  /**
18822
18839
  * Returns a function that maps a value to a color using a color scale defined by the given
18823
18840
  * color/threshold values pairs.
@@ -19735,8 +19752,7 @@
19735
19752
  .filter((ds) => !isTrendLineAxis(ds["xAxisID"]))
19736
19753
  .map((ds) => ({
19737
19754
  ...ds,
19738
- pointRadius: 0,
19739
- showLine: true,
19755
+ pointRadius: ds.showLine === false ? 2 : 0, // Show points only for scatter plots
19740
19756
  })),
19741
19757
  },
19742
19758
  options: {
@@ -26514,6 +26530,20 @@
26514
26530
  }
26515
26531
  return data;
26516
26532
  },
26533
+ })
26534
+ .add("19.2", {
26535
+ migrate(data) {
26536
+ for (const sheet of data.sheets || []) {
26537
+ for (const figure of sheet.figures || []) {
26538
+ if (figure.tag === "chart" && figure.data.type === "geo") {
26539
+ if ("colorScale" in figure.data && typeof figure.data.colorScale === "string") {
26540
+ figure.data.colorScale = COLORSCHEMES[figure.data.colorScale];
26541
+ }
26542
+ }
26543
+ }
26544
+ }
26545
+ return data;
26546
+ },
26517
26547
  });
26518
26548
  function fixOverlappingFilters(data) {
26519
26549
  for (const sheet of data.sheets || []) {
@@ -27314,22 +27344,34 @@
27314
27344
  addBorder(sheetId, zone, newBorder, force = false) {
27315
27345
  const borders = [];
27316
27346
  const plannedBorder = newBorder ? { zone, style: newBorder } : undefined;
27317
- const sideToClear = {
27318
- left: force || !!newBorder?.left,
27319
- right: force || !!newBorder?.right,
27320
- top: force || !!newBorder?.top,
27321
- bottom: force || !!newBorder?.bottom,
27347
+ // For each side, decide if we must clear the border on the *adjacent*
27348
+ // existing cell when we draw on the opposite side of the new zone.
27349
+ //
27350
+ // Example:
27351
+ // - newBorder.right is set → we draw border on the RIGHT side of `zone`
27352
+ // - the cell on the right may already have a LEFT border on that edge
27353
+ // In that case we clear that LEFT border, so only the new RIGHT border
27354
+ // remains on the shared edge.
27355
+ //
27356
+ // existingBorderSideToClear[side] = true means we should clear the border on that
27357
+ // side of the existing adjacent zone before adding the new border.
27358
+ const existingBorderSideToClear = {
27359
+ left: force || !!newBorder?.right,
27360
+ right: force || !!newBorder?.left,
27361
+ top: force || !!newBorder?.bottom,
27362
+ bottom: force || !!newBorder?.top,
27322
27363
  };
27323
27364
  let editingZone = [zone];
27324
27365
  for (const existingBorder of this.borders[sheetId] ?? []) {
27325
27366
  const inter = intersection(existingBorder.zone, zone);
27326
27367
  if (!inter) {
27327
- // Clear adjacent borders on which you write
27368
+ // Check if the existing border is adjacent to the new zone
27328
27369
  const adjacentEdge = adjacent(existingBorder.zone, zone);
27329
- if (adjacentEdge && sideToClear[adjacentEdge.position]) {
27370
+ if (adjacentEdge && existingBorderSideToClear[adjacentEdge.position]) {
27330
27371
  for (const newZone of splitIfAdjacent(existingBorder.zone, zone)) {
27331
27372
  const border = this.computeBorderFromZone(newZone, existingBorder);
27332
27373
  const adjacentEdge = adjacent(newZone, zone);
27374
+ // Clear the existing border on the side that touches the new zone
27333
27375
  switch (adjacentEdge?.position) {
27334
27376
  case "left":
27335
27377
  border.style.left = undefined;
@@ -33216,6 +33258,13 @@
33216
33258
  "getStyleColors",
33217
33259
  ];
33218
33260
  styles = {};
33261
+ allowDispatch(cmd) {
33262
+ switch (cmd.type) {
33263
+ case "SET_FORMATTING":
33264
+ return this.checkUselessSetFormatting(cmd);
33265
+ }
33266
+ return "Success" /* CommandResult.Success */;
33267
+ }
33219
33268
  handle(cmd) {
33220
33269
  switch (cmd.type) {
33221
33270
  case "ADD_MERGE":
@@ -33440,6 +33489,28 @@
33440
33489
  exportForExcel(data) {
33441
33490
  this.export(data);
33442
33491
  }
33492
+ checkUselessSetFormatting(cmd) {
33493
+ const { sheetId, target } = cmd;
33494
+ const hasStyle = "style" in cmd;
33495
+ const hasFormat = "format" in cmd;
33496
+ if (!hasStyle && !hasFormat) {
33497
+ return "NoChanges" /* CommandResult.NoChanges */;
33498
+ }
33499
+ for (const zone of recomputeZones(target)) {
33500
+ for (let col = zone.left; col <= zone.right; col++) {
33501
+ for (let row = zone.top; row <= zone.bottom; row++) {
33502
+ const position = { sheetId, col, row };
33503
+ const cell = this.getters.getCell(position);
33504
+ const style = this.getCellStyle(position);
33505
+ if ((hasStyle && !deepEquals(style, cmd.style)) ||
33506
+ (hasFormat && cell?.format !== cmd.format)) {
33507
+ return "Success" /* CommandResult.Success */;
33508
+ }
33509
+ }
33510
+ }
33511
+ }
33512
+ return "NoChanges" /* CommandResult.NoChanges */;
33513
+ }
33443
33514
  }
33444
33515
 
33445
33516
  class TableStylePlugin extends CorePlugin {
@@ -36467,48 +36538,64 @@
36467
36538
  }
36468
36539
  function getCellContentHeight(ctx, content, style, colSize) {
36469
36540
  const maxWidth = style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
36470
- const numberOfLines = splitTextToWidth(ctx, content, style, maxWidth).length;
36471
- const fontSize = computeTextFontSizeInPixels(style);
36472
- return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
36541
+ const lines = splitTextToWidth(ctx, content, style, maxWidth);
36542
+ return computeMultilineTextSize(ctx, lines, style).height + 2 * PADDING_AUTORESIZE_VERTICAL;
36473
36543
  }
36474
36544
  function getDefaultContextFont(fontSize, bold = false, italic = false) {
36475
36545
  const italicStr = italic ? "italic" : "";
36476
36546
  const weight = bold ? "bold" : "";
36477
36547
  return `${italicStr} ${weight} ${fontSize}px ${DEFAULT_FONT}`;
36478
36548
  }
36479
- const textWidthCache = {};
36480
- function computeTextWidth(context, text, style, fontUnit = "pt") {
36549
+ function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
36550
+ if (!textLines.length)
36551
+ return { width: 0, height: 0 };
36481
36552
  const font = computeTextFont(style, fontUnit);
36482
- return computeCachedTextWidth(context, text, font);
36483
- }
36484
- function computeCachedTextWidth(context, text, font) {
36485
- if (!textWidthCache[font]) {
36486
- textWidthCache[font] = {};
36553
+ const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
36554
+ const height = computeTextLinesHeight(sizes[0].height, textLines.length);
36555
+ const width = Math.max(...sizes.map((size) => size.width));
36556
+ if (!style.rotation) {
36557
+ return { height, width };
36487
36558
  }
36488
- if (textWidthCache[font][text] === undefined) {
36489
- const oldFont = context.font;
36490
- context.font = font;
36491
- textWidthCache[font][text] = context.measureText(text).width;
36492
- context.font = oldFont;
36559
+ const cos = Math.abs(Math.cos(style.rotation));
36560
+ const sin = Math.abs(Math.sin(style.rotation));
36561
+ return { width: width * cos + height * sin, height: sin * width + cos * height };
36562
+ }
36563
+ function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
36564
+ const font = computeTextFont(style, fontUnit);
36565
+ return computeCachedTextWidth(context, text, font, style.rotation);
36566
+ }
36567
+ function computeCachedTextWidth(context, text, font, rotation) {
36568
+ const size = computeCachedTextDimension(context, text, font);
36569
+ if (!rotation) {
36570
+ return size.width;
36493
36571
  }
36494
- return textWidthCache[font][text];
36572
+ const cos = Math.abs(Math.cos(rotation));
36573
+ const sin = Math.abs(Math.sin(rotation));
36574
+ return size.width * cos + size.height * sin;
36495
36575
  }
36496
36576
  const textDimensionsCache = {};
36497
36577
  function computeTextDimension(context, text, style, fontUnit = "pt") {
36498
36578
  const font = computeTextFont(style, fontUnit);
36499
- context.save();
36500
- context.font = font;
36501
- const dimensions = computeCachedTextDimension(context, text);
36502
- context.restore();
36503
- return dimensions;
36504
- }
36505
- function computeCachedTextDimension(context, text) {
36506
- const font = context.font;
36579
+ const size = computeCachedTextDimension(context, text, font);
36580
+ if (!style.rotation) {
36581
+ return size;
36582
+ }
36583
+ const cos = Math.abs(Math.cos(style.rotation));
36584
+ const sin = Math.abs(Math.sin(style.rotation));
36585
+ return {
36586
+ width: size.width * cos + size.height * sin,
36587
+ height: size.height * cos + size.width * sin,
36588
+ };
36589
+ }
36590
+ function computeCachedTextDimension(context, text, font) {
36507
36591
  if (!textDimensionsCache[font]) {
36508
36592
  textDimensionsCache[font] = {};
36509
36593
  }
36510
36594
  if (textDimensionsCache[font][text] === undefined) {
36595
+ context.save();
36596
+ context.font = font;
36511
36597
  const measure = context.measureText(text);
36598
+ context.restore();
36512
36599
  const width = measure.width;
36513
36600
  const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
36514
36601
  textDimensionsCache[font][text] = { width, height };
@@ -38051,9 +38138,13 @@
38051
38138
  if (!blob) {
38052
38139
  return undefined;
38053
38140
  }
38054
- if (!URL.createObjectURL)
38055
- throw new Error("URL.createObjectURL is not supported in this environment");
38056
- return URL.createObjectURL(blob);
38141
+ return new Promise((resolve) => {
38142
+ const f = new FileReader();
38143
+ f.addEventListener("load", () => {
38144
+ resolve(f.result);
38145
+ });
38146
+ f.readAsDataURL(blob);
38147
+ });
38057
38148
  }
38058
38149
 
38059
38150
  class EvaluationChartPlugin extends CoreViewPlugin {
@@ -38701,7 +38792,8 @@
38701
38792
  }
38702
38793
  break;
38703
38794
  case "SET_FORMATTING":
38704
- if (cmd.style && ("fontSize" in cmd.style || "wrapping" in cmd.style)) {
38795
+ if (cmd.style &&
38796
+ ("fontSize" in cmd.style || "wrapping" in cmd.style || "rotation" in cmd.style)) {
38705
38797
  for (const zone of cmd.target) {
38706
38798
  // TODO FLDA use rangeSet
38707
38799
  this.updateRowSizeForZoneChange(cmd.sheetId, zone);
@@ -43478,6 +43570,7 @@
43478
43570
  "getTextWidth",
43479
43571
  "getCellText",
43480
43572
  "getCellMultiLineText",
43573
+ "getMultilineTextSize",
43481
43574
  "getContiguousZone",
43482
43575
  "computeTextYCoordinate",
43483
43576
  ];
@@ -43528,7 +43621,7 @@
43528
43621
  const content = this.getters.getEvaluatedCell(position).formattedValue;
43529
43622
  if (content) {
43530
43623
  const multiLineText = splitTextToWidth(this.ctx, content, style, undefined);
43531
- contentWidth += Math.max(...multiLineText.map((line) => computeTextWidth(this.ctx, line, style)));
43624
+ contentWidth += computeMultilineTextSize(this.ctx, multiLineText, style).width;
43532
43625
  }
43533
43626
  for (const icon of this.getters.getCellIcons(position)) {
43534
43627
  contentWidth += icon.margin + icon.size;
@@ -43549,6 +43642,9 @@
43549
43642
  getTextWidth(text, style) {
43550
43643
  return computeTextWidth(this.ctx, text, style);
43551
43644
  }
43645
+ getMultilineTextSize(text, style) {
43646
+ return computeMultilineTextSize(this.ctx, text, style);
43647
+ }
43552
43648
  getCellText(position, args) {
43553
43649
  const cell = this.getters.getCell(position);
43554
43650
  const locale = this.getters.getLocale();
@@ -46132,6 +46228,7 @@
46132
46228
  "getGridOffset",
46133
46229
  "getViewportZoomLevel",
46134
46230
  "getScrollBarWidth",
46231
+ "getMaximumSheetOffset",
46135
46232
  ];
46136
46233
  viewports = {};
46137
46234
  /**
@@ -47237,6 +47334,9 @@
47237
47334
  observe(owner, callbacks) {
47238
47335
  this.observers.set(owner, { owner, callbacks });
47239
47336
  }
47337
+ unobserve(owner) {
47338
+ this.observers.delete(owner);
47339
+ }
47240
47340
  /**
47241
47341
  * Capture the stream for yourself
47242
47342
  */
@@ -47329,6 +47429,9 @@
47329
47429
  observe(owner, callbacks) {
47330
47430
  this.stream.observe(owner, callbacks);
47331
47431
  }
47432
+ unobserve(owner) {
47433
+ this.stream.unobserve(owner);
47434
+ }
47332
47435
  release(owner) {
47333
47436
  if (this.stream.isListening(owner)) {
47334
47437
  this.stream.release(owner);
@@ -50921,8 +51024,8 @@
50921
51024
 
50922
51025
 
50923
51026
  __info__.version = "19.1.0-alpha.3";
50924
- __info__.date = "2025-11-03T12:33:46.742Z";
50925
- __info__.hash = "d9230f3";
51027
+ __info__.date = "2025-11-24T07:52:34.809Z";
51028
+ __info__.hash = "e232982";
50926
51029
 
50927
51030
 
50928
51031
  })(this.o_spreadsheet_engine = this.o_spreadsheet_engine || {});