@odoo/o-spreadsheet 17.0.50 → 17.0.52

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 17.0.50
6
- * @date 2025-02-14T08:37:34.289Z
7
- * @hash cc1037d
5
+ * @version 17.0.52
6
+ * @date 2025-03-07T10:30:04.783Z
7
+ * @hash c19cd9a
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -4732,11 +4732,12 @@
4732
4732
  if (this.isFastIdStrategy) {
4733
4733
  this.fastIdStart++;
4734
4734
  return String(this.fastIdStart);
4735
- //@ts-ignore
4736
4735
  }
4737
- else if (window.crypto && window.crypto.getRandomValues) {
4738
- //@ts-ignore
4739
- return ([1e7] + -1e3).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
4736
+ else if (window.crypto) {
4737
+ return "10000000-1000".replace(/[01]/g, (c) => {
4738
+ const n = Number(c);
4739
+ return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
4740
+ });
4740
4741
  }
4741
4742
  else {
4742
4743
  // mainly for jest and other browsers that do not have the crypto functionality
@@ -4754,11 +4755,12 @@
4754
4755
  if (this.isFastIdStrategy) {
4755
4756
  this.fastIdStart++;
4756
4757
  return String(this.fastIdStart);
4757
- //@ts-ignore
4758
4758
  }
4759
- else if (window.crypto && window.crypto.getRandomValues) {
4760
- //@ts-ignore
4761
- return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
4759
+ else if (window.crypto) {
4760
+ return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => {
4761
+ const n = Number(c);
4762
+ return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
4763
+ });
4762
4764
  }
4763
4765
  else {
4764
4766
  // mainly for jest and other browsers that do not have the crypto functionality
@@ -6211,7 +6213,7 @@
6211
6213
  condition: (cell) => !cell.isFormula &&
6212
6214
  evaluateLiteral(cell.content, { locale: DEFAULT_LOCALE }).type === CellValueType.text &&
6213
6215
  alphaNumericValueRegExp.test(cell.content),
6214
- generateRule: (cell, cells) => {
6216
+ generateRule: (cell, cells, direction) => {
6215
6217
  const numberPostfix = parseInt(cell.content.match(numberPostfixRegExp)[0]);
6216
6218
  const prefix = cell.content.match(stringPrefixRegExp)[0];
6217
6219
  const numberPostfixLength = cell.content.length - prefix.length;
@@ -6219,7 +6221,10 @@
6219
6221
  alphaNumericValueRegExp.test(evaluatedCell.value)) // get consecutive alphanumeric cells, no matter what the prefix is
6220
6222
  .filter((cell) => prefix === cell.value.toString().match(stringPrefixRegExp)[0])
6221
6223
  .map((cell) => parseInt(cell.value.toString().match(numberPostfixRegExp)[0]));
6222
- const increment = calculateIncrementBasedOnGroup(group);
6224
+ let increment = calculateIncrementBasedOnGroup(group);
6225
+ if (["up", "left"].includes(direction) && group.length === 1) {
6226
+ increment = -increment;
6227
+ }
6223
6228
  return {
6224
6229
  type: "ALPHANUMERIC_INCREMENT_MODIFIER",
6225
6230
  prefix,
@@ -6248,9 +6253,12 @@
6248
6253
  .add("increment_number", {
6249
6254
  condition: (cell) => !cell.isFormula &&
6250
6255
  evaluateLiteral(cell.content, { locale: DEFAULT_LOCALE }).type === CellValueType.number,
6251
- generateRule: (cell, cells) => {
6256
+ generateRule: (cell, cells, direction) => {
6252
6257
  const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number).map((cell) => Number(cell.value));
6253
- const increment = calculateIncrementBasedOnGroup(group);
6258
+ let increment = calculateIncrementBasedOnGroup(group);
6259
+ if (["up", "left"].includes(direction) && group.length === 1) {
6260
+ increment = -increment;
6261
+ }
6254
6262
  const evaluation = evaluateLiteral(cell.content, { locale: DEFAULT_LOCALE });
6255
6263
  return {
6256
6264
  type: "INCREMENT_MODIFIER",
@@ -6984,10 +6992,6 @@
6984
6992
  this.currentDisplayValue = newDisplay;
6985
6993
  if (!anchor)
6986
6994
  return;
6987
- el.style.top = "";
6988
- el.style.left = "";
6989
- el.style["max-height"] = "";
6990
- el.style["max-width"] = "";
6991
6995
  const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };
6992
6996
  let elDims = {
6993
6997
  width: el.getBoundingClientRect().width,
@@ -9729,7 +9733,7 @@
9729
9733
  const dataset = {
9730
9734
  label,
9731
9735
  data,
9732
- borderColor: "#FFFFFF",
9736
+ borderColor: chart.background || "#FFFFFF",
9733
9737
  backgroundColor,
9734
9738
  };
9735
9739
  config.data.datasets.push(dataset);
@@ -26761,25 +26765,21 @@
26761
26765
  }
26762
26766
  }
26763
26767
  }
26768
+ }
26769
+ .o-spreadsheet .o-composer-assistant {
26770
+ pointer-events: none;
26764
26771
 
26765
- .o-composer-assistant {
26766
- position: absolute;
26767
- margin: 1px 4px;
26768
- pointer-events: none;
26769
- overflow: auto;
26770
-
26771
- .o-semi-bold {
26772
- /** FIXME: to remove in favor of Bootstrap
26773
- * 'fw-semibold' when we upgrade to Bootstrap 5.2
26774
- */
26775
- font-weight: 600 !important;
26776
- }
26772
+ .o-semi-bold {
26773
+ /** FIXME: to remove in favor of Bootstrap
26774
+ * 'fw-semibold' when we upgrade to Bootstrap 5.2
26775
+ */
26776
+ font-weight: 600 !important;
26777
26777
  }
26778
26778
  }
26779
26779
  `;
26780
26780
  class Composer extends owl.Component {
26781
26781
  static template = "o-spreadsheet-Composer";
26782
- static components = { TextValueProvider, FunctionDescriptionProvider };
26782
+ static components = { TextValueProvider, FunctionDescriptionProvider, Popover };
26783
26783
  static defaultProps = {
26784
26784
  inputStyle: "",
26785
26785
  isDefaultFocus: false,
@@ -26804,35 +26804,13 @@
26804
26804
  argToFocus: 0,
26805
26805
  });
26806
26806
  compositionActive = false;
26807
- spreadsheetRect = useSpreadsheetRect();
26808
26807
  get assistantStyle() {
26809
- const composerRect = this.composerRef.el.getBoundingClientRect();
26810
26808
  const assistantStyle = {};
26811
- assistantStyle["min-width"] = `${this.props.rect?.width || ASSISTANT_WIDTH}px`;
26809
+ const minWidth = Math.min(getBoundingRectAsPOJO(this.composerRef.el).width || Infinity, ASSISTANT_WIDTH);
26810
+ assistantStyle["min-width"] = `${minWidth}px`;
26812
26811
  if (this.autoCompleteState.type === "function") {
26813
26812
  assistantStyle.width = `${ASSISTANT_WIDTH}px`;
26814
26813
  }
26815
- if (this.props.delimitation && this.props.rect) {
26816
- const { x: cellX, y: cellY, height: cellHeight } = this.props.rect;
26817
- const remainingHeight = this.props.delimitation.height - (cellY + cellHeight);
26818
- assistantStyle["max-height"] = `${remainingHeight}px`;
26819
- if (cellY > remainingHeight) {
26820
- const availableSpaceAbove = cellY;
26821
- assistantStyle["max-height"] = `${availableSpaceAbove}px`;
26822
- // render top
26823
- // We compensate 2 px of margin on the assistant style + 1px for design reasons
26824
- assistantStyle.top = `-3px`;
26825
- assistantStyle.transform = `translate(0, -100%)`;
26826
- }
26827
- if (cellX + ASSISTANT_WIDTH > this.props.delimitation.width) {
26828
- // render left
26829
- assistantStyle.right = `0px`;
26830
- }
26831
- }
26832
- if (this.props.focus === "contentFocus" &&
26833
- composerRect.left + ASSISTANT_WIDTH > this.spreadsheetRect.width) {
26834
- assistantStyle.right = "0px";
26835
- }
26836
26814
  return cssPropertiesToCss(assistantStyle);
26837
26815
  }
26838
26816
  // we can't allow input events to be triggered while we remove and add back the content of the composer in processContent
@@ -27081,7 +27059,7 @@
27081
27059
  this.autoCompleteState.selectedIndex = 0;
27082
27060
  }
27083
27061
  updateAutoCompleteIndex(index) {
27084
- this.autoCompleteState.selectedIndex = clip(0, index, 10);
27062
+ this.autoCompleteState.selectedIndex = index;
27085
27063
  }
27086
27064
  /**
27087
27065
  * This is required to ensure the content helper selection is
@@ -27369,11 +27347,16 @@
27369
27347
  this.autoCompleteState.values = values.map((value) => ({ text: value, description: "" }));
27370
27348
  this.autoCompleteState.getHtmlContent = (value) => [{ value }];
27371
27349
  }
27350
+ get popoverProps() {
27351
+ return {
27352
+ anchorRect: getBoundingRectAsPOJO(this.composerRef.el),
27353
+ positioning: "BottomLeft",
27354
+ verticalOffset: 0,
27355
+ };
27356
+ }
27372
27357
  }
27373
27358
  Composer.props = {
27374
27359
  inputStyle: { type: String, optional: true },
27375
- rect: { type: Object, optional: true },
27376
- delimitation: { type: Object, optional: true },
27377
27360
  focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27378
27361
  onComposerCellFocused: { type: Function, optional: true },
27379
27362
  onComposerContentFocused: Function,
@@ -27443,13 +27426,7 @@
27443
27426
  });
27444
27427
  }
27445
27428
  get composerProps() {
27446
- const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
27447
27429
  return {
27448
- rect: { ...this.rect },
27449
- delimitation: {
27450
- width,
27451
- height,
27452
- },
27453
27430
  focus: this.props.focus,
27454
27431
  isDefaultFocus: true,
27455
27432
  onComposerContentFocused: this.props.onComposerContentFocused,
@@ -28991,6 +28968,9 @@
28991
28968
  this.MAX_SIZE_MARGIN = 90;
28992
28969
  this.MIN_ELEMENT_SIZE = MIN_COL_WIDTH;
28993
28970
  }
28971
+ get sheetId() {
28972
+ return this.env.model.getters.getActiveSheetId();
28973
+ }
28994
28974
  _getEvOffset(ev) {
28995
28975
  return ev.offsetX;
28996
28976
  }
@@ -29013,10 +28993,10 @@
29013
28993
  return this.env.model.getters.getEdgeScrollCol(position, position, position);
29014
28994
  }
29015
28995
  _getDimensionsInViewport(index) {
29016
- return this.env.model.getters.getColDimensionsInViewport(this.env.model.getters.getActiveSheetId(), index);
28996
+ return this.env.model.getters.getColDimensionsInViewport(this.sheetId, index);
29017
28997
  }
29018
28998
  _getElementSize(index) {
29019
- return this.env.model.getters.getColSize(this.env.model.getters.getActiveSheetId(), index);
28999
+ return this.env.model.getters.getColSize(this.sheetId, index);
29020
29000
  }
29021
29001
  _getMaxSize() {
29022
29002
  return this.colResizerRef.el.clientWidth;
@@ -29027,7 +29007,7 @@
29027
29007
  const cols = this.env.model.getters.getActiveCols();
29028
29008
  this.env.model.dispatch("RESIZE_COLUMNS_ROWS", {
29029
29009
  dimension: "COL",
29030
- sheetId: this.env.model.getters.getActiveSheetId(),
29010
+ sheetId: this.sheetId,
29031
29011
  elements: cols.has(index) ? [...cols] : [index],
29032
29012
  size,
29033
29013
  });
@@ -29040,7 +29020,7 @@
29040
29020
  elements.push(colIndex);
29041
29021
  }
29042
29022
  const result = this.env.model.dispatch("MOVE_COLUMNS_ROWS", {
29043
- sheetId: this.env.model.getters.getActiveSheetId(),
29023
+ sheetId: this.sheetId,
29044
29024
  dimension: "COL",
29045
29025
  base: this.state.base,
29046
29026
  elements,
@@ -29059,7 +29039,7 @@
29059
29039
  _fitElementSize(index) {
29060
29040
  const cols = this.env.model.getters.getActiveCols();
29061
29041
  this.env.model.dispatch("AUTORESIZE_COLUMNS", {
29062
- sheetId: this.env.model.getters.getActiveSheetId(),
29042
+ sheetId: this.sheetId,
29063
29043
  cols: cols.has(index) ? [...cols] : [index],
29064
29044
  });
29065
29045
  }
@@ -29070,7 +29050,7 @@
29070
29050
  return this.env.model.getters.getActiveCols();
29071
29051
  }
29072
29052
  _getPreviousVisibleElement(index) {
29073
- const sheetId = this.env.model.getters.getActiveSheetId();
29053
+ const sheetId = this.sheetId;
29074
29054
  let row;
29075
29055
  for (row = index - 1; row >= 0; row--) {
29076
29056
  if (!this.env.model.getters.isColHidden(sheetId, row)) {
@@ -29081,7 +29061,7 @@
29081
29061
  }
29082
29062
  unhide(hiddenElements) {
29083
29063
  this.env.model.dispatch("UNHIDE_COLUMNS_ROWS", {
29084
- sheetId: this.env.model.getters.getActiveSheetId(),
29064
+ sheetId: this.sheetId,
29085
29065
  elements: hiddenElements,
29086
29066
  dimension: "COL",
29087
29067
  });
@@ -29097,7 +29077,7 @@
29097
29077
  left: 0;
29098
29078
  right: 0;
29099
29079
  width: ${HEADER_WIDTH}px;
29100
- height: 100%;
29080
+ height: calc(100% - ${HEADER_HEIGHT + SCROLLBAR_WIDTH}px);
29101
29081
  &.o-dragging {
29102
29082
  cursor: grabbing;
29103
29083
  }
@@ -29165,6 +29145,9 @@
29165
29145
  this.MIN_ELEMENT_SIZE = MIN_ROW_HEIGHT;
29166
29146
  }
29167
29147
  rowResizerRef;
29148
+ get sheetId() {
29149
+ return this.env.model.getters.getActiveSheetId();
29150
+ }
29168
29151
  _getEvOffset(ev) {
29169
29152
  return ev.offsetY;
29170
29153
  }
@@ -29187,10 +29170,10 @@
29187
29170
  return this.env.model.getters.getEdgeScrollRow(position, position, position);
29188
29171
  }
29189
29172
  _getDimensionsInViewport(index) {
29190
- return this.env.model.getters.getRowDimensionsInViewport(this.env.model.getters.getActiveSheetId(), index);
29173
+ return this.env.model.getters.getRowDimensionsInViewport(this.sheetId, index);
29191
29174
  }
29192
29175
  _getElementSize(index) {
29193
- return this.env.model.getters.getRowSize(this.env.model.getters.getActiveSheetId(), index);
29176
+ return this.env.model.getters.getRowSize(this.sheetId, index);
29194
29177
  }
29195
29178
  _getMaxSize() {
29196
29179
  return this.rowResizerRef.el.clientHeight;
@@ -29201,7 +29184,7 @@
29201
29184
  const rows = this.env.model.getters.getActiveRows();
29202
29185
  this.env.model.dispatch("RESIZE_COLUMNS_ROWS", {
29203
29186
  dimension: "ROW",
29204
- sheetId: this.env.model.getters.getActiveSheetId(),
29187
+ sheetId: this.sheetId,
29205
29188
  elements: rows.has(index) ? [...rows] : [index],
29206
29189
  size,
29207
29190
  });
@@ -29214,7 +29197,7 @@
29214
29197
  elements.push(rowIndex);
29215
29198
  }
29216
29199
  const result = this.env.model.dispatch("MOVE_COLUMNS_ROWS", {
29217
- sheetId: this.env.model.getters.getActiveSheetId(),
29200
+ sheetId: this.sheetId,
29218
29201
  dimension: "ROW",
29219
29202
  base: this.state.base,
29220
29203
  elements,
@@ -29233,7 +29216,7 @@
29233
29216
  _fitElementSize(index) {
29234
29217
  const rows = this.env.model.getters.getActiveRows();
29235
29218
  this.env.model.dispatch("AUTORESIZE_ROWS", {
29236
- sheetId: this.env.model.getters.getActiveSheetId(),
29219
+ sheetId: this.sheetId,
29237
29220
  rows: rows.has(index) ? [...rows] : [index],
29238
29221
  });
29239
29222
  }
@@ -29244,7 +29227,7 @@
29244
29227
  return this.env.model.getters.getActiveRows();
29245
29228
  }
29246
29229
  _getPreviousVisibleElement(index) {
29247
- const sheetId = this.env.model.getters.getActiveSheetId();
29230
+ const sheetId = this.sheetId;
29248
29231
  let row;
29249
29232
  for (row = index - 1; row >= 0; row--) {
29250
29233
  if (!this.env.model.getters.isRowHidden(sheetId, row)) {
@@ -29255,7 +29238,7 @@
29255
29238
  }
29256
29239
  unhide(hiddenElements) {
29257
29240
  this.env.model.dispatch("UNHIDE_COLUMNS_ROWS", {
29258
- sheetId: this.env.model.getters.getActiveSheetId(),
29241
+ sheetId: this.sheetId,
29259
29242
  dimension: "ROW",
29260
29243
  elements: hiddenElements,
29261
29244
  });
@@ -30400,6 +30383,7 @@
30400
30383
  const CONTENT_TYPES = {
30401
30384
  workbook: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
30402
30385
  sheet: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
30386
+ metadata: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml",
30403
30387
  sharedStrings: "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
30404
30388
  styles: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
30405
30389
  drawing: "application/vnd.openxmlformats-officedocument.drawing+xml",
@@ -30412,6 +30396,7 @@
30412
30396
  const XLSX_RELATION_TYPE = {
30413
30397
  document: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
30414
30398
  sheet: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
30399
+ metadata: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata",
30415
30400
  sharedStrings: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings",
30416
30401
  styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
30417
30402
  drawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
@@ -30421,6 +30406,7 @@
30421
30406
  hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
30422
30407
  image: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
30423
30408
  };
30409
+ const ARRAY_FORMULA_URI = "bdbb8cdc-fa1e-496e-a857-3c3f30c029c3";
30424
30410
  const RELATIONSHIP_NSR = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
30425
30411
  const HEIGHT_FACTOR = 0.75; // 100px => 75 u
30426
30412
  /**
@@ -32250,29 +32236,33 @@
32250
32236
  * In all the sheets, replace the table-only references in the formula cells with standard references.
32251
32237
  */
32252
32238
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
32253
- for (let sheet of convertedSheets) {
32254
- const tables = xlsxSheets.find((s) => s.sheetName === sheet.name).tables;
32239
+ for (let tableSheet of convertedSheets) {
32240
+ const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
32255
32241
  for (let table of tables) {
32256
32242
  const tabRef = table.name + "[";
32257
- for (let position of positions(toZone(table.ref))) {
32258
- const xc = toXC(position.col, position.row);
32259
- const cell = sheet.cells[xc];
32260
- if (cell && cell.content && cell.content.startsWith("=")) {
32261
- let refIndex;
32262
- while ((refIndex = cell.content.indexOf(tabRef)) !== -1) {
32263
- let reference = cell.content.slice(refIndex + tabRef.length);
32264
- // Expression can either be tableName[colName] or tableName[[#This Row], [colName]]
32265
- let endIndex = reference.indexOf("]");
32266
- if (reference.startsWith(`[`)) {
32267
- endIndex = reference.indexOf("]", endIndex + 1);
32268
- endIndex = reference.indexOf("]", endIndex + 1);
32243
+ for (let sheet of convertedSheets) {
32244
+ for (let xc in sheet.cells) {
32245
+ const cell = sheet.cells[xc];
32246
+ if (cell && cell.content && cell.content.startsWith("=")) {
32247
+ let refIndex;
32248
+ while ((refIndex = cell.content.indexOf(tabRef)) !== -1) {
32249
+ let endIndex = refIndex + tabRef.length;
32250
+ let openBrackets = 1;
32251
+ while (openBrackets > 0 && endIndex < cell.content.length) {
32252
+ if (cell.content[endIndex] === "[") {
32253
+ openBrackets++;
32254
+ }
32255
+ else if (cell.content[endIndex] === "]") {
32256
+ openBrackets--;
32257
+ }
32258
+ endIndex++;
32259
+ }
32260
+ let reference = cell.content.slice(refIndex + tabRef.length, endIndex - 1);
32261
+ const sheetPrefix = tableSheet.id === sheet.id ? "" : tableSheet.name + "!";
32262
+ const convertedRef = convertTableReference(sheetPrefix, reference, table, xc);
32263
+ cell.content =
32264
+ cell.content.slice(0, refIndex) + convertedRef + cell.content.slice(endIndex);
32269
32265
  }
32270
- reference = reference.slice(0, endIndex);
32271
- const convertedRef = convertTableReference(reference, table, xc);
32272
- cell.content =
32273
- cell.content.slice(0, refIndex) +
32274
- convertedRef +
32275
- cell.content.slice(tabRef.length + refIndex + endIndex + 1);
32276
32266
  }
32277
32267
  }
32278
32268
  }
@@ -32280,11 +32270,17 @@
32280
32270
  }
32281
32271
  }
32282
32272
  /**
32283
- * Convert table-specific references in formulas into standard references.
32273
+ * Convert table-specific references in formulas into standard references. A table reference is composed of columns names,
32274
+ * and of keywords determining the rows of the table to reference.
32284
32275
  *
32285
32276
  * A reference in a table can have the form (only the part between brackets should be given to this function):
32286
32277
  * - tableName[colName] : reference to the whole column "colName"
32278
+ * - tableName[#keyword] : reference to the whatever row the keyword refers to
32287
32279
  * - tableName[[#keyword], [colName]] : reference to some of the element(s) of the column colName
32280
+ * - tableName[[#keyword], [colName]:[col2Name]] : reference to some of the element(s) of the columns colName to col2Name
32281
+ * - tableName[[#keyword1], [#keyword2], [colName]] : reference to all the rows referenced by the keywords in the column colName
32282
+ * - tableName[[#keyword1], [colName], [#keyword2]]: the keywords and colName can be in any order
32283
+ *
32288
32284
  *
32289
32285
  * The available keywords are :
32290
32286
  * - #All : all the column (including totals)
@@ -32292,58 +32288,109 @@
32292
32288
  * - #Headers : only the header of the column
32293
32289
  * - #Totals : only the totals of the column
32294
32290
  * - #This Row : only the element in the same row as the cell
32291
+ *
32292
+ * Note that the only valid combination of multiple keywords are #Data + #Totals and #Headers + #Data.
32295
32293
  */
32296
- function convertTableReference(expr, table, cellXc) {
32297
- const refElements = expr.split(",");
32294
+ function convertTableReference(sheetPrefix, expr, table, cellXc) {
32295
+ // TODO: Ideally we'd want to make a real tokenizer, this simple approach won't work if for example the column name
32296
+ // contain # or , characters. But that's probably an edge case that we can ignore for now.
32297
+ const parts = expr.split(",").map((part) => part.trim());
32298
32298
  const tableZone = toZone(table.ref);
32299
- const refZone = { ...tableZone };
32300
- let isReferencedZoneValid = true;
32301
- // Single column reference
32302
- if (refElements.length === 1) {
32303
- const colRelativeIndex = table.cols.findIndex((col) => col.name === refElements[0]);
32304
- refZone.left = refZone.right = colRelativeIndex + tableZone.left;
32305
- if (table.headerRowCount) {
32306
- refZone.top += table.headerRowCount;
32307
- }
32308
- if (table.totalsRowCount) {
32309
- refZone.bottom -= 1;
32299
+ const colIndexes = [];
32300
+ const rowIndexes = [];
32301
+ const foundKeywords = [];
32302
+ for (const part of parts) {
32303
+ if (removeBrackets(part).startsWith("#")) {
32304
+ const keyWord = removeBrackets(part);
32305
+ foundKeywords.push(keyWord);
32306
+ switch (keyWord) {
32307
+ case "#All":
32308
+ rowIndexes.push(tableZone.top, tableZone.bottom);
32309
+ break;
32310
+ case "#Data":
32311
+ const top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
32312
+ const bottom = table.totalsRowCount
32313
+ ? tableZone.bottom - table.totalsRowCount
32314
+ : tableZone.bottom;
32315
+ rowIndexes.push(top, bottom);
32316
+ break;
32317
+ case "#This Row":
32318
+ rowIndexes.push(toCartesian(cellXc).row);
32319
+ break;
32320
+ case "#Headers":
32321
+ if (!table.headerRowCount) {
32322
+ return INCORRECT_RANGE_STRING;
32323
+ }
32324
+ rowIndexes.push(tableZone.top);
32325
+ break;
32326
+ case "#Totals":
32327
+ if (!table.totalsRowCount) {
32328
+ return INCORRECT_RANGE_STRING;
32329
+ }
32330
+ rowIndexes.push(tableZone.bottom);
32331
+ break;
32332
+ }
32310
32333
  }
32311
- }
32312
- // Other references
32313
- else {
32314
- switch (refElements[0].slice(1, refElements[0].length - 1)) {
32315
- case "#All":
32316
- refZone.top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
32317
- refZone.bottom = tableZone.bottom;
32318
- break;
32319
- case "#Data":
32320
- refZone.top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
32321
- refZone.bottom = table.totalsRowCount ? tableZone.bottom + 1 : tableZone.bottom;
32322
- break;
32323
- case "#This Row":
32324
- refZone.top = refZone.bottom = toCartesian(cellXc).row;
32325
- break;
32326
- case "#Headers":
32327
- refZone.top = refZone.bottom = tableZone.top;
32328
- if (!table.headerRowCount) {
32329
- isReferencedZoneValid = false;
32330
- }
32331
- break;
32332
- case "#Totals":
32333
- refZone.top = refZone.bottom = tableZone.bottom;
32334
- if (!table.totalsRowCount) {
32335
- isReferencedZoneValid = false;
32334
+ else {
32335
+ const columns = part
32336
+ .split(":")
32337
+ .map((part) => part.trim())
32338
+ .map(removeBrackets);
32339
+ if (colIndexes.length) {
32340
+ return INCORRECT_RANGE_STRING;
32341
+ }
32342
+ const colRelativeIndex = table.cols.findIndex((col) => col.name === columns[0]);
32343
+ if (colRelativeIndex === -1) {
32344
+ return INCORRECT_RANGE_STRING;
32345
+ }
32346
+ colIndexes.push(colRelativeIndex + tableZone.left);
32347
+ if (columns[1]) {
32348
+ const colRelativeIndex2 = table.cols.findIndex((col) => col.name === columns[1]);
32349
+ if (colRelativeIndex2 === -1) {
32350
+ return INCORRECT_RANGE_STRING;
32336
32351
  }
32337
- break;
32352
+ colIndexes.push(colRelativeIndex2 + tableZone.left);
32353
+ }
32338
32354
  }
32339
- const colRef = refElements[1].slice(1, refElements[1].length - 1);
32340
- const colRelativeIndex = table.cols.findIndex((col) => col.name === colRef);
32341
- refZone.left = refZone.right = colRelativeIndex + tableZone.left;
32342
32355
  }
32343
- if (!isReferencedZoneValid) {
32356
+ if (!areKeywordsCompatible(foundKeywords)) {
32344
32357
  return INCORRECT_RANGE_STRING;
32345
32358
  }
32346
- return refZone.top !== refZone.bottom ? zoneToXc(refZone) : toXC(refZone.left, refZone.top);
32359
+ if (rowIndexes.length === 0) {
32360
+ const top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
32361
+ const bottom = table.totalsRowCount
32362
+ ? tableZone.bottom - table.totalsRowCount
32363
+ : tableZone.bottom;
32364
+ rowIndexes.push(top, bottom);
32365
+ }
32366
+ if (colIndexes.length === 0) {
32367
+ colIndexes.push(tableZone.left, tableZone.right);
32368
+ }
32369
+ const refZone = {
32370
+ top: Math.min(...rowIndexes),
32371
+ left: Math.min(...colIndexes),
32372
+ bottom: Math.max(...rowIndexes),
32373
+ right: Math.max(...colIndexes),
32374
+ };
32375
+ return sheetPrefix + zoneToXc(refZone);
32376
+ }
32377
+ function removeBrackets(str) {
32378
+ return str.startsWith("[") && str.endsWith("]") ? str.slice(1, str.length - 1) : str;
32379
+ }
32380
+ function areKeywordsCompatible(keywords) {
32381
+ if (keywords.length < 2) {
32382
+ return true;
32383
+ }
32384
+ else if (keywords.length > 2) {
32385
+ return false;
32386
+ }
32387
+ else if (keywords.includes("#Data") && keywords.includes("#Totals")) {
32388
+ return true;
32389
+ }
32390
+ else if (keywords.includes("#Headers") && keywords.includes("#Data")) {
32391
+ return true;
32392
+ }
32393
+ return false;
32347
32394
  }
32348
32395
 
32349
32396
  // -------------------------------------
@@ -33896,7 +33943,7 @@
33896
33943
  * a breaking change is made in the way the state is handled, and an upgrade
33897
33944
  * function should be defined
33898
33945
  */
33899
- const CURRENT_VERSION = 14;
33946
+ const CURRENT_VERSION = 14.5;
33900
33947
  const INITIAL_SHEET_ID = "Sheet1";
33901
33948
  /**
33902
33949
  * This function tries to load anything that could look like a valid
@@ -42405,6 +42452,9 @@
42405
42452
  // Export
42406
42453
  // ---------------------------------------------------------------------------
42407
42454
  exportForExcel(data) {
42455
+ for (const sheet of data.sheets) {
42456
+ sheet.formulaSpillRanges = {};
42457
+ }
42408
42458
  for (const position of this.evaluator.getEvaluatedPositions()) {
42409
42459
  const evaluatedCell = this.evaluator.getEvaluatedCell(position);
42410
42460
  const xc = toXC(position.col, position.row);
@@ -42416,8 +42466,9 @@
42416
42466
  const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
42417
42467
  const formulaCell = this.getCorrespondingFormulaCell(position);
42418
42468
  if (formulaCell) {
42469
+ const cell = this.getters.getCell(position);
42419
42470
  isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
42420
- isFormula = isExported;
42471
+ isFormula = isExported && cell?.content === formulaCell.content;
42421
42472
  if (!isExported) {
42422
42473
  // If the cell contains a non-exported formula and that is evaluates to
42423
42474
  // nothing* ,we don't export it.
@@ -42441,6 +42492,14 @@
42441
42492
  content = !isExported ? newContent : exportedCellData.content;
42442
42493
  }
42443
42494
  exportedSheetData.cells[xc] = { ...exportedCellData, value, isFormula, content, format };
42495
+ const spillCells = this.getSpreadPositionsOf(position);
42496
+ spillCells.push(position);
42497
+ const spillZones = spillCells.map((cell) => positionToZone(cell));
42498
+ const spillZone = union(...spillZones);
42499
+ const spillZoneXc = this.getters.getRangeString(this.getters.getRangeFromZone(position.sheetId, spillZone), position.sheetId);
42500
+ if (spillZoneXc && spillZoneXc !== "#REF") {
42501
+ exportedSheetData.formulaSpillRanges[xc] = spillZoneXc;
42502
+ }
42444
42503
  }
42445
42504
  }
42446
42505
  /**
@@ -43641,7 +43700,7 @@
43641
43700
  getRule(cell, cells) {
43642
43701
  const rules = autofillRulesRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
43643
43702
  const rule = rules.find((rule) => rule.condition(cell, cells));
43644
- return rule && rule.generateRule(cell, cells);
43703
+ return rule && this.direction && rule.generateRule(cell, cells, this.direction);
43645
43704
  }
43646
43705
  /**
43647
43706
  * Create the generator to be able to autofill the next cells.
@@ -51311,7 +51370,8 @@
51311
51370
  ? this.getters.getSheetViewVisibleCols()
51312
51371
  : this.getters.getSheetViewVisibleRows();
51313
51372
  const startIndex = visibleHeaders.findIndex((header) => referenceHeaderIndex >= header);
51314
- const endIndex = visibleHeaders.findIndex((header) => targetHeaderIndex <= header);
51373
+ let endIndex = visibleHeaders.findIndex((header) => targetHeaderIndex <= header);
51374
+ endIndex = endIndex === -1 ? visibleHeaders.length : endIndex;
51315
51375
  const relevantIndexes = visibleHeaders.slice(startIndex, endIndex);
51316
51376
  let offset = 0;
51317
51377
  for (const i of relevantIndexes) {
@@ -56110,7 +56170,7 @@
56110
56170
  `;
56111
56171
  }
56112
56172
 
56113
- function addFormula(cell) {
56173
+ function addFormula(cell, formulaSpillRange) {
56114
56174
  const formula = cell.content;
56115
56175
  if (!formula) {
56116
56176
  return { attrs: [], node: escapeXml `` };
@@ -56119,10 +56179,17 @@
56119
56179
  if (type === undefined) {
56120
56180
  return { attrs: [], node: escapeXml `` };
56121
56181
  }
56122
- const attrs = [["t", type]];
56182
+ const attrs = [
56183
+ ["cm", "1"],
56184
+ ["t", type],
56185
+ ];
56123
56186
  const XlsxFormula = adaptFormulaToExcel(formula);
56124
56187
  const exportedValue = adaptFormulaValueToExcel(cell.value);
56125
- const node = escapeXml /*xml*/ `<f>${XlsxFormula}</f><v>${exportedValue}</v>`;
56188
+ // We treat all formulas as array formulas (a simple formula
56189
+ // is an array formula that spills on only one cell) to avoid
56190
+ // trying to detect spilling sub-formulas which is not a trivial task.
56191
+ let node;
56192
+ node = escapeXml /*xml*/ `<f t="array" ref="${formulaSpillRange}">${XlsxFormula}</f><v>${exportedValue}</v>`;
56126
56193
  return { attrs, node };
56127
56194
  }
56128
56195
  function addContent(content, sharedStrings, forceString = false) {
@@ -56934,7 +57001,7 @@
56934
57001
  let cellNode = escapeXml ``;
56935
57002
  // Either formula or static value inside the cell
56936
57003
  if (cell.isFormula) {
56937
- const res = addFormula(cell);
57004
+ const res = addFormula(cell, sheet.formulaSpillRanges[xc] ?? xc);
56938
57005
  if (!res) {
56939
57006
  continue;
56940
57007
  }
@@ -57191,6 +57258,30 @@
57191
57258
  `;
57192
57259
  files.push(createXMLFile(parseXML(sheetXml), `xl/worksheets/sheet${sheetIndex}.xml`, "sheet"));
57193
57260
  }
57261
+ const sheetMetadataXml = escapeXml /*xml*/ `
57262
+ <metadata xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:xda="http://schemas.microsoft.com/office/spreadsheetml/2017/dynamicarray">
57263
+ <metadataTypes count="1">
57264
+ <metadataType name="XLDAPR" minSupportedVersion="120000" copy="1" pasteAll="1"
57265
+ pasteValues="1" merge="1" splitFirst="1" rowColShift="1" clearFormats="1"
57266
+ clearComments="1" assign="1" coerce="1" cellMeta="1" />
57267
+ </metadataTypes>
57268
+ <futureMetadata name="XLDAPR" count="1">
57269
+ <bk>
57270
+ <extLst>
57271
+ <ext uri="{${ARRAY_FORMULA_URI}}">
57272
+ <xda:dynamicArrayProperties fDynamic="1" fCollapsed="0" />
57273
+ </ext>
57274
+ </extLst>
57275
+ </bk>
57276
+ </futureMetadata>
57277
+ <cellMetadata count="1">
57278
+ <bk>
57279
+ <rc t="1" v="0" />
57280
+ </bk>
57281
+ </cellMetadata>
57282
+ </metadata>
57283
+ `;
57284
+ files.push(createXMLFile(parseXML(sheetMetadataXml), "xl/metadata.xml", "metadata"));
57194
57285
  addRelsToFile(construct.relsFiles, "xl/_rels/workbook.xml.rels", {
57195
57286
  type: XLSX_RELATION_TYPE.sharedStrings,
57196
57287
  target: "sharedStrings.xml",
@@ -57199,6 +57290,10 @@
57199
57290
  type: XLSX_RELATION_TYPE.styles,
57200
57291
  target: "styles.xml",
57201
57292
  });
57293
+ addRelsToFile(construct.relsFiles, "xl/_rels/workbook.xml.rels", {
57294
+ type: XLSX_RELATION_TYPE.metadata,
57295
+ target: "metadata.xml",
57296
+ });
57202
57297
  return files;
57203
57298
  }
57204
57299
  /**
@@ -57973,9 +58068,9 @@
57973
58068
  exports.tokenize = tokenize;
57974
58069
 
57975
58070
 
57976
- __info__.version = "17.0.50";
57977
- __info__.date = "2025-02-14T08:37:34.289Z";
57978
- __info__.hash = "cc1037d";
58071
+ __info__.version = "17.0.52";
58072
+ __info__.date = "2025-03-07T10:30:04.783Z";
58073
+ __info__.hash = "c19cd9a";
57979
58074
 
57980
58075
 
57981
58076
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);