@odoo/o-spreadsheet 18.3.2 → 18.3.3

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.3.2
6
- * @date 2025-05-12T05:25:42.099Z
7
- * @hash 57d5a67
5
+ * @version 18.3.3
6
+ * @date 2025-05-13T17:54:43.312Z
7
+ * @hash b79924a
8
8
  */
9
9
 
10
10
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, App, blockDom, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -3319,7 +3319,7 @@ function isDateAfter(date, dateAfter) {
3319
3319
  */
3320
3320
  const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSeparator) {
3321
3321
  decimalSeparator = escapeRegExp(decimalSeparator);
3322
- return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3322
+ return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e(\\+|-)?\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3323
3323
  });
3324
3324
  const getNumberRegex = memoize(function getNumberRegex(locale) {
3325
3325
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
@@ -6000,6 +6000,67 @@ function scrollDelay(value) {
6000
6000
  return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
6001
6001
  }
6002
6002
 
6003
+ function createDefaultRows(rowNumber) {
6004
+ const rows = [];
6005
+ for (let i = 0; i < rowNumber; i++) {
6006
+ const row = {
6007
+ cells: {},
6008
+ };
6009
+ rows.push(row);
6010
+ }
6011
+ return rows;
6012
+ }
6013
+ function moveHeaderIndexesOnHeaderAddition(indexHeaderAdded, numberAdded, headers) {
6014
+ return headers.map((header) => {
6015
+ if (header >= indexHeaderAdded) {
6016
+ return header + numberAdded;
6017
+ }
6018
+ return header;
6019
+ });
6020
+ }
6021
+ function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6022
+ deletedHeaders = [...deletedHeaders].sort((a, b) => b - a);
6023
+ return headers
6024
+ .map((header) => {
6025
+ for (const deletedHeader of deletedHeaders) {
6026
+ if (header > deletedHeader) {
6027
+ header--;
6028
+ }
6029
+ else if (header === deletedHeader) {
6030
+ return undefined;
6031
+ }
6032
+ }
6033
+ return header;
6034
+ })
6035
+ .filter(isDefined);
6036
+ }
6037
+ function getNextSheetName(existingNames, baseName = "Sheet") {
6038
+ let i = 1;
6039
+ let name = `${baseName}${i}`;
6040
+ while (existingNames.includes(name)) {
6041
+ name = `${baseName}${i}`;
6042
+ i++;
6043
+ }
6044
+ return name;
6045
+ }
6046
+ function getDuplicateSheetName(nameToDuplicate, existingNames) {
6047
+ let i = 1;
6048
+ const baseName = _t("Copy of %s", nameToDuplicate);
6049
+ let name = baseName.toString();
6050
+ while (existingNames.includes(name)) {
6051
+ name = `${baseName} (${i})`;
6052
+ i++;
6053
+ }
6054
+ return name;
6055
+ }
6056
+ function isSheetNameEqual(name1, name2) {
6057
+ if (name1 === undefined || name2 === undefined) {
6058
+ return false;
6059
+ }
6060
+ return (getUnquotedSheetName(name1.trim().toUpperCase()) ===
6061
+ getUnquotedSheetName(name2.trim().toUpperCase()));
6062
+ }
6063
+
6003
6064
  function createRange(args, getSheetSize) {
6004
6065
  const unboundedZone = args.zone;
6005
6066
  const zone = boundUnboundedZone(unboundedZone, getSheetSize(args.sheetId));
@@ -6290,7 +6351,7 @@ function getApplyRangeChangeRemoveColRow(cmd) {
6290
6351
  elements.sort((a, b) => b - a);
6291
6352
  const groups = groupConsecutive(elements);
6292
6353
  return (range) => {
6293
- if (range.sheetId !== cmd.sheetId) {
6354
+ if (!isSheetNameEqual(range.sheetId, cmd.sheetId)) {
6294
6355
  return { changeType: "NONE" };
6295
6356
  }
6296
6357
  let newRange = range;
@@ -6497,60 +6558,6 @@ function fuzzyLookup(pattern, list, fn) {
6497
6558
  return results.map((r) => r.elem);
6498
6559
  }
6499
6560
 
6500
- function createDefaultRows(rowNumber) {
6501
- const rows = [];
6502
- for (let i = 0; i < rowNumber; i++) {
6503
- const row = {
6504
- cells: {},
6505
- };
6506
- rows.push(row);
6507
- }
6508
- return rows;
6509
- }
6510
- function moveHeaderIndexesOnHeaderAddition(indexHeaderAdded, numberAdded, headers) {
6511
- return headers.map((header) => {
6512
- if (header >= indexHeaderAdded) {
6513
- return header + numberAdded;
6514
- }
6515
- return header;
6516
- });
6517
- }
6518
- function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6519
- deletedHeaders = [...deletedHeaders].sort((a, b) => b - a);
6520
- return headers
6521
- .map((header) => {
6522
- for (const deletedHeader of deletedHeaders) {
6523
- if (header > deletedHeader) {
6524
- header--;
6525
- }
6526
- else if (header === deletedHeader) {
6527
- return undefined;
6528
- }
6529
- }
6530
- return header;
6531
- })
6532
- .filter(isDefined);
6533
- }
6534
- function getNextSheetName(existingNames, baseName = "Sheet") {
6535
- let i = 1;
6536
- let name = `${baseName}${i}`;
6537
- while (existingNames.includes(name)) {
6538
- name = `${baseName}${i}`;
6539
- i++;
6540
- }
6541
- return name;
6542
- }
6543
- function getDuplicateSheetName(nameToDuplicate, existingNames) {
6544
- let i = 1;
6545
- const baseName = _t("Copy of %s", nameToDuplicate);
6546
- let name = baseName.toString();
6547
- while (existingNames.includes(name)) {
6548
- name = `${baseName} (${i})`;
6549
- i++;
6550
- }
6551
- return name;
6552
- }
6553
-
6554
6561
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
6555
6562
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
6556
6563
  }
@@ -8307,7 +8314,8 @@ const monthAdapter = {
8307
8314
  };
8308
8315
  },
8309
8316
  toFunctionValue(normalizedValue) {
8310
- return `"${normalizedValue}"`;
8317
+ const jsDate = toJsDate(normalizedValue, DEFAULT_LOCALE);
8318
+ return `DATE(${jsDate.getFullYear()},${jsDate.getMonth() + 1},1)`;
8311
8319
  },
8312
8320
  };
8313
8321
  /**
@@ -8462,10 +8470,9 @@ const AGGREGATOR_NAMES = {
8462
8470
  avg: _t("Average"),
8463
8471
  sum: _t("Sum"),
8464
8472
  };
8465
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8466
8473
  const AGGREGATORS_BY_FIELD_TYPE = {
8467
- integer: NUMBER_CHAR_AGGREGATORS,
8468
- char: NUMBER_CHAR_AGGREGATORS,
8474
+ integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
8475
+ char: ["count_distinct", "count"],
8469
8476
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8470
8477
  datetime: ["max", "min", "count_distinct", "count"],
8471
8478
  };
@@ -9833,7 +9840,10 @@ function proxifyStoreMutation(store, callback) {
9833
9840
  const functionProxy = new Proxy(value, {
9834
9841
  // trap the function call
9835
9842
  apply(target, thisArg, argArray) {
9836
- Reflect.apply(target, thisStore, argArray);
9843
+ const res = Reflect.apply(target, thisStore, argArray);
9844
+ if (res === "noStateChange") {
9845
+ return;
9846
+ }
9837
9847
  callback();
9838
9848
  },
9839
9849
  });
@@ -9855,7 +9865,7 @@ function getDependencyContainer(env) {
9855
9865
  const ModelStore = createAbstractStore("Model");
9856
9866
 
9857
9867
  class RendererStore {
9858
- mutators = ["register", "unRegister"];
9868
+ mutators = ["register", "unRegister", "drawLayer"];
9859
9869
  renderers = {};
9860
9870
  register(renderer) {
9861
9871
  if (!renderer.renderingLayers.length) {
@@ -9875,14 +9885,14 @@ class RendererStore {
9875
9885
  }
9876
9886
  drawLayer(context, layer) {
9877
9887
  const renderers = this.renderers[layer];
9878
- if (!renderers) {
9879
- return;
9880
- }
9881
- for (const renderer of renderers) {
9882
- context.ctx.save();
9883
- renderer.drawLayer(context, layer);
9884
- context.ctx.restore();
9888
+ if (renderers) {
9889
+ for (const renderer of renderers) {
9890
+ context.ctx.save();
9891
+ renderer.drawLayer(context, layer);
9892
+ context.ctx.restore();
9893
+ }
9885
9894
  }
9895
+ return "noStateChange";
9886
9896
  }
9887
9897
  }
9888
9898
 
@@ -9935,16 +9945,17 @@ class ComposerFocusStore extends SpreadsheetStore {
9935
9945
  focusComposer(listener, args) {
9936
9946
  this.activeComposer = listener;
9937
9947
  if (this.getters.isReadonly()) {
9938
- return;
9948
+ return "noStateChange";
9939
9949
  }
9940
9950
  this._focusMode = args.focusMode || "contentFocus";
9941
9951
  if (this._focusMode !== "inactive") {
9942
9952
  this.setComposerContent(args);
9943
9953
  }
9954
+ return;
9944
9955
  }
9945
9956
  focusActiveComposer(args) {
9946
9957
  if (this.getters.isReadonly()) {
9947
- return;
9958
+ return "noStateChange";
9948
9959
  }
9949
9960
  if (!this.activeComposer) {
9950
9961
  throw new Error("No composer is registered");
@@ -9953,6 +9964,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9953
9964
  if (this._focusMode !== "inactive") {
9954
9965
  this.setComposerContent(args);
9955
9966
  }
9967
+ return;
9956
9968
  }
9957
9969
  /**
9958
9970
  * Start the edition or update the content if it's already started.
@@ -10556,7 +10568,7 @@ function getDefinedAxis(definition) {
10556
10568
  }
10557
10569
  function formatChartDatasetValue(axisFormats, locale) {
10558
10570
  return (value, axisId) => {
10559
- const format = axisId ? axisFormats?.[axisId] : undefined;
10571
+ const format = axisFormats?.[axisId];
10560
10572
  return formatTickValue({ format, locale })(value);
10561
10573
  };
10562
10574
  }
@@ -10733,7 +10745,7 @@ function drawPieChartValues(chart, options, ctx) {
10733
10745
  const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10734
10746
  ctx.fillStyle = chartFontColor(options.background);
10735
10747
  ctx.strokeStyle = options.background || "#ffffff";
10736
- const displayValue = options.callback(value);
10748
+ const displayValue = options.callback(value, "y");
10737
10749
  drawTextWithBackground(displayValue, x, y, ctx);
10738
10750
  }
10739
10751
  }
@@ -20542,6 +20554,9 @@ const PIVOT_VALUE = {
20542
20554
  };
20543
20555
  }
20544
20556
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
20557
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
20558
+ this.getters.getPivotPresenceTracker(pivotId)?.trackValue(_measure, domain);
20559
+ }
20545
20560
  return pivot.getPivotCellValueAndFormat(_measure, domain);
20546
20561
  },
20547
20562
  };
@@ -20573,6 +20588,9 @@ const PIVOT_HEADER = {
20573
20588
  };
20574
20589
  }
20575
20590
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
20591
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
20592
+ this.getters.getPivotPresenceTracker(_pivotId)?.trackHeader(domain);
20593
+ }
20576
20594
  const lastNode = domain.at(-1);
20577
20595
  if (lastNode?.field === "measure") {
20578
20596
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
@@ -20795,6 +20813,9 @@ function isEmpty(data) {
20795
20813
  return data === undefined || data.value === null;
20796
20814
  }
20797
20815
  const getNeutral = { number: 0, string: "", boolean: false };
20816
+ function areAlmostEqual(value1, value2, epsilon = 2e-16) {
20817
+ return Math.abs(value1 - value2) < epsilon;
20818
+ }
20798
20819
  const EQ = {
20799
20820
  description: _t("Equal."),
20800
20821
  args: [
@@ -20816,6 +20837,9 @@ const EQ = {
20816
20837
  if (typeof _value2 === "string") {
20817
20838
  _value2 = _value2.toUpperCase();
20818
20839
  }
20840
+ if (typeof _value1 === "number" && typeof _value2 === "number") {
20841
+ return { value: areAlmostEqual(_value1, _value2) };
20842
+ }
20819
20843
  return { value: _value1 === _value2 };
20820
20844
  },
20821
20845
  };
@@ -20855,6 +20879,9 @@ const GT = {
20855
20879
  ],
20856
20880
  compute: function (value1, value2) {
20857
20881
  return applyRelationalOperator(value1, value2, (v1, v2) => {
20882
+ if (typeof v1 === "number" && typeof v2 === "number") {
20883
+ return !areAlmostEqual(v1, v2) && v1 > v2;
20884
+ }
20858
20885
  return v1 > v2;
20859
20886
  });
20860
20887
  },
@@ -20870,6 +20897,9 @@ const GTE = {
20870
20897
  ],
20871
20898
  compute: function (value1, value2) {
20872
20899
  return applyRelationalOperator(value1, value2, (v1, v2) => {
20900
+ if (typeof v1 === "number" && typeof v2 === "number") {
20901
+ return areAlmostEqual(v1, v2) || v1 > v2;
20902
+ }
20873
20903
  return v1 >= v2;
20874
20904
  });
20875
20905
  },
@@ -22562,7 +22592,7 @@ class AbstractComposerStore extends SpreadsheetStore {
22562
22592
  .find((token) => {
22563
22593
  const { xc, sheetName: sheet } = splitReference(token.value);
22564
22594
  const sheetName = sheet || this.getters.getSheetName(this.sheetId);
22565
- if (this.getters.getSheetName(activeSheetId) !== sheetName) {
22595
+ if (!isSheetNameEqual(this.getters.getSheetName(activeSheetId), sheetName)) {
22566
22596
  return false;
22567
22597
  }
22568
22598
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
@@ -24499,6 +24529,8 @@ async function chartToImageFile(runtime, figure, type) {
24499
24529
  const div = document.createElement("div");
24500
24530
  div.style.width = `${figure.width}px`;
24501
24531
  div.style.height = `${figure.height}px`;
24532
+ div.style.position = "fixed";
24533
+ div.style.opacity = "0";
24502
24534
  const canvas = document.createElement("canvas");
24503
24535
  div.append(canvas);
24504
24536
  canvas.setAttribute("width", figure.width.toString());
@@ -29701,10 +29733,15 @@ function getChartMenu(figureId, onFigureDeleted, env) {
29701
29733
  const imageUrl = chartToImageUrl(runtime, figure, chartType);
29702
29734
  const innerHTML = `<img src="${xmlEscape(imageUrl)}" />`;
29703
29735
  const blob = await chartToImageFile(runtime, figure, chartType);
29704
- env.clipboard.write({
29736
+ await env.clipboard.write({
29705
29737
  "text/html": innerHTML,
29706
29738
  "image/png": blob,
29707
29739
  });
29740
+ env.notifyUser({
29741
+ text: _t("The chart was copied to your clipboard"),
29742
+ sticky: false,
29743
+ type: "info",
29744
+ });
29708
29745
  },
29709
29746
  },
29710
29747
  {
@@ -31554,7 +31591,7 @@ function getRangeSize(reference, defaultSheetIndex, data) {
31554
31591
  ({ xc, sheetName } = splitReference(reference));
31555
31592
  let rangeSheetIndex;
31556
31593
  if (sheetName) {
31557
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
31594
+ const index = data.sheets.findIndex((sheet) => isSheetNameEqual(sheet.name, sheetName));
31558
31595
  if (index < 0) {
31559
31596
  throw new Error("Unable to find a sheet with the name " + sheetName);
31560
31597
  }
@@ -31911,7 +31948,7 @@ function convertFormula(formula, data) {
31911
31948
  formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
31912
31949
  externalRefId = Number(externalRefId) - 1;
31913
31950
  cellRef = cellRef.replace(/\$/g, "");
31914
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
31951
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => isSheetNameEqual(name, sheetName));
31915
31952
  if (sheetIndex === -1) {
31916
31953
  return match;
31917
31954
  }
@@ -32562,7 +32599,7 @@ function convertPivotTableConfig(pivotTable) {
32562
32599
  */
32563
32600
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
32564
32601
  for (let tableSheet of convertedSheets) {
32565
- const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
32602
+ const tables = xlsxSheets.find((s) => isSheetNameEqual(s.sheetName, tableSheet.name)).tables;
32566
32603
  for (let table of tables) {
32567
32604
  const tabRef = table.name + "[";
32568
32605
  for (let sheet of convertedSheets) {
@@ -35217,12 +35254,20 @@ class DelayedHoveredCellStore extends SpreadsheetStore {
35217
35254
  }
35218
35255
  }
35219
35256
  hover(position) {
35257
+ if (position.col === this.col && position.row === this.row) {
35258
+ return "noStateChange";
35259
+ }
35220
35260
  this.col = position.col;
35221
35261
  this.row = position.row;
35262
+ return;
35222
35263
  }
35223
35264
  clear() {
35265
+ if (this.col === undefined && this.row === undefined) {
35266
+ return "noStateChange";
35267
+ }
35224
35268
  this.col = undefined;
35225
35269
  this.row = undefined;
35270
+ return;
35226
35271
  }
35227
35272
  }
35228
35273
 
@@ -35244,7 +35289,11 @@ class CellPopoverStore extends SpreadsheetStore {
35244
35289
  this.persistentPopover = { col, row, sheetId, type };
35245
35290
  }
35246
35291
  close() {
35292
+ if (!this.persistentPopover) {
35293
+ return "noStateChange";
35294
+ }
35247
35295
  this.persistentPopover = undefined;
35296
+ return;
35248
35297
  }
35249
35298
  get persistentCellPopover() {
35250
35299
  return ((this.persistentPopover && { isOpen: true, ...this.persistentPopover }) || { isOpen: false });
@@ -42362,10 +42411,18 @@ class GaugeChartConfigPanel extends Component {
42362
42411
  }
42363
42412
 
42364
42413
  class DOMFocusableElementStore {
42365
- mutators = ["setFocusableElement"];
42414
+ mutators = ["setFocusableElement", "focus"];
42366
42415
  focusableElement = undefined;
42367
42416
  setFocusableElement(element) {
42368
42417
  this.focusableElement = element;
42418
+ return "noStateChange";
42419
+ }
42420
+ focus() {
42421
+ if (this.focusableElement === document.activeElement) {
42422
+ return "noStateChange";
42423
+ }
42424
+ this.focusableElement?.focus();
42425
+ return;
42369
42426
  }
42370
42427
  }
42371
42428
 
@@ -43037,7 +43094,7 @@ class Composer extends Component {
43037
43094
  if (document.activeElement === this.contentHelper.el &&
43038
43095
  this.props.composerStore.editionMode === "inactive" &&
43039
43096
  !this.props.isDefaultFocus) {
43040
- this.DOMFocusableElementStore.focusableElement?.focus();
43097
+ this.DOMFocusableElementStore.focus();
43041
43098
  }
43042
43099
  });
43043
43100
  useEffect(() => {
@@ -49575,7 +49632,12 @@ class SpreadsheetPivot {
49575
49632
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
49576
49633
  }
49577
49634
  else {
49578
- entry[field.name] = cell;
49635
+ if (field.type === "char") {
49636
+ entry[field.name] = { ...cell, value: cell.formattedValue || null };
49637
+ }
49638
+ else {
49639
+ entry[field.name] = cell;
49640
+ }
49579
49641
  }
49580
49642
  }
49581
49643
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -53300,9 +53362,13 @@ class HoveredTableStore extends SpreadsheetStore {
53300
53362
  }
53301
53363
  }
53302
53364
  hover(position) {
53365
+ if (position.col === this.col && position.row === this.row) {
53366
+ return "noStateChange";
53367
+ }
53303
53368
  this.col = position.col;
53304
53369
  this.row = position.row;
53305
53370
  this.computeOverlay();
53371
+ return;
53306
53372
  }
53307
53373
  clear() {
53308
53374
  this.col = undefined;
@@ -54934,10 +55000,6 @@ function useGridDrawing(refName, model, canvasSize) {
54934
55000
  ctx.scale(dpr, dpr);
54935
55001
  for (const layer of OrderedLayers()) {
54936
55002
  model.drawLayer(renderingContext, layer);
54937
- // @ts-ignore 'drawLayer' is not declated as a mutator because:
54938
- // it does not mutate anything. Most importantly it's used
54939
- // during rendering. Invoking a mutator during rendering would
54940
- // trigger another rendering, ultimately resulting in an infinite loop.
54941
55003
  rendererStore.drawLayer(renderingContext, layer);
54942
55004
  }
54943
55005
  }
@@ -55631,7 +55693,7 @@ class Grid extends Component {
55631
55693
  this.cellPopovers = useStore(CellPopoverStore);
55632
55694
  useEffect(() => {
55633
55695
  if (!this.sidePanel.isOpen) {
55634
- this.DOMFocusableElementStore.focusableElement?.focus();
55696
+ this.DOMFocusableElementStore.focus();
55635
55697
  }
55636
55698
  }, () => [this.sidePanel.isOpen]);
55637
55699
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
@@ -55840,7 +55902,7 @@ class Grid extends Component {
55840
55902
  focusDefaultElement() {
55841
55903
  if (!this.env.model.getters.getSelectedFigureId() &&
55842
55904
  this.composerFocusStore.activeComposer.editionMode === "inactive") {
55843
- this.DOMFocusableElementStore.focusableElement?.focus();
55905
+ this.DOMFocusableElementStore.focus();
55844
55906
  }
55845
55907
  }
55846
55908
  get gridEl() {
@@ -56189,6 +56251,322 @@ class Grid extends Component {
56189
56251
  }
56190
56252
  }
56191
56253
 
56254
+ css /* scss */ `
56255
+ .o_pivot_html_renderer {
56256
+ width: 100%;
56257
+ border-collapse: collapse;
56258
+
56259
+ &:hover {
56260
+ cursor: pointer;
56261
+ }
56262
+
56263
+ td,
56264
+ th {
56265
+ border: 1px solid #dee2e6;
56266
+ background-color: #fff;
56267
+ padding: 0.3rem;
56268
+ white-space: nowrap;
56269
+
56270
+ &:hover {
56271
+ filter: brightness(0.9);
56272
+ }
56273
+ }
56274
+
56275
+ td {
56276
+ text-align: right;
56277
+ }
56278
+
56279
+ th {
56280
+ background-color: #f5f5f5;
56281
+ font-weight: bold;
56282
+ color: black;
56283
+ }
56284
+
56285
+ .o_missing_value {
56286
+ color: #46646d;
56287
+ background: #e7f2f6;
56288
+ }
56289
+ }
56290
+ `;
56291
+ class PivotHTMLRenderer extends Component {
56292
+ static template = "o_spreadsheet.PivotHTMLRenderer";
56293
+ static components = { Checkbox };
56294
+ static props = {
56295
+ pivotId: String,
56296
+ onCellClicked: Function,
56297
+ };
56298
+ pivot = this.env.model.getters.getPivot(this.props.pivotId);
56299
+ data = {
56300
+ columns: [],
56301
+ rows: [],
56302
+ values: [],
56303
+ };
56304
+ state = useState({
56305
+ showMissingValuesOnly: false,
56306
+ });
56307
+ setup() {
56308
+ const table = this.pivot.getTableStructure();
56309
+ const formulaId = this.env.model.getters.getPivotFormulaId(this.props.pivotId);
56310
+ this.data = {
56311
+ columns: this._buildColHeaders(formulaId, table),
56312
+ rows: this._buildRowHeaders(formulaId, table),
56313
+ values: this._buildValues(formulaId, table),
56314
+ };
56315
+ }
56316
+ get tracker() {
56317
+ return this.env.model.getters.getPivotPresenceTracker(this.props.pivotId);
56318
+ }
56319
+ // ---------------------------------------------------------------------
56320
+ // Missing values building
56321
+ // ---------------------------------------------------------------------
56322
+ /**
56323
+ * Retrieve the data to display in the Pivot Table
56324
+ * In the case when showMissingValuesOnly is false, the returned value
56325
+ * is the complete data
56326
+ * In the case when showMissingValuesOnly is true, the returned value is
56327
+ * the data which contains only missing values in the rows and cols. In
56328
+ * the rows, we also return the parent rows of rows which contains missing
56329
+ * values, to give context to the user.
56330
+ *
56331
+ */
56332
+ getTableData() {
56333
+ if (!this.state.showMissingValuesOnly) {
56334
+ return this.data;
56335
+ }
56336
+ const colIndexes = this.getColumnsIndexes();
56337
+ const rowIndexes = this.getRowsIndexes();
56338
+ const columns = this.buildColumnsMissing(colIndexes);
56339
+ const rows = this.buildRowsMissing(rowIndexes);
56340
+ const values = this.buildValuesMissing(colIndexes, rowIndexes);
56341
+ return { columns, rows, values };
56342
+ }
56343
+ /**
56344
+ * Retrieve the parents of the given row
56345
+ * ex:
56346
+ * Australia
56347
+ * January
56348
+ * February
56349
+ * The parent of "January" is "Australia"
56350
+ */
56351
+ addRecursiveRow(index) {
56352
+ const rows = this.pivot.getTableStructure().rows;
56353
+ const row = [...rows[index].values];
56354
+ if (row.length <= 1) {
56355
+ return [index];
56356
+ }
56357
+ row.pop();
56358
+ const parentRowIndex = rows.findIndex((r) => JSON.stringify(r.values) === JSON.stringify(row));
56359
+ return [index].concat(this.addRecursiveRow(parentRowIndex));
56360
+ }
56361
+ /**
56362
+ * Create the columns to be used, based on the indexes of the columns in
56363
+ * which a missing value is present
56364
+ *
56365
+ */
56366
+ buildColumnsMissing(indexes) {
56367
+ // columnsMap explode the columns in an array of array of the same
56368
+ // size with the index of each column, repeated 'span' times.
56369
+ // ex:
56370
+ // | A | B |
56371
+ // | 1 | 2 | 3 |
56372
+ // => [
56373
+ // [0, 0, 1]
56374
+ // [0, 1, 2]
56375
+ // ]
56376
+ const columnsMap = [];
56377
+ for (const column of this.data.columns) {
56378
+ const columnMap = [];
56379
+ for (const index in column) {
56380
+ for (let i = 0; i < column[index].span; i++) {
56381
+ columnMap.push(parseInt(index, 10));
56382
+ }
56383
+ }
56384
+ columnsMap.push(columnMap);
56385
+ }
56386
+ // Remove the columns that are not present in indexes
56387
+ for (let i = columnsMap[columnsMap.length - 1].length; i >= 0; i--) {
56388
+ if (!indexes.includes(i)) {
56389
+ for (const columnMap of columnsMap) {
56390
+ columnMap.splice(i, 1);
56391
+ }
56392
+ }
56393
+ }
56394
+ // Build the columns
56395
+ const columns = [];
56396
+ for (const mapIndex in columnsMap) {
56397
+ const column = [];
56398
+ let index = undefined;
56399
+ let span = 1;
56400
+ for (let i = 0; i < columnsMap[mapIndex].length; i++) {
56401
+ if (index !== columnsMap[mapIndex][i]) {
56402
+ if (index !== undefined) {
56403
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
56404
+ }
56405
+ index = columnsMap[mapIndex][i];
56406
+ span = 1;
56407
+ }
56408
+ else {
56409
+ span++;
56410
+ }
56411
+ }
56412
+ if (index !== undefined) {
56413
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
56414
+ }
56415
+ columns.push(column);
56416
+ }
56417
+ return columns;
56418
+ }
56419
+ /**
56420
+ * Create the rows to be used, based on the indexes of the rows in
56421
+ * which a missing value is present.
56422
+ */
56423
+ buildRowsMissing(indexes) {
56424
+ return indexes.map((index) => this.data.rows[index]);
56425
+ }
56426
+ /**
56427
+ * Create the value to be used, based on the indexes of the columns and
56428
+ * rows in which a missing value is present.
56429
+ */
56430
+ buildValuesMissing(colIndexes, rowIndexes) {
56431
+ const values = colIndexes.map(() => []);
56432
+ for (const row of rowIndexes) {
56433
+ for (const col in colIndexes) {
56434
+ values[col].push(this.data.values[colIndexes[col]][row]);
56435
+ }
56436
+ }
56437
+ return values;
56438
+ }
56439
+ getColumnsIndexes() {
56440
+ const indexes = new Set();
56441
+ for (let i = 0; i < this.data.columns.length; i++) {
56442
+ const exploded = [];
56443
+ for (let y = 0; y < this.data.columns[i].length; y++) {
56444
+ for (let x = 0; x < this.data.columns[i][y].span; x++) {
56445
+ exploded.push(this.data.columns[i][y]);
56446
+ }
56447
+ }
56448
+ for (let y = 0; y < exploded.length; y++) {
56449
+ if (exploded[y].isMissing) {
56450
+ indexes.add(y);
56451
+ }
56452
+ }
56453
+ }
56454
+ for (let i = 0; i < this.data.columns[this.data.columns.length - 1].length; i++) {
56455
+ const values = this.data.values[i];
56456
+ if (values.find((x) => x.isMissing)) {
56457
+ indexes.add(i);
56458
+ }
56459
+ }
56460
+ return Array.from(indexes).sort((a, b) => a - b);
56461
+ }
56462
+ getRowsIndexes() {
56463
+ const rowIndexes = new Set();
56464
+ for (let i = 0; i < this.data.rows.length; i++) {
56465
+ if (this.data.rows[i].isMissing) {
56466
+ rowIndexes.add(i);
56467
+ }
56468
+ for (const col of this.data.values) {
56469
+ if (col[i].isMissing) {
56470
+ this.addRecursiveRow(i).forEach((x) => rowIndexes.add(x));
56471
+ }
56472
+ }
56473
+ }
56474
+ return Array.from(rowIndexes).sort((a, b) => a - b);
56475
+ }
56476
+ // ---------------------------------------------------------------------
56477
+ // Data table creation
56478
+ // ---------------------------------------------------------------------
56479
+ _buildColHeaders(id, table) {
56480
+ const headers = [];
56481
+ for (const row of table.columns) {
56482
+ const current = [];
56483
+ for (const cell of row) {
56484
+ const args = [];
56485
+ for (let i = 0; i < cell.fields.length; i++) {
56486
+ args.push({ value: cell.fields[i] }, { value: cell.values[i] });
56487
+ }
56488
+ const domain = this.pivot.parseArgsToPivotDomain(args);
56489
+ const locale = this.env.model.getters.getLocale();
56490
+ if (domain.at(-1)?.field === "measure") {
56491
+ const { value, format } = this.pivot.getPivotMeasureValue(toString(domain.at(-1).value), domain);
56492
+ current.push({
56493
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
56494
+ value: formatValue(value, { format, locale }),
56495
+ span: cell.width,
56496
+ isMissing: !this.tracker?.isHeaderPresent(domain),
56497
+ });
56498
+ }
56499
+ else {
56500
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
56501
+ current.push({
56502
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
56503
+ value: formatValue(value, { format, locale }),
56504
+ span: cell.width,
56505
+ isMissing: !this.tracker?.isHeaderPresent(domain),
56506
+ });
56507
+ }
56508
+ }
56509
+ headers.push(current);
56510
+ }
56511
+ const last = headers[headers.length - 1];
56512
+ headers[headers.length - 1] = last.map((cell) => {
56513
+ if (!cell.isMissing) {
56514
+ cell.style = "color: #756f6f;";
56515
+ }
56516
+ return cell;
56517
+ });
56518
+ return headers;
56519
+ }
56520
+ _buildRowHeaders(id, table) {
56521
+ const headers = [];
56522
+ for (const row of table.rows) {
56523
+ const args = [];
56524
+ for (let i = 0; i < row.fields.length; i++) {
56525
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
56526
+ }
56527
+ const domain = this.pivot.parseArgsToPivotDomain(args);
56528
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
56529
+ const locale = this.env.model.getters.getLocale();
56530
+ const cell = {
56531
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
56532
+ value: formatValue(value, { format, locale }),
56533
+ isMissing: !this.tracker?.isHeaderPresent(domain),
56534
+ };
56535
+ if (row.indent > 1) {
56536
+ cell.style = `padding-left: ${row.indent - 1 * 10}px`;
56537
+ }
56538
+ headers.push(cell);
56539
+ }
56540
+ return headers;
56541
+ }
56542
+ _buildValues(id, table) {
56543
+ const values = [];
56544
+ for (const col of table.columns.at(-1) || []) {
56545
+ const current = [];
56546
+ const measure = toString(col.values[col.values.length - 1]);
56547
+ for (const row of table.rows) {
56548
+ const args = [];
56549
+ for (let i = 0; i < row.fields.length; i++) {
56550
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
56551
+ }
56552
+ for (let i = 0; i < col.fields.length - 1; i++) {
56553
+ args.push({ value: col.fields[i] }, { value: col.values[i] });
56554
+ }
56555
+ const domain = this.pivot.parseArgsToPivotDomain(args);
56556
+ const { value, format } = this.pivot.getPivotCellValueAndFormat(measure, domain);
56557
+ const locale = this.env.model.getters.getLocale();
56558
+ current.push({
56559
+ formula: `=PIVOT.VALUE(${generatePivotArgs(id, domain, measure).join(",")})`,
56560
+ value: formatValue(value, { format, locale }),
56561
+ isMissing: !this.tracker?.isValuePresent(measure, domain),
56562
+ });
56563
+ }
56564
+ values.push(current);
56565
+ }
56566
+ return values;
56567
+ }
56568
+ }
56569
+
56192
56570
  /**
56193
56571
  * BasePlugin
56194
56572
  *
@@ -60208,7 +60586,7 @@ class SheetPlugin extends CorePlugin {
60208
60586
  if (name) {
60209
60587
  const unquotedName = getUnquotedSheetName(name);
60210
60588
  for (const key in this.sheetIdsMapName) {
60211
- if (key.toUpperCase() === unquotedName.toUpperCase()) {
60589
+ if (isSheetNameEqual(key, unquotedName)) {
60212
60590
  return this.sheetIdsMapName[key];
60213
60591
  }
60214
60592
  }
@@ -60457,7 +60835,7 @@ class SheetPlugin extends CorePlugin {
60457
60835
  }
60458
60836
  const { orderedSheetIds, sheets } = this;
60459
60837
  const name = sheetName && sheetName.trim().toLowerCase();
60460
- if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
60838
+ if (orderedSheetIds.find((id) => isSheetNameEqual(sheets[id]?.name, name) && id !== cmd.sheetId)) {
60461
60839
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
60462
60840
  }
60463
60841
  if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
@@ -69541,6 +69919,55 @@ class HistoryPlugin extends UIPlugin {
69541
69919
  }
69542
69920
  }
69543
69921
 
69922
+ class PivotPresenceTracker {
69923
+ trackedValues = new Set();
69924
+ domainToArray(domain) {
69925
+ return domain.flatMap((node) => [node.field, toString(node.value)]);
69926
+ }
69927
+ isValuePresent(measure, domain) {
69928
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
69929
+ return this.trackedValues.has(key);
69930
+ }
69931
+ isHeaderPresent(domain) {
69932
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
69933
+ return this.trackedValues.has(key);
69934
+ }
69935
+ trackValue(measure, domain) {
69936
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
69937
+ this.trackedValues.add(key);
69938
+ }
69939
+ trackHeader(domain) {
69940
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
69941
+ this.trackedValues.add(key);
69942
+ }
69943
+ }
69944
+
69945
+ class PivotPresencePlugin extends UIPlugin {
69946
+ static getters = ["getPivotPresenceTracker"];
69947
+ trackPresencePivotId;
69948
+ tracker;
69949
+ handle(cmd) {
69950
+ switch (cmd.type) {
69951
+ case "PIVOT_START_PRESENCE_TRACKING":
69952
+ this.tracker = new PivotPresenceTracker();
69953
+ this.trackPresencePivotId = cmd.pivotId;
69954
+ break;
69955
+ case "PIVOT_STOP_PRESENCE_TRACKING":
69956
+ this.trackPresencePivotId = undefined;
69957
+ break;
69958
+ }
69959
+ }
69960
+ getPivotPresenceTracker(pivotId) {
69961
+ if (this.trackPresencePivotId !== pivotId) {
69962
+ return undefined;
69963
+ }
69964
+ if (!this.tracker) {
69965
+ throw new Error("Tracker not initialized");
69966
+ }
69967
+ return this.tracker;
69968
+ }
69969
+ }
69970
+
69544
69971
  class SplitToColumnsPlugin extends UIPlugin {
69545
69972
  static getters = ["getAutomaticSeparator"];
69546
69973
  allowDispatch(cmd) {
@@ -72509,6 +72936,7 @@ const featurePluginRegistry = new Registry()
72509
72936
  .add("automatic_sum", AutomaticSumPlugin)
72510
72937
  .add("format", FormatPlugin)
72511
72938
  .add("insert_pivot", InsertPivotPlugin)
72939
+ .add("pivot_presence", PivotPresencePlugin)
72512
72940
  .add("split_to_columns", SplitToColumnsPlugin)
72513
72941
  .add("collaborative", CollaborativePlugin)
72514
72942
  .add("history", HistoryPlugin)
@@ -72898,11 +73326,11 @@ class BottomBarSheet extends Component {
72898
73326
  if (ev.key === "Enter") {
72899
73327
  ev.preventDefault();
72900
73328
  this.stopEdition();
72901
- this.DOMFocusableElementStore.focusableElement?.focus();
73329
+ this.DOMFocusableElementStore.focus();
72902
73330
  }
72903
73331
  if (ev.key === "Escape") {
72904
73332
  this.cancelEdition();
72905
- this.DOMFocusableElementStore.focusableElement?.focus();
73333
+ this.DOMFocusableElementStore.focus();
72906
73334
  }
72907
73335
  }
72908
73336
  onMouseEventSheetName(ev) {
@@ -79911,6 +80339,7 @@ const components = {
79911
80339
  PivotDimensionOrder,
79912
80340
  PivotDimension,
79913
80341
  PivotLayoutConfigurator,
80342
+ PivotHTMLRenderer,
79914
80343
  PivotDeferUpdate,
79915
80344
  PivotTitleSection,
79916
80345
  CogWheelMenu,
@@ -79962,6 +80391,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
79962
80391
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, CoreViewPlugin, DispatchResult, EvaluationError, 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 };
79963
80392
 
79964
80393
 
79965
- __info__.version = "18.3.2";
79966
- __info__.date = "2025-05-12T05:25:42.099Z";
79967
- __info__.hash = "57d5a67";
80394
+ __info__.version = "18.3.3";
80395
+ __info__.date = "2025-05-13T17:54:43.312Z";
80396
+ __info__.hash = "b79924a";