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