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