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