@odoo/o-spreadsheet 17.2.0-alpha.6 → 17.2.0

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.
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
5
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.2.0-alpha.6
7
- * @date 2024-02-28T12:28:44.114Z
8
- * @hash 318a04e
6
+ * @version 17.2.0
7
+ * @date 2024-03-20T08:12:44.398Z
8
+ * @hash 1869c24
9
9
  */
10
10
 
11
11
  'use strict';
@@ -191,8 +191,8 @@ const DEFAULT_GAUGE_LOWER_COLOR = "#cc0000";
191
191
  const DEFAULT_GAUGE_MIDDLE_COLOR = "#f1c232";
192
192
  const DEFAULT_GAUGE_UPPER_COLOR = "#6aa84f";
193
193
  const DEFAULT_SCORECARD_BASELINE_MODE = "difference";
194
- const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#00A04A";
195
- const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#DC6965";
194
+ const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#6aa84f";
195
+ const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#E06666";
196
196
  const LINE_FILL_TRANSPARENCY = 0.4;
197
197
  // session
198
198
  const DEBOUNCE_TIME = 200;
@@ -642,21 +642,6 @@ function isConsecutive(iterable) {
642
642
  }
643
643
  return true;
644
644
  }
645
- class JetSet extends Set {
646
- addMany(iterable) {
647
- for (const element of iterable) {
648
- super.add(element);
649
- }
650
- return this;
651
- }
652
- deleteMany(iterable) {
653
- let wasDeleted = false;
654
- for (const element of iterable) {
655
- wasDeleted ||= super.delete(element);
656
- }
657
- return wasDeleted;
658
- }
659
- }
660
645
  /**
661
646
  * Creates a version of the function that's memoized on the value of its first
662
647
  * argument, if any.
@@ -2146,7 +2131,6 @@ const LAYERS = {
2146
2131
  Background: 0,
2147
2132
  Highlights: 1,
2148
2133
  Clipboard: 2,
2149
- Search: 3,
2150
2134
  Chart: 4,
2151
2135
  Autofill: 5,
2152
2136
  Selection: 6,
@@ -4103,14 +4087,6 @@ function positions(zone) {
4103
4087
  }
4104
4088
  return positions;
4105
4089
  }
4106
- function forEachPositionsInZone(zone, callback) {
4107
- const { left, right, top, bottom } = zone;
4108
- for (let col = left; col <= right; col++) {
4109
- for (let row = top; row <= bottom; row++) {
4110
- callback(col, row);
4111
- }
4112
- }
4113
- }
4114
4090
  /**
4115
4091
  * This function returns a zone with coordinates modified according to the change
4116
4092
  * applied to the zone. It may be possible to change the zone by resizing or moving
@@ -6318,6 +6294,11 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
6318
6294
  this.pasteTableCell(sheetId, tableCell, position, clipboardOptions);
6319
6295
  }
6320
6296
  }
6297
+ if (tableCells.length === 1) {
6298
+ for (let c = 0; c < tableCells[0].length; c++) {
6299
+ this.dispatch("AUTOFILL_TABLE_COLUMN", { col: col + c, row, sheetId });
6300
+ }
6301
+ }
6321
6302
  }
6322
6303
  pasteTableCell(sheetId, tableCell, position, options) {
6323
6304
  if (tableCell.table && !options?.pasteOption) {
@@ -7444,6 +7425,7 @@ const TableTerms = {
7444
7425
  firstColumn: _t("First column"),
7445
7426
  lastColumn: _t("Last column"),
7446
7427
  bandedColumns: _t("Banded columns"),
7428
+ automaticAutofill: _t("Automatically autofill formulas"),
7447
7429
  totalRow: _t("Total row"),
7448
7430
  },
7449
7431
  Tooltips: {
@@ -8190,11 +8172,18 @@ function drawHighlight(renderingContext, highlight, rect) {
8190
8172
  }
8191
8173
  const color = highlight.color || HIGHLIGHT_COLOR;
8192
8174
  const { ctx } = renderingContext;
8193
- ctx.lineWidth = 2;
8194
- ctx.strokeStyle = color;
8195
- /** + 0.5 offset to have sharp lines. See comment in {@link RendererPlugin#drawBorders} for more details */
8196
- ctx.strokeRect(x + 0.5, y + 0.5, width, height);
8197
- ctx.globalCompositeOperation = "source-over";
8175
+ if (!highlight.noBorder) {
8176
+ ctx.strokeStyle = color;
8177
+ if (highlight.thinLine) {
8178
+ ctx.lineWidth = 1;
8179
+ ctx.strokeRect(x, y, width, height);
8180
+ }
8181
+ else {
8182
+ ctx.lineWidth = 2;
8183
+ /** + 0.5 offset to have sharp lines. See comment in {@link RendererPlugin#drawBorder} for more details */
8184
+ ctx.strokeRect(x + 0.5, y + 0.5, width, height);
8185
+ }
8186
+ }
8198
8187
  if (!highlight.noFill) {
8199
8188
  ctx.fillStyle = setColorAlpha(color, highlight.fillAlpha ?? 0.12);
8200
8189
  ctx.fillRect(x, y, width, height);
@@ -8568,6 +8557,7 @@ class ComposerStore extends SpreadsheetStore {
8568
8557
  else if (cell.link) {
8569
8558
  content = markdownLink(content, cell.link.url);
8570
8559
  }
8560
+ this.addHeadersForSpreadingFormula(content);
8571
8561
  this.model.dispatch("UPDATE_CELL", {
8572
8562
  sheetId: this.sheetId,
8573
8563
  col,
@@ -8583,6 +8573,7 @@ class ComposerStore extends SpreadsheetStore {
8583
8573
  row,
8584
8574
  });
8585
8575
  }
8576
+ this.model.dispatch("AUTOFILL_TABLE_COLUMN", { col, row, sheetId: this.sheetId });
8586
8577
  this.setContent("");
8587
8578
  }
8588
8579
  }
@@ -8785,6 +8776,38 @@ class ComposerStore extends SpreadsheetStore {
8785
8776
  }
8786
8777
  this.colorIndexByRange = colorsToKeep;
8787
8778
  }
8779
+ /** Add headers at the end of the sheet so the formula in the composer has enough space to spread */
8780
+ addHeadersForSpreadingFormula(content) {
8781
+ if (!content.startsWith("=")) {
8782
+ return;
8783
+ }
8784
+ const evaluated = this.getters.evaluateFormula(this.sheetId, content);
8785
+ if (!isMatrix(evaluated)) {
8786
+ return;
8787
+ }
8788
+ const numberOfRows = this.getters.getNumberRows(this.sheetId);
8789
+ const numberOfCols = this.getters.getNumberCols(this.sheetId);
8790
+ const missingRows = this.row + evaluated[0].length - numberOfRows;
8791
+ const missingCols = this.col + evaluated.length - numberOfCols;
8792
+ if (missingCols > 0) {
8793
+ this.model.dispatch("ADD_COLUMNS_ROWS", {
8794
+ sheetId: this.sheetId,
8795
+ dimension: "COL",
8796
+ base: numberOfCols - 1,
8797
+ position: "after",
8798
+ quantity: missingCols + 20,
8799
+ });
8800
+ }
8801
+ if (missingRows > 0) {
8802
+ this.model.dispatch("ADD_COLUMNS_ROWS", {
8803
+ sheetId: this.sheetId,
8804
+ dimension: "ROW",
8805
+ base: numberOfRows - 1,
8806
+ position: "after",
8807
+ quantity: missingRows + 50,
8808
+ });
8809
+ }
8810
+ }
8788
8811
  /**
8789
8812
  * Highlight all ranges that can be found in the composer content.
8790
8813
  */
@@ -10009,6 +10032,9 @@ function makeArg(str, description) {
10009
10032
  result.default = true;
10010
10033
  result.defaultValue = defaultValue;
10011
10034
  }
10035
+ if (types.some((t) => t.startsWith("RANGE"))) {
10036
+ result.acceptMatrix = true;
10037
+ }
10012
10038
  return result;
10013
10039
  }
10014
10040
  /**
@@ -17418,10 +17444,11 @@ const DEFAULT_IS_SORTED = true;
17418
17444
  const DEFAULT_MATCH_MODE = 0;
17419
17445
  const DEFAULT_SEARCH_MODE = 1;
17420
17446
  const DEFAULT_ABSOLUTE_RELATIVE_MODE = 1;
17421
- function assertAvailable(variable, searchKey) {
17422
- if (variable === undefined) {
17423
- throw new NotAvailableError(_t("Did not find value '%s' in [[FUNCTION_NAME]] evaluation.", toString(searchKey)));
17424
- }
17447
+ function valueNotAvailable(searchKey) {
17448
+ return {
17449
+ value: CellErrorType.NotAvailable,
17450
+ message: _t("Did not find value '%s' in [[FUNCTION_NAME]] evaluation.", toString(searchKey)),
17451
+ };
17425
17452
  }
17426
17453
  // -----------------------------------------------------------------------------
17427
17454
  // ADDRESS
@@ -17518,7 +17545,9 @@ const HLOOKUP = {
17518
17545
  ? dichotomicSearch(range, searchKey, "nextSmaller", "asc", range.length, getValueFromRange)
17519
17546
  : linearSearch(range, searchKey, "wildcard", range.length, getValueFromRange);
17520
17547
  const col = range[colIndex];
17521
- assertAvailable(col, searchKey?.value);
17548
+ if (col === undefined) {
17549
+ return valueNotAvailable(searchKey);
17550
+ }
17522
17551
  return col[_index - 1];
17523
17552
  },
17524
17553
  isExported: true,
@@ -17637,11 +17666,11 @@ const LOOKUP = {
17637
17666
  : (range, index) => range[index][0].value;
17638
17667
  const rangeLength = verticalSearch ? nbRow : nbCol;
17639
17668
  const index = dichotomicSearch(searchArray, searchKey, "nextSmaller", "asc", rangeLength, getElement);
17640
- if (index === -1)
17641
- assertAvailable(undefined, searchKey?.value);
17642
- verticalSearch
17643
- ? assertAvailable(searchArray[0][index], searchKey?.value)
17644
- : assertAvailable(searchArray[index][nbRow - 1], searchKey?.value);
17669
+ if (index === -1 ||
17670
+ (verticalSearch && searchArray[0][index] === undefined) ||
17671
+ (!verticalSearch && searchArray[index][nbRow - 1] === undefined)) {
17672
+ return valueNotAvailable(searchKey);
17673
+ }
17645
17674
  if (resultRange === undefined) {
17646
17675
  return verticalSearch ? searchArray[nbCol - 1][index] : searchArray[index][nbRow - 1];
17647
17676
  }
@@ -17691,7 +17720,10 @@ const MATCH = {
17691
17720
  index = dichotomicSearch(range, searchKey, "nextGreater", "desc", rangeLen, getElement);
17692
17721
  break;
17693
17722
  }
17694
- assertAvailable(nbCol === 1 ? range[0][index] : range[index], searchKey);
17723
+ if ((nbCol === 1 && range[0][index] === undefined) ||
17724
+ (nbCol !== 1 && range[index] === undefined)) {
17725
+ return valueNotAvailable(searchKey);
17726
+ }
17695
17727
  return index + 1;
17696
17728
  },
17697
17729
  isExported: true,
@@ -17750,7 +17782,9 @@ const VLOOKUP = {
17750
17782
  ? dichotomicSearch(range, searchKey, "nextSmaller", "asc", range[0].length, getValueFromRange)
17751
17783
  : linearSearch(range, searchKey, "wildcard", range[0].length, getValueFromRange);
17752
17784
  const value = range[_index - 1][rowIndex];
17753
- assertAvailable(value, searchKey);
17785
+ if (value === undefined) {
17786
+ return valueNotAvailable(searchKey);
17787
+ }
17754
17788
  return value;
17755
17789
  },
17756
17790
  isExported: true,
@@ -17810,7 +17844,9 @@ const XLOOKUP = {
17810
17844
  ? returnRange.map((col) => [col[index]])
17811
17845
  : [returnRange[index]];
17812
17846
  }
17813
- assertAvailable(defaultValue, searchKey);
17847
+ if (defaultValue === undefined) {
17848
+ return valueNotAvailable(searchKey);
17849
+ }
17814
17850
  return [[defaultValue]];
17815
17851
  },
17816
17852
  isExported: true,
@@ -18569,16 +18605,31 @@ class FunctionRegistry extends Registry {
18569
18605
  }
18570
18606
  const descr = addMetaInfoFromArg(addDescr);
18571
18607
  validateArguments(descr.args);
18572
- this.mapping[name] = addErrorHandling(addResultHandling(descr.compute), name);
18608
+ this.mapping[name] = addErrorHandling(addResultHandling(addInputHandling(descr)), name);
18573
18609
  super.add(name, descr);
18574
18610
  return this;
18575
18611
  }
18576
18612
  }
18613
+ function addInputHandling(descr) {
18614
+ function computeWithInputHandling(...args) {
18615
+ for (let i = 0; i < args.length; i++) {
18616
+ const argDefinition = descr.args[descr.getArgToFocus(i + 1) - 1];
18617
+ const arg = args[i];
18618
+ if (isMatrix(arg) && !argDefinition.acceptMatrix) {
18619
+ if (arg.length !== 1 || arg[0].length !== 1) {
18620
+ throw new EvaluationError(_t("Function [[FUNCTION_NAME]] expects the parameter '%s' to be a single value or a single cell reference, not a range.", argDefinition.name));
18621
+ }
18622
+ args[i] = arg[0][0];
18623
+ }
18624
+ }
18625
+ return descr.compute.apply(this, args);
18626
+ }
18627
+ return computeWithInputHandling;
18628
+ }
18577
18629
  function addErrorHandling(compute, functionName) {
18578
18630
  return function (...args) {
18579
18631
  try {
18580
- const computeFormula = compute.bind(this);
18581
- return computeFormula(...args);
18632
+ return compute.apply(this, args);
18582
18633
  }
18583
18634
  catch (e) {
18584
18635
  return handleError(e, functionName);
@@ -18612,8 +18663,7 @@ function hasStringMessage(obj) {
18612
18663
  }
18613
18664
  function addResultHandling(compute) {
18614
18665
  return function (...args) {
18615
- const computeResult = compute.bind(this);
18616
- const result = computeResult(...args);
18666
+ const result = compute.apply(this, args);
18617
18667
  if (!isMatrix(result)) {
18618
18668
  if (typeof result === "object" && result !== null && "value" in result) {
18619
18669
  return result;
@@ -18828,8 +18878,10 @@ function getGroup(cell, cells, filter) {
18828
18878
  if (x === cell) {
18829
18879
  found = true;
18830
18880
  }
18831
- const cellValue = evaluateLiteral(x?.content, { locale: DEFAULT_LOCALE });
18832
- if (filter(cellValue)) {
18881
+ const cellValue = x?.isFormula
18882
+ ? undefined
18883
+ : evaluateLiteral(x?.content, { locale: DEFAULT_LOCALE });
18884
+ if (cellValue && filter(cellValue)) {
18833
18885
  group.push(cellValue);
18834
18886
  }
18835
18887
  else {
@@ -19478,9 +19530,15 @@ function getChartDatasetValues(getters, dataSets) {
19478
19530
  : (label = `${ChartTerms.Series} ${parseInt(dsIndex) + 1}`);
19479
19531
  }
19480
19532
  else {
19481
- label = label = `${ChartTerms.Series} ${parseInt(dsIndex) + 1}`;
19533
+ label = `${ChartTerms.Series} ${parseInt(dsIndex) + 1}`;
19482
19534
  }
19483
19535
  let data = ds.dataRange ? getData(getters, ds) : [];
19536
+ if (data.every((e) => typeof e === "string" && !isEvaluationError(e))) {
19537
+ // In this case, we want a chart based on the string occurrences count
19538
+ // This will be done by associating each string with a value of 1 and
19539
+ // the using the classical aggregation method to sum the values.
19540
+ data.fill(1);
19541
+ }
19484
19542
  datasetValues.push({ data, label });
19485
19543
  }
19486
19544
  return datasetValues;
@@ -19579,7 +19637,7 @@ class BarChart extends AbstractChart {
19579
19637
  dataSets: context.range ? context.range : [],
19580
19638
  dataSetsHaveTitle: false,
19581
19639
  stacked: false,
19582
- aggregated: false,
19640
+ aggregated: context.aggregated ?? false,
19583
19641
  legendPosition: "top",
19584
19642
  title: context.title || "",
19585
19643
  type: "bar",
@@ -19595,6 +19653,7 @@ class BarChart extends AbstractChart {
19595
19653
  auxiliaryRange: this.labelRange
19596
19654
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
19597
19655
  : undefined,
19656
+ aggregated: this.aggregated,
19598
19657
  };
19599
19658
  }
19600
19659
  copyForSheetId(sheetId) {
@@ -20344,7 +20403,7 @@ class LineChart extends AbstractChart {
20344
20403
  verticalAxisPosition: "left",
20345
20404
  labelRange: context.auxiliaryRange || undefined,
20346
20405
  stacked: false,
20347
- aggregated: false,
20406
+ aggregated: context.aggregated ?? false,
20348
20407
  cumulative: false,
20349
20408
  };
20350
20409
  }
@@ -20377,6 +20436,7 @@ class LineChart extends AbstractChart {
20377
20436
  auxiliaryRange: this.labelRange
20378
20437
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
20379
20438
  : undefined,
20439
+ aggregated: this.aggregated,
20380
20440
  };
20381
20441
  }
20382
20442
  updateRanges(applyChange) {
@@ -20451,7 +20511,7 @@ class PieChart extends AbstractChart {
20451
20511
  title: context.title || "",
20452
20512
  type: "pie",
20453
20513
  labelRange: context.auxiliaryRange || undefined,
20454
- aggregated: false,
20514
+ aggregated: context.aggregated ?? false,
20455
20515
  };
20456
20516
  }
20457
20517
  getDefinition() {
@@ -20465,6 +20525,7 @@ class PieChart extends AbstractChart {
20465
20525
  auxiliaryRange: this.labelRange
20466
20526
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
20467
20527
  : undefined,
20528
+ aggregated: this.aggregated,
20468
20529
  };
20469
20530
  }
20470
20531
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
@@ -20656,7 +20717,7 @@ class ScatterChart extends AbstractChart {
20656
20717
  type: "scatter",
20657
20718
  verticalAxisPosition: "left",
20658
20719
  labelRange: context.auxiliaryRange || undefined,
20659
- aggregated: false,
20720
+ aggregated: context.aggregated ?? false,
20660
20721
  };
20661
20722
  }
20662
20723
  getDefinition() {
@@ -20686,6 +20747,7 @@ class ScatterChart extends AbstractChart {
20686
20747
  auxiliaryRange: this.labelRange
20687
20748
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
20688
20749
  : undefined,
20750
+ aggregated: this.aggregated,
20689
20751
  };
20690
20752
  }
20691
20753
  updateRanges(applyChange) {
@@ -22353,6 +22415,50 @@ function isCtrlKey(ev) {
22353
22415
  return isMacOS() ? ev.metaKey : ev.ctrlKey;
22354
22416
  }
22355
22417
 
22418
+ /**
22419
+ * Repeatedly calls a callback function with a time delay between calls.
22420
+ */
22421
+ function useInterval(callback, delay) {
22422
+ let intervalId;
22423
+ const { setInterval, clearInterval } = window;
22424
+ owl.useEffect(() => {
22425
+ intervalId = setInterval(callback, delay);
22426
+ return () => clearInterval(intervalId);
22427
+ }, () => [delay]);
22428
+ return {
22429
+ pause: () => {
22430
+ clearInterval(intervalId);
22431
+ intervalId = undefined;
22432
+ },
22433
+ resume: () => {
22434
+ if (intervalId === undefined) {
22435
+ intervalId = setInterval(callback, delay);
22436
+ }
22437
+ },
22438
+ };
22439
+ }
22440
+ /**
22441
+ * Calls a callback function with a time delay
22442
+ */
22443
+ function useTimeOut() {
22444
+ let timeOutId;
22445
+ function clear() {
22446
+ if (timeOutId !== undefined) {
22447
+ clearTimeout(timeOutId);
22448
+ timeOutId = undefined;
22449
+ }
22450
+ }
22451
+ function schedule(callback, delay) {
22452
+ clear();
22453
+ timeOutId = setTimeout(callback, delay);
22454
+ }
22455
+ owl.onWillUnmount(clear);
22456
+ return {
22457
+ clear,
22458
+ schedule,
22459
+ };
22460
+ }
22461
+
22356
22462
  //------------------------------------------------------------------------------
22357
22463
  // Context Menu Component
22358
22464
  //------------------------------------------------------------------------------
@@ -22403,6 +22509,7 @@ css /* scss */ `
22403
22509
  }
22404
22510
  }
22405
22511
  `;
22512
+ const TIMEOUT_DELAY = 250;
22406
22513
  class Menu extends owl.Component {
22407
22514
  static template = "o-spreadsheet-Menu";
22408
22515
  static props = {
@@ -22413,6 +22520,7 @@ class Menu extends owl.Component {
22413
22520
  onClose: Function,
22414
22521
  onMenuClicked: { type: Function, optional: true },
22415
22522
  menuId: { type: String, optional: true },
22523
+ onMouseOver: { type: Function, optional: true },
22416
22524
  };
22417
22525
  static components = { Menu, Popover };
22418
22526
  static defaultProps = {
@@ -22423,10 +22531,12 @@ class Menu extends owl.Component {
22423
22531
  position: null,
22424
22532
  scrollOffset: 0,
22425
22533
  menuItems: [],
22534
+ isHoveringChild: false,
22426
22535
  });
22427
22536
  menuRef = owl.useRef("menu");
22428
22537
  hoveredMenu = undefined;
22429
22538
  position = useAbsoluteBoundingRect(this.menuRef);
22539
+ openingTimeOut = useTimeOut();
22430
22540
  setup() {
22431
22541
  owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
22432
22542
  owl.useExternalListener(window, "contextmenu", this.onExternalClick, { capture: true });
@@ -22526,6 +22636,9 @@ class Menu extends owl.Component {
22526
22636
  }
22527
22637
  return false;
22528
22638
  }
22639
+ isActive(menuItem) {
22640
+ return (this.subMenu?.isHoveringChild || false) && this.isParentMenu(this.subMenu, menuItem);
22641
+ }
22529
22642
  onScroll(ev) {
22530
22643
  this.subMenu.scrollOffset = ev.target.scrollTop;
22531
22644
  }
@@ -22533,10 +22646,10 @@ class Menu extends owl.Component {
22533
22646
  * If the given menu is not disabled, open it's submenu at the
22534
22647
  * correct position according to available surrounding space.
22535
22648
  */
22536
- openSubMenu(menu, ev) {
22537
- const parentMenuEl = ev.currentTarget;
22538
- if (!parentMenuEl)
22649
+ openSubMenu(menu, parentMenuEl) {
22650
+ if (!parentMenuEl) {
22539
22651
  return;
22652
+ }
22540
22653
  const y = parentMenuEl.getBoundingClientRect().top;
22541
22654
  this.subMenu.position = {
22542
22655
  x: this.position.x + this.props.depth * MENU_WIDTH,
@@ -22550,32 +22663,50 @@ class Menu extends owl.Component {
22550
22663
  return subMenu.parentMenu?.id === menuItem.id;
22551
22664
  }
22552
22665
  closeSubMenu() {
22666
+ if (this.subMenu.isHoveringChild) {
22667
+ return;
22668
+ }
22553
22669
  this.subMenu.isOpen = false;
22554
22670
  this.subMenu.parentMenu = undefined;
22555
22671
  }
22556
22672
  onClickMenu(menu, ev) {
22557
22673
  if (this.isEnabled(menu)) {
22558
22674
  if (this.isRoot(menu)) {
22559
- this.openSubMenu(menu, ev);
22675
+ this.openSubMenu(menu, ev.currentTarget);
22560
22676
  }
22561
22677
  else {
22562
22678
  this.activateMenu(menu);
22563
22679
  }
22564
22680
  }
22565
22681
  }
22566
- onMouseEnter(menu, ev) {
22567
- this.hoveredMenu = menu;
22568
- menu.onStartHover?.(this.env);
22682
+ onMouseOver(menu, ev) {
22569
22683
  if (this.isEnabled(menu)) {
22570
- if (this.isRoot(menu)) {
22571
- this.openSubMenu(menu, ev);
22684
+ if (this.isParentMenu(this.subMenu, menu)) {
22685
+ this.openingTimeOut.clear();
22686
+ return;
22572
22687
  }
22573
- else {
22574
- this.closeSubMenu();
22688
+ const currentTarget = ev.currentTarget;
22689
+ if (this.isRoot(menu)) {
22690
+ this.openingTimeOut.schedule(() => {
22691
+ this.openSubMenu(menu, currentTarget);
22692
+ }, TIMEOUT_DELAY);
22575
22693
  }
22576
22694
  }
22577
22695
  }
22696
+ onMouseOverMainMenu() {
22697
+ this.props.onMouseOver?.();
22698
+ this.subMenu.isHoveringChild = false;
22699
+ }
22700
+ onMouseOverChildMenu() {
22701
+ this.subMenu.isHoveringChild = true;
22702
+ this.openingTimeOut.clear();
22703
+ }
22704
+ onMouseEnter(menu, ev) {
22705
+ this.hoveredMenu = menu;
22706
+ menu.onStartHover?.(this.env);
22707
+ }
22578
22708
  onMouseLeave(menu) {
22709
+ this.openingTimeOut.schedule(this.closeSubMenu.bind(this), TIMEOUT_DELAY);
22579
22710
  this.hoveredMenu = undefined;
22580
22711
  menu.onStopHover?.(this.env);
22581
22712
  }
@@ -22827,7 +22958,8 @@ function getChartTypes() {
22827
22958
  */
22828
22959
  function getSmartChartDefinition(zone, getters) {
22829
22960
  let dataSetZone = zone;
22830
- if (zone.left !== zone.right) {
22961
+ const singleColumn = zoneToDimension(zone).numberOfCols === 1;
22962
+ if (!singleColumn) {
22831
22963
  dataSetZone = { ...zone, left: zone.left + 1 };
22832
22964
  }
22833
22965
  const dataSets = [zoneToXc(dataSetZone)];
@@ -22861,7 +22993,7 @@ function getSmartChartDefinition(zone, getters) {
22861
22993
  }
22862
22994
  }
22863
22995
  let labelRangeXc;
22864
- if (zone.left !== zone.right) {
22996
+ if (!singleColumn) {
22865
22997
  labelRangeXc = zoneToXc({
22866
22998
  ...zone,
22867
22999
  right: zone.left,
@@ -22885,6 +23017,19 @@ function getSmartChartDefinition(zone, getters) {
22885
23017
  legendPosition: newLegendPos,
22886
23018
  };
22887
23019
  }
23020
+ const _dataSets = createDataSets(getters, dataSets, sheetId, dataSetsHaveTitle);
23021
+ if (singleColumn &&
23022
+ getData(getters, _dataSets[0]).every((e) => typeof e === "string" && !isEvaluationError(e))) {
23023
+ return {
23024
+ title: "",
23025
+ dataSets,
23026
+ aggregated: true,
23027
+ labelRange: dataSets[0],
23028
+ type: "pie",
23029
+ legendPosition: "top",
23030
+ dataSetsHaveTitle: false,
23031
+ };
23032
+ }
22888
23033
  return {
22889
23034
  title,
22890
23035
  dataSets,
@@ -22912,6 +23057,7 @@ const DEFAULT_TABLE_CONFIG = {
22912
23057
  numberOfHeaders: 1,
22913
23058
  bandedRows: true,
22914
23059
  bandedColumns: false,
23060
+ automaticAutofill: true,
22915
23061
  styleId: "TableStyleMedium2",
22916
23062
  };
22917
23063
  function generateColorSet(name, highlightColor) {
@@ -26395,7 +26541,7 @@ class LineBarPieConfigPanel extends owl.Component {
26395
26541
  {
26396
26542
  name: "aggregated",
26397
26543
  label: _t("Aggregate"),
26398
- value: this.props.definition.aggregated,
26544
+ value: this.props.definition.aggregated ?? false,
26399
26545
  onChange: this.onUpdateAggregated.bind(this),
26400
26546
  },
26401
26547
  ];
@@ -27566,15 +27712,15 @@ css /* scss */ `
27566
27712
  // -----------------------------------------------------------------------------
27567
27713
  // We need here the svg of the icons that we need to convert to images for the renderer
27568
27714
  // -----------------------------------------------------------------------------
27569
- const ARROW_DOWN = '<svg class="o-cf-icon arrow-down" width="10" height="10" focusable="false" viewBox="0 0 448 512"><path fill="#DC6965" d="M413.1 222.5l22.2 22.2c9.4 9.4 9.4 24.6 0 33.9L241 473c-9.4 9.4-24.6 9.4-33.9 0L12.7 278.6c-9.4-9.4-9.4-24.6 0-33.9l22.2-22.2c9.5-9.5 25-9.3 34.3.4L184 343.4V56c0-13.3 10.7-24 24-24h32c13.3 0 24 10.7 24 24v287.4l114.8-120.5c9.3-9.8 24.8-10 34.3-.4z"></path></svg>';
27570
- const ARROW_UP = '<svg class="o-cf-icon arrow-up" width="10" height="10" focusable="false" viewBox="0 0 448 512"><path fill="#00A04A" d="M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L69.2 289.1c-9.3 9.8-24.8 10-34.3.4z"></path></svg>';
27715
+ const ARROW_DOWN = '<svg class="o-cf-icon arrow-down" width="10" height="10" focusable="false" viewBox="0 0 448 512"><path fill="#E06666" d="M413.1 222.5l22.2 22.2c9.4 9.4 9.4 24.6 0 33.9L241 473c-9.4 9.4-24.6 9.4-33.9 0L12.7 278.6c-9.4-9.4-9.4-24.6 0-33.9l22.2-22.2c9.5-9.5 25-9.3 34.3.4L184 343.4V56c0-13.3 10.7-24 24-24h32c13.3 0 24 10.7 24 24v287.4l114.8-120.5c9.3-9.8 24.8-10 34.3-.4z"></path></svg>';
27716
+ const ARROW_UP = '<svg class="o-cf-icon arrow-up" width="10" height="10" focusable="false" viewBox="0 0 448 512"><path fill="#6AA84F" d="M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L69.2 289.1c-9.3 9.8-24.8 10-34.3.4z"></path></svg>';
27571
27717
  const ARROW_RIGHT = '<svg class="o-cf-icon arrow-right" width="10" height="10" focusable="false" viewBox="0 0 448 512"><path fill="#F0AD4E" d="M190.5 66.9l22.2-22.2c9.4-9.4 24.6-9.4 33.9 0L441 239c9.4 9.4 9.4 24.6 0 33.9L246.6 467.3c-9.4 9.4-24.6 9.4-33.9 0l-22.2-22.2c-9.5-9.5-9.3-25 .4-34.3L311.4 296H24c-13.3 0-24-10.7-24-24v-32c0-13.3 10.7-24 24-24h287.4L190.9 101.2c-9.8-9.3-10-24.8-.4-34.3z"></path></svg>';
27572
- const SMILE = '<svg class="o-cf-icon smile" width="10" height="10" focusable="false" viewBox="0 0 496 512"><path fill="#00A04A" d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm4 72.6c-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.7-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8-10.1-8.4-25.3-7.1-33.8 3.1z"></path></svg>';
27718
+ const SMILE = '<svg class="o-cf-icon smile" width="10" height="10" focusable="false" viewBox="0 0 496 512"><path fill="#6AA84F" d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm4 72.6c-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.7-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8-10.1-8.4-25.3-7.1-33.8 3.1z"></path></svg>';
27573
27719
  const MEH = '<svg class="o-cf-icon meh" width="10" height="10" focusable="false" viewBox="0 0 496 512"><path fill="#F0AD4E" d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm8 144H160c-13.2 0-24 10.8-24 24s10.8 24 24 24h176c13.2 0 24-10.8 24-24s-10.8-24-24-24z"></path></svg>';
27574
- const FROWN = '<svg class="o-cf-icon frown" width="10" height="10" focusable="false" viewBox="0 0 496 512"><path fill="#DC6965" d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm-80 128c-40.2 0-78 17.7-103.8 48.6-8.5 10.2-7.1 25.3 3.1 33.8 10.2 8.4 25.3 7.1 33.8-3.1 16.6-19.9 41-31.4 66.9-31.4s50.3 11.4 66.9 31.4c8.1 9.7 23.1 11.9 33.8 3.1 10.2-8.5 11.5-23.6 3.1-33.8C326 321.7 288.2 304 248 304z"></path></svg>';
27575
- const GREEN_DOT = '<svg class="o-cf-icon green-dot" width="10" height="10" focusable="false" viewBox="0 0 512 512"><path fill="#00A04A" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z"></path></svg>';
27720
+ const FROWN = '<svg class="o-cf-icon frown" width="10" height="10" focusable="false" viewBox="0 0 496 512"><path fill="#E06666" d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm-80 128c-40.2 0-78 17.7-103.8 48.6-8.5 10.2-7.1 25.3 3.1 33.8 10.2 8.4 25.3 7.1 33.8-3.1 16.6-19.9 41-31.4 66.9-31.4s50.3 11.4 66.9 31.4c8.1 9.7 23.1 11.9 33.8 3.1 10.2-8.5 11.5-23.6 3.1-33.8C326 321.7 288.2 304 248 304z"></path></svg>';
27721
+ const GREEN_DOT = '<svg class="o-cf-icon green-dot" width="10" height="10" focusable="false" viewBox="0 0 512 512"><path fill="#6AA84F" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z"></path></svg>';
27576
27722
  const YELLOW_DOT = '<svg class="o-cf-icon yellow-dot" width="10" height="10" focusable="false" viewBox="0 0 512 512"><path fill="#F0AD4E" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z"></path></svg>';
27577
- const RED_DOT = '<svg class="o-cf-icon red-dot" width="10" height="10" focusable="false" viewBox="0 0 512 512"><path fill="#DC6965" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z"></path></svg>';
27723
+ const RED_DOT = '<svg class="o-cf-icon red-dot" width="10" height="10" focusable="false" viewBox="0 0 512 512"><path fill="#E06666" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z"></path></svg>';
27578
27724
  function loadIconImage(svg) {
27579
27725
  /** We have to add xmlns, as it's not added by owl in the canvas */
27580
27726
  svg = `<svg xmlns="http://www.w3.org/2000/svg" ${svg.slice(4)}`;
@@ -29440,8 +29586,7 @@ class DataValidationPanel extends owl.Component {
29440
29586
  }
29441
29587
  }
29442
29588
 
29443
- const BORDER_COLOR = "#8B008B";
29444
- const BACKGROUND_COLOR = "#8B008B33";
29589
+ const FIND_AND_REPLACE_HIGHLIGHT_COLOR = "#8B008B";
29445
29590
  var Direction;
29446
29591
  (function (Direction) {
29447
29592
  Direction[Direction["previous"] = -1] = "previous";
@@ -29472,14 +29617,14 @@ class FindAndReplaceStore extends SpreadsheetStore {
29472
29617
  super(get);
29473
29618
  this.initialShowFormulaState = this.model.getters.shouldShowFormulas();
29474
29619
  this.searchOptions.searchFormulas = this.initialShowFormulaState;
29620
+ const highlightStore = get(HighlightStore);
29621
+ highlightStore.register(owl.toRaw(this));
29475
29622
  this.onDispose(() => {
29476
29623
  this.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.initialShowFormulaState });
29477
29624
  this.updateSearchContent.stopDebounce();
29625
+ highlightStore.unRegister(owl.toRaw(this));
29478
29626
  });
29479
29627
  }
29480
- get renderingLayers() {
29481
- return ["Search"];
29482
- }
29483
29628
  get searchMatches() {
29484
29629
  switch (this.searchOptions.searchScope) {
29485
29630
  case "allSheets":
@@ -29706,8 +29851,8 @@ class FindAndReplaceStore extends SpreadsheetStore {
29706
29851
  // ---------------------------------------------------------------------------
29707
29852
  // Grid rendering
29708
29853
  // ---------------------------------------------------------------------------
29709
- draw(renderingContext) {
29710
- const { ctx } = renderingContext;
29854
+ get highlights() {
29855
+ const highlights = [];
29711
29856
  const sheetId = this.getters.getActiveSheetId();
29712
29857
  for (const [index, match] of this.searchMatches.entries()) {
29713
29858
  if (match.sheetId !== sheetId) {
@@ -29715,26 +29860,31 @@ class FindAndReplaceStore extends SpreadsheetStore {
29715
29860
  }
29716
29861
  const zone = positionToZone(match);
29717
29862
  const zoneWithMerge = this.getters.expandZone(sheetId, zone);
29718
- const { x, y, width, height } = this.getters.getVisibleRect(zoneWithMerge);
29863
+ const { width, height } = this.getters.getVisibleRect(zoneWithMerge);
29719
29864
  if (width > 0 && height > 0) {
29720
- ctx.fillStyle = BACKGROUND_COLOR;
29721
- ctx.fillRect(x, y, width, height);
29722
- if (index === this.selectedMatchIndex) {
29723
- ctx.strokeStyle = BORDER_COLOR;
29724
- ctx.strokeRect(x, y, width, height);
29725
- }
29865
+ highlights.push({
29866
+ sheetId,
29867
+ zone: zoneWithMerge,
29868
+ color: FIND_AND_REPLACE_HIGHLIGHT_COLOR,
29869
+ noBorder: index !== this.selectedMatchIndex,
29870
+ thinLine: true,
29871
+ fillAlpha: 0.2,
29872
+ });
29726
29873
  }
29727
29874
  }
29728
- // TODO: use Highlights helpers/stores when the Highlight PR is merged
29729
29875
  if (this.searchOptions.searchScope === "specificRange") {
29730
29876
  const range = this.searchOptions.specificRange;
29731
- if (!range || range.sheetId !== sheetId) {
29732
- return;
29877
+ if (range && range.sheetId === sheetId) {
29878
+ highlights.push({
29879
+ sheetId,
29880
+ zone: range.zone,
29881
+ color: FIND_AND_REPLACE_HIGHLIGHT_COLOR,
29882
+ noFill: true,
29883
+ thinLine: true,
29884
+ });
29733
29885
  }
29734
- const { x, y, width, height } = this.getters.getVisibleRect(range.zone);
29735
- ctx.strokeStyle = BORDER_COLOR;
29736
- ctx.strokeRect(x, y, width, height);
29737
29886
  }
29887
+ return highlights;
29738
29888
  }
29739
29889
  }
29740
29890
 
@@ -30148,6 +30298,12 @@ const TABLE_ELEMENTS_BY_PRIORITY = [
30148
30298
  "headerRow",
30149
30299
  "totalRow",
30150
30300
  ];
30301
+ /** Return the content zone of the table, ie. the table zone without the headers */
30302
+ function getTableContentZone(tableZone, tableConfig) {
30303
+ const numberOfHeaders = tableConfig.numberOfHeaders;
30304
+ const contentZone = { ...tableZone, top: tableZone.top + numberOfHeaders };
30305
+ return contentZone.top <= contentZone.bottom ? contentZone : undefined;
30306
+ }
30151
30307
  function getComputedTableStyle(tableConfig, numberOfCols, numberOfRows) {
30152
30308
  return {
30153
30309
  borders: getAllTableBorders(tableConfig, numberOfCols, numberOfRows),
@@ -30599,14 +30755,6 @@ class TablePanel extends owl.Component {
30599
30755
  tableXc: this.env.model.getters.getRangeString(this.props.table.range, sheetId),
30600
30756
  filtersEnabledIfPossible: this.props.table.config.hasFilters,
30601
30757
  });
30602
- owl.onWillUpdateProps((nextProps) => {
30603
- if (this.props.table.id !== nextProps.table.id) {
30604
- const sheetId = this.env.model.getters.getActiveSheetId();
30605
- this.state.tableXc = this.env.model.getters.getRangeString(nextProps.table.range, sheetId);
30606
- this.state.tableZoneErrors = [];
30607
- this.state.filtersEnabledIfPossible = nextProps.table.config.hasFilters;
30608
- }
30609
- });
30610
30758
  }
30611
30759
  updateHasFilters(hasFilters) {
30612
30760
  this.state.filtersEnabledIfPossible = hasFilters;
@@ -30755,7 +30903,11 @@ sidePanelRegistry.add("TableSidePanel", {
30755
30903
  if (!table) {
30756
30904
  return { isOpen: false };
30757
30905
  }
30758
- return { isOpen: true, props: { table } };
30906
+ return {
30907
+ isOpen: true,
30908
+ props: { table },
30909
+ key: table.id,
30910
+ };
30759
30911
  },
30760
30912
  });
30761
30913
 
@@ -32470,19 +32622,21 @@ class GridCellIcon extends owl.Component {
32470
32622
  slots: Object,
32471
32623
  };
32472
32624
  get iconStyle() {
32473
- const x = this.getIconHorizontalPosition();
32474
- const y = this.getIconVerticalPosition();
32625
+ const cellPosition = this.props.cellPosition;
32626
+ const merge = this.env.model.getters.getMerge(cellPosition);
32627
+ const zone = merge || positionToZone(cellPosition);
32628
+ const rect = this.env.model.getters.getVisibleRectWithoutHeaders(zone);
32629
+ const x = this.getIconHorizontalPosition(rect, cellPosition);
32630
+ const y = this.getIconVerticalPosition(rect, cellPosition);
32475
32631
  return cssPropertiesToCss({
32476
32632
  top: `${y + (this.props.offset?.y || 0)}px`,
32477
32633
  left: `${x + (this.props.offset?.x || 0)}px`,
32478
32634
  });
32479
32635
  }
32480
- getIconVerticalPosition() {
32481
- const { sheetId, row } = this.props.cellPosition;
32482
- const merge = this.env.model.getters.getMerge(this.props.cellPosition);
32483
- const start = this.env.model.getters.getRowDimensionsInViewport(sheetId, row).start;
32484
- const end = this.env.model.getters.getRowDimensionsInViewport(sheetId, merge ? merge.bottom : row).end;
32485
- const cell = this.env.model.getters.getCell(this.props.cellPosition);
32636
+ getIconVerticalPosition(rect, cellPosition) {
32637
+ const start = rect.y;
32638
+ const end = rect.y + rect.height;
32639
+ const cell = this.env.model.getters.getCell(cellPosition);
32486
32640
  const align = this.props.verticalAlign || cell?.style?.verticalAlign || DEFAULT_VERTICAL_ALIGN;
32487
32641
  switch (align) {
32488
32642
  case "bottom":
@@ -32494,13 +32648,11 @@ class GridCellIcon extends owl.Component {
32494
32648
  return end - GRID_ICON_EDGE_LENGTH - centeringOffset;
32495
32649
  }
32496
32650
  }
32497
- getIconHorizontalPosition() {
32498
- const { sheetId, col } = this.props.cellPosition;
32499
- const merge = this.env.model.getters.getMerge(this.props.cellPosition);
32500
- const start = this.env.model.getters.getColDimensionsInViewport(sheetId, col).start;
32501
- const end = this.env.model.getters.getColDimensionsInViewport(sheetId, merge ? merge.right : col).end;
32502
- const cell = this.env.model.getters.getCell(this.props.cellPosition);
32503
- const evaluatedCell = this.env.model.getters.getEvaluatedCell(this.props.cellPosition);
32651
+ getIconHorizontalPosition(rect, cellPosition) {
32652
+ const start = rect.x;
32653
+ const end = rect.x + rect.width;
32654
+ const cell = this.env.model.getters.getCell(cellPosition);
32655
+ const evaluatedCell = this.env.model.getters.getEvaluatedCell(cellPosition);
32504
32656
  const align = this.props.horizontalAlign || cell?.style?.align || evaluatedCell.defaultAlign;
32505
32657
  switch (align) {
32506
32658
  case "right":
@@ -32605,6 +32757,8 @@ css /* scss */ `
32605
32757
  height: ${CHECKBOX_WIDTH}px;
32606
32758
  accent-color: #808080;
32607
32759
  margin: ${MARGIN}px;
32760
+ /** required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
32761
+ position: absolute;
32608
32762
  }
32609
32763
  `;
32610
32764
  class DataValidationCheckbox extends owl.Component {
@@ -32623,7 +32777,7 @@ class DataValidationCheckbox extends owl.Component {
32623
32777
  }
32624
32778
  get isDisabled() {
32625
32779
  const cell = this.env.model.getters.getCell(this.props.cellPosition);
32626
- return !!cell?.isFormula;
32780
+ return this.env.model.getters.isReadonly() || !!cell?.isFormula;
32627
32781
  }
32628
32782
  }
32629
32783
 
@@ -32663,12 +32817,17 @@ class DataValidationOverlay extends owl.Component {
32663
32817
  static props = {};
32664
32818
  static components = { GridCellIcon, DataValidationCheckbox, DataValidationListIcon };
32665
32819
  get checkBoxCellPositions() {
32666
- return this.env.model.getters.getDataValidationCheckBoxCellPositions();
32820
+ return this.env.model.getters
32821
+ .getVisibleCellPositions()
32822
+ .filter(this.env.model.getters.isCellValidCheckbox);
32667
32823
  }
32668
32824
  get listIconsCellPositions() {
32669
- return this.env.model.getters.isReadonly()
32670
- ? []
32671
- : this.env.model.getters.getDataValidationListCellsPositions();
32825
+ if (this.env.model.getters.isReadonly()) {
32826
+ return [];
32827
+ }
32828
+ return this.env.model.getters
32829
+ .getVisibleCellPositions()
32830
+ .filter(this.env.model.getters.cellHasListDataValidationIcon);
32672
32831
  }
32673
32832
  }
32674
32833
 
@@ -32713,7 +32872,7 @@ function dragFigureForMove({ x: mouseX, y: mouseY }, { x: mouseInitialX, y: mous
32713
32872
  const newY = clip(initialFigure.y + deltaY, minY, maxY - initialFigure.height - scrollY);
32714
32873
  return { ...initialFigure, x: newX, y: newY };
32715
32874
  }
32716
- function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, keepRatio, minFigSize) {
32875
+ function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, keepRatio, minFigSize, { scrollX, scrollY }) {
32717
32876
  let { x, y, width, height } = initialFigure;
32718
32877
  if (keepRatio && dirX != 0 && dirY != 0) {
32719
32878
  const deltaX = Math.min(dirX * (mouseInitialX - mouseX), initialFigure.width - minFigSize);
@@ -32740,14 +32899,14 @@ function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }
32740
32899
  y = initialFigure.y - deltaY;
32741
32900
  }
32742
32901
  }
32743
- // Restrict resizing if x or y reaches header boundaries
32744
- if (x < 0) {
32745
- width += x;
32746
- x = 0;
32902
+ // Adjusts figure dimensions to ensure it remains within header boundaries and viewport during resizing.
32903
+ if (x + scrollX <= 0) {
32904
+ width = width + x + scrollX;
32905
+ x = -scrollX;
32747
32906
  }
32748
- if (y < 0) {
32749
- height += y;
32750
- y = 0;
32907
+ if (y + scrollY <= 0) {
32908
+ height = height + y + scrollY;
32909
+ y = -scrollY;
32751
32910
  }
32752
32911
  return { ...initialFigure, x, y, width, height };
32753
32912
  }
@@ -33167,7 +33326,7 @@ class FiguresContainer extends owl.Component {
33167
33326
  const minFigSize = figureRegistry.get(figure.tag).minFigSize || MIN_FIG_SIZE;
33168
33327
  const onMouseMove = (ev) => {
33169
33328
  const currentMousePosition = { x: ev.clientX, y: ev.clientY };
33170
- const draggedFigure = dragFigureForResize(initialFig, dirX, dirY, currentMousePosition, initialMousePosition, keepRatio, minFigSize);
33329
+ const draggedFigure = dragFigureForResize(initialFig, dirX, dirY, currentMousePosition, initialMousePosition, keepRatio, minFigSize, this.env.model.getters.getActiveSheetScrollInfo());
33171
33330
  const otherFigures = this.getOtherFigures(figure.id);
33172
33331
  const snapResult = snapForResize(this.env.model.getters, dirX, dirY, draggedFigure, otherFigures);
33173
33332
  this.dnd.draggedFigure = snapResult.snappedFigure;
@@ -33343,29 +33502,6 @@ class GridAddRowsFooter extends owl.Component {
33343
33502
  }
33344
33503
  }
33345
33504
 
33346
- /**
33347
- * Repeatedly calls a callback function with a time delay between calls.
33348
- */
33349
- function useInterval(callback, delay) {
33350
- let intervalId;
33351
- const { setInterval, clearInterval } = window;
33352
- owl.useEffect(() => {
33353
- intervalId = setInterval(callback, delay);
33354
- return () => clearInterval(intervalId);
33355
- }, () => [delay]);
33356
- return {
33357
- pause: () => {
33358
- clearInterval(intervalId);
33359
- intervalId = undefined;
33360
- },
33361
- resume: () => {
33362
- if (intervalId === undefined) {
33363
- intervalId = setInterval(callback, delay);
33364
- }
33365
- },
33366
- };
33367
- }
33368
-
33369
33505
  const CURSOR_SVG = /*xml*/ `
33370
33506
  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="16"><path d="M6.5.4c1.3-.8 2.9-.1 3.8 1.4l2.9 5.1c.2.4.9 1.6-.4 2.3l-1.6.9 1.8 3.1c.2.4.1 1-.2 1.2l-1.6 1c-.3.1-.9 0-1.1-.4l-1.8-3.1-1.6 1c-.6.4-1.7 0-2.2-.8L0 4.3"/><path fill="#fff" d="M9.1 2a1.4 1.1 60 0 0-1.7-.6L5.5 2.5l.9 1.6-1 .6-.9-1.6-.6.4 1.8 3.1-1.3.7-1.8-3.1-1 .6 3.8 6.6 6.8-3.98M3.9 8.8 10.82 5l.795 1.4-6.81 3.96"/></svg>
33371
33507
  `;
@@ -35250,10 +35386,17 @@ class SidePanelStore extends SpreadsheetStore {
35250
35386
  get panelProps() {
35251
35387
  const state = this.computeState(this.componentTag, this.initialPanelProps);
35252
35388
  if (state.isOpen) {
35253
- return state.props;
35389
+ return state.props ?? {};
35254
35390
  }
35255
35391
  return {};
35256
35392
  }
35393
+ get panelKey() {
35394
+ const state = this.computeState(this.componentTag, this.initialPanelProps);
35395
+ if (state.isOpen) {
35396
+ return state.key;
35397
+ }
35398
+ return undefined;
35399
+ }
35257
35400
  open(componentTag, panelProps = {}) {
35258
35401
  const state = this.computeState(componentTag, panelProps);
35259
35402
  if (state.isOpen === false) {
@@ -35263,7 +35406,7 @@ class SidePanelStore extends SpreadsheetStore {
35263
35406
  this.initialPanelProps?.onCloseSidePanel?.();
35264
35407
  }
35265
35408
  this.componentTag = componentTag;
35266
- this.initialPanelProps = state.props;
35409
+ this.initialPanelProps = state.props ?? {};
35267
35410
  }
35268
35411
  toggle(componentTag, panelProps) {
35269
35412
  if (this.isOpen && componentTag === this.componentTag) {
@@ -40740,10 +40883,8 @@ function compileTokens(tokens) {
40740
40883
  "ref", // a function to access a certain dependency at a given index
40741
40884
  "range", // same as above, but guarantee that the result is in the form of a range
40742
40885
  "ctx", code.toString());
40743
- functionCache[cacheKey] = {
40744
- // @ts-ignore
40745
- execute: baseFunction,
40746
- };
40886
+ // @ts-ignore
40887
+ functionCache[cacheKey] = baseFunction;
40747
40888
  /**
40748
40889
  * This function compile the function arguments. It is mostly straightforward,
40749
40890
  * except that there is a non trivial transformation in one situation:
@@ -40850,7 +40991,7 @@ function compileTokens(tokens) {
40850
40991
  }
40851
40992
  }
40852
40993
  const compiledFormula = {
40853
- execute: functionCache[cacheKey].execute,
40994
+ execute: functionCache[cacheKey],
40854
40995
  dependencies,
40855
40996
  constantValues,
40856
40997
  tokens,
@@ -42335,6 +42476,15 @@ class FigurePlugin extends CorePlugin {
42335
42476
  return "Success" /* CommandResult.Success */;
42336
42477
  }
42337
42478
  }
42479
+ beforeHandle(cmd) {
42480
+ switch (cmd.type) {
42481
+ case "DELETE_SHEET":
42482
+ this.getters.getFigures(cmd.sheetId).forEach((figure) => {
42483
+ this.dispatch("DELETE_FIGURE", { id: figure.id, sheetId: cmd.sheetId });
42484
+ });
42485
+ break;
42486
+ }
42487
+ }
42338
42488
  handle(cmd) {
42339
42489
  switch (cmd.type) {
42340
42490
  case "CREATE_SHEET":
@@ -43821,7 +43971,6 @@ class SheetPlugin extends CorePlugin {
43821
43971
  "getNumberHeaders",
43822
43972
  "getGridLinesVisibility",
43823
43973
  "getNextSheetName",
43824
- "isEmpty",
43825
43974
  "getSheetSize",
43826
43975
  "getSheetZone",
43827
43976
  "getPaneDivisions",
@@ -44193,14 +44342,6 @@ class SheetPlugin extends CorePlugin {
44193
44342
  // ---------------------------------------------------------------------------
44194
44343
  // Row/Col manipulation
44195
44344
  // ---------------------------------------------------------------------------
44196
- /**
44197
- * Check if a zone only contains empty cells
44198
- */
44199
- isEmpty(sheetId, zone) {
44200
- return positions(zone)
44201
- .map(({ col, row }) => this.getCell({ sheetId, col, row }))
44202
- .every((cell) => !cell || cell.content === "");
44203
- }
44204
44345
  getCommandZones(cmd) {
44205
44346
  const zones = [];
44206
44347
  if ("zone" in cmd) {
@@ -45004,13 +45145,15 @@ class TablePlugin extends CorePlugin {
45004
45145
  if (zone.left !== zone.right) {
45005
45146
  throw new Error("Can only define a filter on a single column");
45006
45147
  }
45007
- const filteredZone = { ...zone, top: zone.top + config.numberOfHeaders };
45008
- const filteredRange = this.getters.getRangeFromZone(range.sheetId, filteredZone);
45148
+ const contentZone = getTableContentZone(zone, config);
45149
+ const filteredRange = contentZone
45150
+ ? this.getters.getRangeFromZone(range.sheetId, contentZone)
45151
+ : undefined;
45009
45152
  return {
45010
45153
  id,
45011
45154
  rangeWithHeaders: range,
45012
45155
  col: zone.left,
45013
- filteredRange: filteredZone.top > filteredZone.bottom ? undefined : filteredRange,
45156
+ filteredRange,
45014
45157
  };
45015
45158
  }
45016
45159
  copyTableForSheet(sheetId, table) {
@@ -45619,7 +45762,11 @@ class CompilationParametersBuilder {
45619
45762
  });
45620
45763
  }
45621
45764
  getParameters() {
45622
- return [this.refFn.bind(this), this.range.bind(this), this.evalContext];
45765
+ return {
45766
+ referenceDenormalizer: this.refFn.bind(this),
45767
+ ensureRange: this.range.bind(this),
45768
+ evalContext: this.evalContext,
45769
+ };
45623
45770
  }
45624
45771
  /**
45625
45772
  * Returns the value of the cell(s) used in reference
@@ -45700,6 +45847,41 @@ class CompilationParametersBuilder {
45700
45847
  }
45701
45848
  }
45702
45849
 
45850
+ class PositionMap {
45851
+ map = {};
45852
+ set({ sheetId, col, row }, value) {
45853
+ const map = this.map;
45854
+ if (!map[sheetId]) {
45855
+ map[sheetId] = {};
45856
+ }
45857
+ if (!map[sheetId][col]) {
45858
+ map[sheetId][col] = {};
45859
+ }
45860
+ map[sheetId][col][row] = value;
45861
+ }
45862
+ get({ sheetId, col, row }) {
45863
+ return this.map[sheetId]?.[col]?.[row];
45864
+ }
45865
+ has({ sheetId, col, row }) {
45866
+ return this.map[sheetId]?.[col]?.[row] !== undefined;
45867
+ }
45868
+ delete({ sheetId, col, row }) {
45869
+ delete this.map[sheetId]?.[col]?.[row];
45870
+ }
45871
+ keys() {
45872
+ const map = this.map;
45873
+ const keys = [];
45874
+ for (const sheetId in map) {
45875
+ for (const col in map[sheetId]) {
45876
+ for (const row in map[sheetId][col]) {
45877
+ keys.push({ sheetId, col: parseInt(col), row: parseInt(row) });
45878
+ }
45879
+ }
45880
+ }
45881
+ return keys;
45882
+ }
45883
+ }
45884
+
45703
45885
  function quickselect(arr, k, left, right, compare) {
45704
45886
  quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
45705
45887
  }
@@ -46426,26 +46608,26 @@ class ZoneRBush extends RBush {
46426
46608
  * It uses an R-Tree data structure to efficiently find dependent cells.
46427
46609
  */
46428
46610
  class FormulaDependencyGraph {
46429
- encoder;
46430
- dependencies = new Map();
46611
+ createEmptyPositionSet;
46612
+ dependencies = new PositionMap();
46431
46613
  rTree;
46432
- constructor(encoder, data = []) {
46433
- this.encoder = encoder;
46614
+ constructor(createEmptyPositionSet, data = []) {
46615
+ this.createEmptyPositionSet = createEmptyPositionSet;
46434
46616
  this.rTree = new SpreadsheetRTree(data);
46435
46617
  }
46436
- removeAllDependencies(formulaPositionId) {
46437
- const ranges = this.dependencies.get(formulaPositionId);
46618
+ removeAllDependencies(formulaPosition) {
46619
+ const ranges = this.dependencies.get(formulaPosition);
46438
46620
  if (!ranges) {
46439
46621
  return;
46440
46622
  }
46441
46623
  for (const range of ranges) {
46442
46624
  this.rTree.remove(range);
46443
46625
  }
46444
- this.dependencies.delete(formulaPositionId);
46626
+ this.dependencies.delete(formulaPosition);
46445
46627
  }
46446
- addDependencies(formulaPositionId, dependencies) {
46628
+ addDependencies(formulaPosition, dependencies) {
46447
46629
  const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
46448
- data: formulaPositionId,
46630
+ data: formulaPosition,
46449
46631
  boundingBox: {
46450
46632
  zone,
46451
46633
  sheetId,
@@ -46454,12 +46636,12 @@ class FormulaDependencyGraph {
46454
46636
  for (const item of rTreeItems) {
46455
46637
  this.rTree.insert(item);
46456
46638
  }
46457
- const existingDependencies = this.dependencies.get(formulaPositionId);
46639
+ const existingDependencies = this.dependencies.get(formulaPosition);
46458
46640
  if (existingDependencies) {
46459
46641
  existingDependencies.push(...rTreeItems);
46460
46642
  }
46461
46643
  else {
46462
- this.dependencies.set(formulaPositionId, rTreeItems);
46644
+ this.dependencies.set(formulaPosition, rTreeItems);
46463
46645
  }
46464
46646
  }
46465
46647
  /**
@@ -46468,23 +46650,194 @@ class FormulaDependencyGraph {
46468
46650
  * This is called a topological ordering (excluding cycles)
46469
46651
  */
46470
46652
  getCellsDependingOn(ranges) {
46471
- const visited = new JetSet();
46653
+ const visited = this.createEmptyPositionSet();
46472
46654
  const queue = Array.from(ranges).reverse();
46473
46655
  while (queue.length > 0) {
46474
46656
  const range = queue.pop();
46475
- visited.addMany(this.encoder.encodeBoundingBox(range));
46476
- const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
46477
- for (const positionId of impactedPositionIds) {
46478
- if (!visited.has(positionId)) {
46479
- queue.push(this.encoder.decodeToBoundingBox(positionId));
46657
+ visited.addMany(positions(range.zone).map((position) => ({ sheetId: range.sheetId, ...position })));
46658
+ const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
46659
+ for (const position of impactedPositions) {
46660
+ if (!visited.has(position)) {
46661
+ queue.push({ sheetId: position.sheetId, zone: positionToZone(position) });
46480
46662
  }
46481
46663
  }
46482
46664
  }
46483
- visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
46665
+ visited.deleteMany(ranges.flatMap((r) => positions(r.zone).map((position) => ({ sheetId: r.sheetId, ...position }))));
46484
46666
  return visited;
46485
46667
  }
46486
46668
  }
46487
46669
 
46670
+ /**
46671
+ * Implements a fixed-sized grid or 2D matrix of bits.
46672
+ * based on https://github.com/zandaqo/structurae
46673
+ *
46674
+ * The grid is implemented as a 1D array of 32-bit integers, where each bit represents a cell in the grid.
46675
+ * It follows row-major order, with each row stored consecutively in 32-bit blocks.
46676
+ * Pads the number of columns to the next power of 2 to allow quick lookups with bitwise operations.
46677
+ *
46678
+ * Key terminology:
46679
+ * - bucket: Index of an item in the Uint32Array, a 32-bit integer.
46680
+ * - bitPosition: The position of a bit within the bucket 32-bit integer.
46681
+ */
46682
+ class BinaryGrid extends Uint32Array {
46683
+ columnOffset = 0;
46684
+ cols = 0;
46685
+ rows = 0;
46686
+ /**
46687
+ * Creates a binary grid of specified dimensions.
46688
+ */
46689
+ static create(rows, columns) {
46690
+ const columnOffset = log2Ceil(columns);
46691
+ const length = (rows << columnOffset) >> 5;
46692
+ const grid = new this(length + 1);
46693
+ grid.columnOffset = columnOffset;
46694
+ grid.cols = columns;
46695
+ grid.rows = rows;
46696
+ return grid;
46697
+ }
46698
+ /**
46699
+ * Returns the bit at given coordinates.
46700
+ */
46701
+ getValue(position) {
46702
+ const [bucket, bitPosition] = this.getCoordinates(position);
46703
+ return ((this[bucket] >> bitPosition) & 1);
46704
+ }
46705
+ /**
46706
+ * Sets the bit at given coordinates.
46707
+ */
46708
+ setValue(position, value) {
46709
+ const [bucket, bitPosition] = this.getCoordinates(position);
46710
+ const currentValue = (this[bucket] >> bitPosition) & 1;
46711
+ const hasBeenInserted = currentValue === 0 && value === 1;
46712
+ this[bucket] = (this[bucket] & ~(1 << bitPosition)) | (value << bitPosition);
46713
+ return hasBeenInserted;
46714
+ // Let's breakdown of the above line:
46715
+ // with an example with a 4-bit integer (instead of 32-bit).
46716
+ //
46717
+ // Let's say we want to set the bit at position 2 to 1 and the existing
46718
+ // bit sequence this[bucket] is 1001. The final bit sequence should be 1101.
46719
+ //
46720
+ // First, we clear the bit at position 2 by AND-ing this[bucket] with a
46721
+ // mask having all 1s except a 0 at the bit position (~ (1 << bitPosition)).
46722
+ // 1 << bitPosition is 0100 (shifting 0001 to the left by 2)
46723
+ // Inverting the bits with ~ gives the final mask ~(1 << bitPosition): 1011
46724
+ //
46725
+ // Then, we shift the value by the bit position (value << bitPosition: 0100)
46726
+ // and OR the result with the previous step's result:
46727
+ // (1001 & 1011) | 0100 = 1101
46728
+ }
46729
+ isEmpty() {
46730
+ return !this.some((bucket) => bucket !== 0);
46731
+ }
46732
+ fillAllPositions() {
46733
+ const thirtyTwoOnes = -1 >>> 0; // same as 2 ** 32 - 1, a 32-bit number with all bits set to 1
46734
+ this.fill(thirtyTwoOnes);
46735
+ }
46736
+ clear() {
46737
+ this.fill(0);
46738
+ }
46739
+ getCoordinates(position) {
46740
+ const { row, col } = position;
46741
+ const index = (row << this.columnOffset) + col;
46742
+ const bucket = index >> 5;
46743
+ return [bucket, index - (bucket << 5)];
46744
+ }
46745
+ }
46746
+ function log2Ceil(value) {
46747
+ // A faster version of Math.ceil(Math.log2(value)).
46748
+ if (value === 0) {
46749
+ return -Infinity;
46750
+ }
46751
+ else if (value < 0) {
46752
+ return NaN;
46753
+ }
46754
+ // --value handles the case where value is a power of 2
46755
+ return 32 - Math.clz32(--value);
46756
+ }
46757
+
46758
+ class PositionSet {
46759
+ sheets = {};
46760
+ /**
46761
+ * List of positions in the order they were inserted.
46762
+ */
46763
+ insertions = [];
46764
+ maxSize = 0;
46765
+ constructor(sheetSizes) {
46766
+ for (const sheetId in sheetSizes) {
46767
+ const cols = sheetSizes[sheetId].cols;
46768
+ const rows = sheetSizes[sheetId].rows;
46769
+ this.maxSize += cols * rows;
46770
+ this.sheets[sheetId] = BinaryGrid.create(rows, cols);
46771
+ }
46772
+ }
46773
+ add(position) {
46774
+ const hasBeenInserted = this.sheets[position.sheetId].setValue(position, 1);
46775
+ if (hasBeenInserted) {
46776
+ this.insertions.push(position);
46777
+ }
46778
+ }
46779
+ addMany(positions) {
46780
+ for (const position of positions) {
46781
+ this.add(position);
46782
+ }
46783
+ }
46784
+ delete(position) {
46785
+ this.sheets[position.sheetId].setValue(position, 0);
46786
+ }
46787
+ deleteMany(positions) {
46788
+ for (const position of positions) {
46789
+ this.delete(position);
46790
+ }
46791
+ }
46792
+ has(position) {
46793
+ return this.sheets[position.sheetId].getValue(position) === 1;
46794
+ }
46795
+ clear() {
46796
+ const insertions = this.insertions;
46797
+ this.insertions = [];
46798
+ for (const sheetId in this.sheets) {
46799
+ this.sheets[sheetId].clear();
46800
+ }
46801
+ return insertions;
46802
+ }
46803
+ isEmpty() {
46804
+ if (this.insertions.length === 0) {
46805
+ return true;
46806
+ }
46807
+ for (const sheetId in this.sheets) {
46808
+ if (!this.sheets[sheetId].isEmpty()) {
46809
+ return false;
46810
+ }
46811
+ }
46812
+ return true;
46813
+ }
46814
+ fillAllPositions() {
46815
+ this.insertions = new Array(this.maxSize);
46816
+ let index = 0;
46817
+ for (const sheetId in this.sheets) {
46818
+ const grid = this.sheets[sheetId];
46819
+ grid.fillAllPositions();
46820
+ for (let i = 0; i < grid.rows; i++) {
46821
+ for (let j = 0; j < grid.cols; j++) {
46822
+ this.insertions[index++] = { sheetId, row: i, col: j };
46823
+ }
46824
+ }
46825
+ }
46826
+ }
46827
+ /**
46828
+ * Iterate over the positions in the order of insertion.
46829
+ * Note that the same position may be yielded multiple times if the value was added
46830
+ * to the set then removed and then added again.
46831
+ */
46832
+ *[Symbol.iterator]() {
46833
+ for (const position of this.insertions) {
46834
+ if (this.sheets[position.sheetId].getValue(position) === 1) {
46835
+ yield position;
46836
+ }
46837
+ }
46838
+ }
46839
+ }
46840
+
46488
46841
  /**
46489
46842
  * Contains, for each cell, the array
46490
46843
  * formulas that could potentially spread on it
@@ -46498,6 +46851,7 @@ class FormulaDependencyGraph {
46498
46851
  *
46499
46852
  */
46500
46853
  class SpreadingRelation {
46854
+ createEmptyPositionSet;
46501
46855
  /**
46502
46856
  * Internal structure:
46503
46857
  * For something like
@@ -46526,39 +46880,42 @@ class SpreadingRelation {
46526
46880
  * - (B1) --> (B2, B3, B4) meaning B1 spreads on B2, B3 and B4
46527
46881
  *
46528
46882
  */
46529
- resultsToArrayFormulas = new Map();
46530
- arrayFormulasToResults = new Map();
46531
- getFormulaPositionsSpreadingOn(resultPositionId) {
46532
- return this.resultsToArrayFormulas.get(resultPositionId) || EMPTY_ARRAY;
46883
+ resultsToArrayFormulas = new PositionMap();
46884
+ arrayFormulasToResults = new PositionMap();
46885
+ constructor(createEmptyPositionSet) {
46886
+ this.createEmptyPositionSet = createEmptyPositionSet;
46887
+ }
46888
+ getFormulaPositionsSpreadingOn(resultPosition) {
46889
+ return this.resultsToArrayFormulas.get(resultPosition) || EMPTY_ARRAY;
46533
46890
  }
46534
- getArrayResultPositionIds(formulasPositionId) {
46535
- return this.arrayFormulasToResults.get(formulasPositionId) || EMPTY_ARRAY;
46891
+ getArrayResultPositions(formulasPosition) {
46892
+ return this.arrayFormulasToResults.get(formulasPosition) || EMPTY_ARRAY;
46536
46893
  }
46537
46894
  /**
46538
46895
  * Remove a node, also remove it from other nodes adjacency list
46539
46896
  */
46540
- removeNode(positionId) {
46541
- this.resultsToArrayFormulas.delete(positionId);
46542
- this.arrayFormulasToResults.delete(positionId);
46897
+ removeNode(position) {
46898
+ this.resultsToArrayFormulas.delete(position);
46899
+ this.arrayFormulasToResults.delete(position);
46543
46900
  }
46544
46901
  /**
46545
46902
  * Create a spreading relation between two cells
46546
46903
  */
46547
- addRelation({ arrayFormulaPositionId, resultPositionId, }) {
46548
- if (!this.resultsToArrayFormulas.has(resultPositionId)) {
46549
- this.resultsToArrayFormulas.set(resultPositionId, new Set());
46904
+ addRelation({ arrayFormulaPosition, resultPosition, }) {
46905
+ if (!this.resultsToArrayFormulas.has(resultPosition)) {
46906
+ this.resultsToArrayFormulas.set(resultPosition, this.createEmptyPositionSet());
46550
46907
  }
46551
- this.resultsToArrayFormulas.get(resultPositionId)?.add(arrayFormulaPositionId);
46552
- if (!this.arrayFormulasToResults.has(arrayFormulaPositionId)) {
46553
- this.arrayFormulasToResults.set(arrayFormulaPositionId, new Set());
46908
+ this.resultsToArrayFormulas.get(resultPosition)?.add(arrayFormulaPosition);
46909
+ if (!this.arrayFormulasToResults.has(arrayFormulaPosition)) {
46910
+ this.arrayFormulasToResults.set(arrayFormulaPosition, this.createEmptyPositionSet());
46554
46911
  }
46555
- this.arrayFormulasToResults.get(arrayFormulaPositionId)?.add(resultPositionId);
46912
+ this.arrayFormulasToResults.get(arrayFormulaPosition)?.add(resultPosition);
46556
46913
  }
46557
- hasArrayFormulaResult(positionId) {
46558
- return this.resultsToArrayFormulas.has(positionId);
46914
+ hasArrayFormulaResult(position) {
46915
+ return this.resultsToArrayFormulas.has(position);
46559
46916
  }
46560
- isArrayFormula(positionId) {
46561
- return this.arrayFormulasToResults.has(positionId);
46917
+ isArrayFormula(position) {
46918
+ return this.arrayFormulasToResults.has(position);
46562
46919
  }
46563
46920
  }
46564
46921
  const EMPTY_ARRAY = [];
@@ -46566,104 +46923,103 @@ const EMPTY_ARRAY = [];
46566
46923
  const MAX_ITERATION = 30;
46567
46924
  const ERROR_CYCLE_CELL = createEvaluatedCell(new CircularDependencyError());
46568
46925
  const EMPTY_CELL = createEvaluatedCell({ value: null });
46569
- const EVAL_CONTEXT_INDEX = 2;
46570
46926
  class Evaluator {
46571
46927
  context;
46572
46928
  getters;
46573
46929
  compilationParams;
46574
- encoder = new PositionBitsEncoder();
46575
- evaluatedCells = new Map();
46576
- formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
46577
- blockedArrayFormulas = new Set();
46578
- spreadingRelations = new SpreadingRelation();
46930
+ evaluatedCells = new PositionMap();
46931
+ formulaDependencies = lazy(new FormulaDependencyGraph(this.createEmptyPositionSet.bind(this)));
46932
+ blockedArrayFormulas = new PositionSet({});
46933
+ spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
46579
46934
  constructor(context, getters) {
46580
46935
  this.context = context;
46581
46936
  this.getters = getters;
46582
46937
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
46583
46938
  }
46584
46939
  getEvaluatedCell(position) {
46585
- return this.evaluatedCells.get(this.encoder.encode(position)) || EMPTY_CELL;
46940
+ return this.evaluatedCells.get(position) || EMPTY_CELL;
46586
46941
  }
46587
46942
  getSpreadPositionsOf(position) {
46588
- const positionId = this.encoder.encode(position);
46589
- if (!this.spreadingRelations.isArrayFormula(positionId)) {
46943
+ if (!this.spreadingRelations.isArrayFormula(position)) {
46590
46944
  return [];
46591
46945
  }
46592
- return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map((positionId) => this.encoder.decode(positionId));
46593
- }
46594
- getArrayFormulaSpreadingOn(position) {
46595
- const positionId = this.encoder.encode(position);
46596
- const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
46597
- return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
46946
+ return Array.from(this.spreadingRelations.getArrayResultPositions(position));
46598
46947
  }
46599
46948
  getEvaluatedPositions() {
46600
- return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
46949
+ return this.evaluatedCells.keys();
46601
46950
  }
46602
- getArrayFormulaSpreadingOnId(positionId) {
46603
- if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
46951
+ getArrayFormulaSpreadingOn(position) {
46952
+ if (!this.spreadingRelations.hasArrayFormulaResult(position)) {
46604
46953
  return undefined;
46605
46954
  }
46606
- const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
46607
- return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
46955
+ const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(position);
46956
+ return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
46608
46957
  }
46609
46958
  updateDependencies(position) {
46610
- const positionId = this.encoder.encode(position);
46611
- this.formulaDependencies().removeAllDependencies(positionId);
46612
- const dependencies = this.getDirectDependencies(positionId);
46613
- this.formulaDependencies().addDependencies(positionId, dependencies);
46959
+ this.formulaDependencies().removeAllDependencies(position);
46960
+ const dependencies = this.getDirectDependencies(position);
46961
+ this.formulaDependencies().addDependencies(position, dependencies);
46614
46962
  }
46615
46963
  addDependencies(position, dependencies) {
46616
- const positionId = this.encoder.encode(position);
46617
- this.formulaDependencies().addDependencies(positionId, dependencies);
46964
+ this.formulaDependencies().addDependencies(position, dependencies);
46618
46965
  }
46619
46966
  updateCompilationParameters() {
46620
46967
  // rebuild the compilation parameters (with a clean cache)
46621
46968
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
46622
- this.compilationParams[EVAL_CONTEXT_INDEX].updateDependencies =
46623
- this.updateDependencies.bind(this);
46624
- this.compilationParams[EVAL_CONTEXT_INDEX].addDependencies = this.addDependencies.bind(this);
46969
+ this.compilationParams.evalContext.updateDependencies = this.updateDependencies.bind(this);
46970
+ this.compilationParams.evalContext.addDependencies = this.addDependencies.bind(this);
46971
+ }
46972
+ createEmptyPositionSet() {
46973
+ const sheetSizes = {};
46974
+ for (const sheetId of this.getters.getSheetIds()) {
46975
+ sheetSizes[sheetId] = {
46976
+ rows: this.getters.getNumberRows(sheetId),
46977
+ cols: this.getters.getNumberCols(sheetId),
46978
+ };
46979
+ }
46980
+ return new PositionSet(sheetSizes);
46625
46981
  }
46626
46982
  evaluateCells(positions) {
46627
- const cells = positions.map((p) => this.encoder.encode(p));
46628
- const cellsToCompute = new JetSet(cells);
46629
- const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
46630
- cellsToCompute.addMany(this.getCellsDependingOn(cells));
46631
- cellsToCompute.addMany(arrayFormulasPositionIds);
46632
- cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositionIds));
46983
+ const cellsToCompute = this.createEmptyPositionSet();
46984
+ cellsToCompute.addMany(positions);
46985
+ const arrayFormulasPositions = this.getArrayFormulasImpactedByChangesOf(positions);
46986
+ cellsToCompute.addMany(this.getCellsDependingOn(positions));
46987
+ cellsToCompute.addMany(arrayFormulasPositions);
46988
+ cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
46633
46989
  this.evaluate(cellsToCompute);
46634
46990
  }
46635
- getArrayFormulasImpactedByChangesOf(positionIds) {
46636
- const impactedPositionIds = new JetSet();
46637
- for (const positionId of positionIds) {
46638
- const content = this.getCell(positionId)?.content;
46639
- const arrayFormulaPositionId = this.getArrayFormulaSpreadingOnId(positionId);
46640
- if (arrayFormulaPositionId !== undefined) {
46991
+ getArrayFormulasImpactedByChangesOf(positions) {
46992
+ const impactedPositions = this.createEmptyPositionSet();
46993
+ for (const position of positions) {
46994
+ const content = this.getters.getCell(position)?.content;
46995
+ const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
46996
+ if (arrayFormulaPosition !== undefined) {
46641
46997
  // take into account new collisions.
46642
- impactedPositionIds.add(arrayFormulaPositionId);
46998
+ impactedPositions.add(arrayFormulaPosition);
46643
46999
  }
46644
47000
  if (!content) {
46645
47001
  // The previous content could have blocked some array formulas
46646
- impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
47002
+ impactedPositions.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(position));
46647
47003
  }
46648
47004
  }
46649
- return impactedPositionIds;
47005
+ return impactedPositions;
46650
47006
  }
46651
47007
  buildDependencyGraph() {
46652
- this.blockedArrayFormulas = new Set();
46653
- this.spreadingRelations = new SpreadingRelation();
47008
+ this.blockedArrayFormulas = this.createEmptyPositionSet();
47009
+ this.spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
46654
47010
  this.formulaDependencies = lazy(() => {
46655
- const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
46656
- data: positionId,
47011
+ const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position).map((range) => ({
47012
+ data: position,
46657
47013
  boundingBox: {
46658
47014
  zone: range.zone,
46659
47015
  sheetId: range.sheetId,
46660
47016
  },
46661
47017
  })));
46662
- return new FormulaDependencyGraph(this.encoder, dependencies);
47018
+ return new FormulaDependencyGraph(this.createEmptyPositionSet.bind(this), dependencies);
46663
47019
  });
46664
47020
  }
46665
47021
  evaluateAllCells() {
46666
- this.evaluatedCells = new Map();
47022
+ this.evaluatedCells = new PositionMap();
46667
47023
  this.evaluate(this.getAllCells());
46668
47024
  }
46669
47025
  evaluateFormula(sheetId, formulaString) {
@@ -46680,57 +47036,50 @@ class Evaluator {
46680
47036
  return result.value;
46681
47037
  }
46682
47038
  getAllCells() {
46683
- const positionIds = new JetSet();
46684
- for (const sheetId of this.getters.getSheetIds()) {
46685
- const cellIds = this.getters.getCells(sheetId);
46686
- for (const cellId in cellIds) {
46687
- positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
46688
- }
46689
- }
46690
- return positionIds;
47039
+ const positions = this.createEmptyPositionSet();
47040
+ positions.fillAllPositions();
47041
+ return positions;
46691
47042
  }
46692
- getArrayFormulasBlockedByOrSpreadingOn(positionId) {
46693
- if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
47043
+ getArrayFormulasBlockedByOrSpreadingOn(position) {
47044
+ if (!this.spreadingRelations.hasArrayFormulaResult(position)) {
46694
47045
  return [];
46695
47046
  }
46696
- const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
46697
- const cells = new JetSet(arrayFormulas);
46698
- cells.addMany(this.getCellsDependingOn(arrayFormulas));
46699
- return cells;
47047
+ const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(position);
47048
+ const positions = this.createEmptyPositionSet();
47049
+ positions.addMany(arrayFormulas);
47050
+ positions.addMany(this.getCellsDependingOn(arrayFormulas));
47051
+ return positions;
46700
47052
  }
46701
- nextPositionsToUpdate = new JetSet();
47053
+ nextPositionsToUpdate = new PositionSet({});
46702
47054
  cellsBeingComputed = new Set();
46703
- evaluate(cells) {
47055
+ evaluate(positions) {
46704
47056
  this.cellsBeingComputed = new Set();
46705
- this.nextPositionsToUpdate = cells;
47057
+ this.nextPositionsToUpdate = positions;
46706
47058
  let currentIteration = 0;
46707
- while (this.nextPositionsToUpdate.size && currentIteration++ < MAX_ITERATION) {
47059
+ while (!this.nextPositionsToUpdate.isEmpty() && currentIteration++ < MAX_ITERATION) {
46708
47060
  this.updateCompilationParameters();
46709
- const positionIds = Array.from(this.nextPositionsToUpdate);
46710
- this.nextPositionsToUpdate.clear();
46711
- for (let i = 0; i < positionIds.length; ++i) {
46712
- const cell = positionIds[i];
46713
- this.evaluatedCells.delete(cell);
47061
+ const positions = this.nextPositionsToUpdate.clear();
47062
+ for (let i = 0; i < positions.length; ++i) {
47063
+ this.evaluatedCells.delete(positions[i]);
46714
47064
  }
46715
- for (let i = 0; i < positionIds.length; ++i) {
46716
- const cell = positionIds[i];
46717
- this.setEvaluatedCell(cell, this.computeCell(cell));
47065
+ for (let i = 0; i < positions.length; ++i) {
47066
+ const position = positions[i];
47067
+ const evaluatedCell = this.computeCell(position);
47068
+ if (evaluatedCell !== EMPTY_CELL) {
47069
+ this.evaluatedCells.set(position, evaluatedCell);
47070
+ }
46718
47071
  }
46719
47072
  }
46720
47073
  }
46721
- setEvaluatedCell(positionId, evaluatedCell) {
46722
- this.evaluatedCells.set(positionId, evaluatedCell);
46723
- }
46724
- computeCell(positionId) {
46725
- const evaluation = this.evaluatedCells.get(positionId);
47074
+ computeCell(position) {
47075
+ const evaluation = this.evaluatedCells.get(position);
46726
47076
  if (evaluation) {
46727
47077
  return evaluation; // already computed
46728
47078
  }
46729
- if (!this.blockedArrayFormulas.has(positionId)) {
46730
- this.invalidateSpreading(positionId);
47079
+ if (!this.blockedArrayFormulas.has(position)) {
47080
+ this.invalidateSpreading(position);
46731
47081
  }
46732
- const cellPosition = this.encoder.decode(positionId);
46733
- const cell = this.getters.getCell(cellPosition);
47082
+ const cell = this.getters.getCell(position);
46734
47083
  if (cell === undefined) {
46735
47084
  return EMPTY_CELL;
46736
47085
  }
@@ -46742,10 +47091,12 @@ class Evaluator {
46742
47091
  }
46743
47092
  this.cellsBeingComputed.add(cellId);
46744
47093
  return cell.isFormula
46745
- ? this.computeFormulaCell(cellPosition.sheetId, cell)
47094
+ ? this.computeFormulaCell(position.sheetId, cell)
46746
47095
  : evaluateLiteral(cell.content, localeFormat);
46747
47096
  }
46748
47097
  catch (e) {
47098
+ e.value = e?.value || CellErrorType.GenericError;
47099
+ e.message = e?.message || implementationErrorMessage;
46749
47100
  return createEvaluatedCell(e);
46750
47101
  }
46751
47102
  finally {
@@ -46753,10 +47104,9 @@ class Evaluator {
46753
47104
  }
46754
47105
  }
46755
47106
  computeAndSave(position) {
46756
- const positionId = this.encoder.encode(position);
46757
- const evaluatedCell = this.computeCell(positionId);
46758
- if (!this.evaluatedCells.has(positionId)) {
46759
- this.setEvaluatedCell(positionId, evaluatedCell);
47107
+ const evaluatedCell = this.computeCell(position);
47108
+ if (!this.evaluatedCells.has(position)) {
47109
+ this.evaluatedCells.set(position, evaluatedCell);
46760
47110
  }
46761
47111
  return evaluatedCell;
46762
47112
  }
@@ -46794,24 +47144,23 @@ class Evaluator {
46794
47144
  throw new EvaluationError(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
46795
47145
  }
46796
47146
  updateSpreadRelation({ sheetId, col, row, }) {
46797
- const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
47147
+ const arrayFormulaPosition = { sheetId, col, row };
46798
47148
  return (i, j) => {
46799
47149
  const position = { sheetId, col: i + col, row: j + row };
46800
- const resultPositionId = this.encoder.encode(position);
46801
- this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
47150
+ this.spreadingRelations.addRelation({ resultPosition: position, arrayFormulaPosition });
46802
47151
  };
46803
47152
  }
46804
- checkCollision({ sheetId, col, row }) {
46805
- const formulaPositionId = this.encoder.encode({ sheetId, col, row });
47153
+ checkCollision(formulaPosition) {
47154
+ const { sheetId, col, row } = formulaPosition;
46806
47155
  return (i, j) => {
46807
47156
  const position = { sheetId: sheetId, col: i + col, row: j + row };
46808
47157
  const rawCell = this.getters.getCell(position);
46809
47158
  if (rawCell?.content ||
46810
47159
  this.getters.getEvaluatedCell(position).type !== CellValueType.empty) {
46811
- this.blockedArrayFormulas.add(formulaPositionId);
47160
+ this.blockedArrayFormulas.add(formulaPosition);
46812
47161
  throw new EvaluationError(_t("Array result was not expanded because it would overwrite data in %s.", toXC(position.col, position.row)));
46813
47162
  }
46814
- this.blockedArrayFormulas.delete(formulaPositionId);
47163
+ this.blockedArrayFormulas.delete(formulaPosition);
46815
47164
  };
46816
47165
  }
46817
47166
  spreadValues({ sheetId, col, row }, matrixResult) {
@@ -46819,19 +47168,18 @@ class Evaluator {
46819
47168
  const position = { sheetId, col: i + col, row: j + row };
46820
47169
  const cell = this.getters.getCell(position);
46821
47170
  const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(matrixResult[i][j]), this.getters.getLocale(), cell);
46822
- const positionId = this.encoder.encode(position);
46823
- this.setEvaluatedCell(positionId, evaluatedCell);
47171
+ this.evaluatedCells.set(position, evaluatedCell);
46824
47172
  // check if formula dependencies present in the spread zone
46825
47173
  // if so, they need to be recomputed
46826
- this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([positionId]));
47174
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([position]));
46827
47175
  };
46828
47176
  }
46829
- invalidateSpreading(positionId) {
46830
- if (!this.spreadingRelations.isArrayFormula(positionId)) {
47177
+ invalidateSpreading(position) {
47178
+ if (!this.spreadingRelations.isArrayFormula(position)) {
46831
47179
  return;
46832
47180
  }
46833
- for (const child of this.spreadingRelations.getArrayResultPositionIds(positionId)) {
46834
- const content = this.getCell(child)?.content;
47181
+ for (const child of this.spreadingRelations.getArrayResultPositions(position)) {
47182
+ const content = this.getters.getCell(child)?.content;
46835
47183
  if (content) {
46836
47184
  // there's no point at re-evaluating overlapping array formulas,
46837
47185
  // there's still a collision
@@ -46841,28 +47189,25 @@ class Evaluator {
46841
47189
  this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
46842
47190
  this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
46843
47191
  }
46844
- this.spreadingRelations.removeNode(positionId);
47192
+ this.spreadingRelations.removeNode(position);
46845
47193
  }
46846
47194
  // ----------------------------------------------------------
46847
47195
  // COMMON FUNCTIONALITY
46848
47196
  // ----------------------------------------------------------
46849
- getDirectDependencies(positionId) {
46850
- const cell = this.getCell(positionId);
47197
+ getDirectDependencies(position) {
47198
+ const cell = this.getters.getCell(position);
46851
47199
  if (!cell?.isFormula) {
46852
47200
  return [];
46853
47201
  }
46854
47202
  return cell.compiledFormula.dependencies;
46855
47203
  }
46856
- getCellsDependingOn(positionIds) {
47204
+ getCellsDependingOn(positions) {
46857
47205
  const ranges = [];
46858
- for (const positionId of positionIds) {
46859
- ranges.push(this.encoder.decodeToBoundingBox(positionId));
47206
+ for (const position of positions) {
47207
+ ranges.push({ sheetId: position.sheetId, zone: positionToZone(position) });
46860
47208
  }
46861
47209
  return this.formulaDependencies().getCellsDependingOn(ranges);
46862
47210
  }
46863
- getCell(positionId) {
46864
- return this.getters.getCell(this.encoder.decode(positionId));
46865
- }
46866
47211
  }
46867
47212
  function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
46868
47213
  for (let i = 0; i < nbColumns; ++i) {
@@ -46888,96 +47233,17 @@ function nullValueToZeroValue(fPayload) {
46888
47233
  }
46889
47234
  return fPayload;
46890
47235
  }
46891
- /**
46892
- * Encode (and decode) cell positions { sheetId, col, row }
46893
- * to a single integer.
46894
- *
46895
- * `col` and `row` values are encoded on 21 bits each (max 2^21 = 2_097_152),
46896
- * An incremental integer id is assigned to each different sheet id, starting at 0.
46897
- *
46898
- * e.g.
46899
- * Given { col: 10, row: 4, sheetId: "abcde" }
46900
- * we have:
46901
- * - row "4" encoded on 21 bits: 000000000000000000100
46902
- * - col "10" encoded on 21 bits: 000000000000000001010
46903
- * - sheetId: let's say it's the 4th sheetId met, encoded to: 11
46904
- *
46905
- * The final encoded value is found by concatenating the 3 bit sequences:
46906
- *
46907
- * sheetId: 11
46908
- * col: 000000000000000001010
46909
- * row: 000000000000000000100
46910
- * => 11000000000000000001010000000000000000000100
46911
- *
46912
- * this binary sequence is the integer 13194160504836
46913
- */
46914
- class PositionBitsEncoder {
46915
- sheetMapping = {};
46916
- inverseSheetMapping = new Map();
46917
- constructor() {
46918
- try {
46919
- // @ts-ignore
46920
- o_spreadsheet.__DEBUG__ = o_spreadsheet.__DEBUG__ || {};
46921
- // @ts-ignore
46922
- o_spreadsheet.__DEBUG__.decodePosition = this.decode.bind(this);
46923
- // @ts-ignore
46924
- o_spreadsheet.__DEBUG__.encodePosition = this.encode.bind(this);
46925
- }
46926
- catch (error) { }
46927
- }
46928
- /**
46929
- * Encode a cell position to a single integer.
46930
- */
46931
- encode({ sheetId, col, row }) {
46932
- return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
46933
- }
46934
- encodeBoundingBox({ sheetId, zone }) {
46935
- const positions = [];
46936
- forEachPositionsInZone(zone, (col, row) => {
46937
- positions.push(this.encode({ sheetId, col, row }));
46938
- });
46939
- return positions;
46940
- }
46941
- decode(id) {
46942
- // keep only the last 21 bits by AND-ing the bit sequence with 21 ones
46943
- const row = Number(id & 2097151n);
46944
- const col = Number((id >> 21n) & 2097151n);
46945
- const sheetId = this.decodeSheet(id >> 42n);
46946
- return { sheetId, col, row };
46947
- }
46948
- decodeToBoundingBox(id) {
46949
- const { sheetId, col, row } = this.decode(id);
46950
- return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
46951
- }
46952
- encodeSheet(sheetId) {
46953
- const sheetKey = this.sheetMapping[sheetId];
46954
- if (sheetKey === undefined) {
46955
- const newSheetKey = BigInt(Object.keys(this.sheetMapping).length);
46956
- this.sheetMapping[sheetId] = newSheetKey;
46957
- this.inverseSheetMapping.set(newSheetKey, sheetId);
46958
- return newSheetKey;
46959
- }
46960
- return sheetKey;
46961
- }
46962
- decodeSheet(sheetKey) {
46963
- const sheetId = this.inverseSheetMapping.get(sheetKey);
46964
- if (sheetId === undefined) {
46965
- throw new Error("Sheet id not found");
46966
- }
46967
- return sheetId;
46968
- }
46969
- }
46970
47236
  function updateEvalContextAndExecute(compiledFormula, compilationParams, sheetId, cellId) {
46971
- compilationParams[EVAL_CONTEXT_INDEX].__originCellXC = lazy(() => {
47237
+ compilationParams.evalContext.__originCellXC = lazy(() => {
46972
47238
  if (!cellId) {
46973
47239
  return undefined;
46974
47240
  }
46975
47241
  // compute the value lazily for performance reasons
46976
- const position = compilationParams[EVAL_CONTEXT_INDEX].getters.getCellPosition(cellId);
47242
+ const position = compilationParams.evalContext.getters.getCellPosition(cellId);
46977
47243
  return toXC(position.col, position.row);
46978
47244
  });
46979
- compilationParams[EVAL_CONTEXT_INDEX].__originSheetId = sheetId;
46980
- return compiledFormula.execute(compiledFormula.dependencies, ...compilationParams);
47245
+ compilationParams.evalContext.__originSheetId = sheetId;
47246
+ return compiledFormula.execute(compiledFormula.dependencies, compilationParams.referenceDenormalizer, compilationParams.ensureRange, compilationParams.evalContext);
46981
47247
  }
46982
47248
 
46983
47249
  //#region
@@ -47084,6 +47350,7 @@ class EvaluationPlugin extends UIPlugin {
47084
47350
  "getEvaluatedCellsInZone",
47085
47351
  "getSpreadPositionsOf",
47086
47352
  "getArrayFormulaSpreadingOn",
47353
+ "isEmpty",
47087
47354
  ];
47088
47355
  shouldRebuildDependenciesGraph = true;
47089
47356
  evaluator;
@@ -47112,6 +47379,10 @@ class EvaluationPlugin extends UIPlugin {
47112
47379
  this.evaluator.updateDependencies(cmd);
47113
47380
  }
47114
47381
  break;
47382
+ case "DUPLICATE_SHEET":
47383
+ case "CREATE_SHEET":
47384
+ this.shouldRebuildDependenciesGraph = true;
47385
+ break;
47115
47386
  case "EVALUATE_CELLS":
47116
47387
  this.evaluator.evaluateAllCells();
47117
47388
  break;
@@ -47189,6 +47460,14 @@ class EvaluationPlugin extends UIPlugin {
47189
47460
  getArrayFormulaSpreadingOn(position) {
47190
47461
  return this.evaluator.getArrayFormulaSpreadingOn(position);
47191
47462
  }
47463
+ /**
47464
+ * Check if a zone only contains empty cells
47465
+ */
47466
+ isEmpty(sheetId, zone) {
47467
+ return positions(zone)
47468
+ .map(({ col, row }) => this.getEvaluatedCell({ sheetId, col, row }))
47469
+ .every((cell) => cell.type === CellValueType.empty);
47470
+ }
47192
47471
  // ---------------------------------------------------------------------------
47193
47472
  // Export
47194
47473
  // ---------------------------------------------------------------------------
@@ -47306,12 +47585,13 @@ function colorDistance(color1, color2) {
47306
47585
  */
47307
47586
  class CustomColorsPlugin extends UIPlugin {
47308
47587
  customColors = {};
47309
- configCustomColors;
47310
- shouldUpdateColors = false;
47588
+ shouldUpdateColors = true;
47311
47589
  static getters = ["getCustomColors"];
47312
47590
  constructor(config) {
47313
47591
  super(config);
47314
- this.configCustomColors = config.customColors;
47592
+ for (const color of config.customColors ?? []) {
47593
+ this.tryToAddColor(color);
47594
+ }
47315
47595
  }
47316
47596
  handle(cmd) {
47317
47597
  switch (cmd.type) {
@@ -47322,31 +47602,29 @@ class CustomColorsPlugin extends UIPlugin {
47322
47602
  case "SET_BORDER":
47323
47603
  case "SET_ZONE_BORDERS":
47324
47604
  case "SET_FORMATTING":
47605
+ case "CREATE_TABLE":
47606
+ case "UPDATE_TABLE":
47325
47607
  this.history.update("shouldUpdateColors", true);
47608
+ break;
47326
47609
  }
47327
47610
  }
47328
47611
  finalize() {
47329
47612
  if (this.shouldUpdateColors) {
47330
47613
  this.history.update("shouldUpdateColors", false);
47331
- for (const color of this.getCustomColors()) {
47614
+ for (const color of this.computeCustomColors()) {
47332
47615
  this.tryToAddColor(color);
47333
47616
  }
47334
47617
  }
47335
47618
  }
47336
47619
  getCustomColors() {
47337
- let usedColors = this.configCustomColors;
47620
+ return sortWithClusters(Object.keys(this.customColors));
47621
+ }
47622
+ computeCustomColors() {
47623
+ let usedColors = [];
47338
47624
  for (const sheetId of this.getters.getSheetIds()) {
47339
- usedColors = usedColors.concat(this.getColorsFromCells(sheetId), this.getFormattingColors(sheetId), this.getChartColors(sheetId));
47340
- }
47341
- return sortWithClusters([
47342
- ...new Set(
47343
- // remove duplicates first to check validity on a reduced
47344
- // set of colors, then normalize to HEX and remove duplicates
47345
- // again
47346
- [...new Set([...usedColors, ...Object.keys(this.customColors)])]
47347
- .filter(isColorValid)
47348
- .map(toHex)),
47349
- ]).filter((color) => !COLOR_PICKER_DEFAULTS.includes(color));
47625
+ usedColors = usedColors.concat(this.getColorsFromCells(sheetId), this.getFormattingColors(sheetId), this.getChartColors(sheetId), this.getTableColors(sheetId));
47626
+ }
47627
+ return [...new Set([...usedColors])];
47350
47628
  }
47351
47629
  getColorsFromCells(sheetId) {
47352
47630
  const cells = Object.values(this.getters.getCells(sheetId));
@@ -47408,7 +47686,43 @@ class CustomColorsPlugin extends UIPlugin {
47408
47686
  }
47409
47687
  return [...chartsColors];
47410
47688
  }
47689
+ getTableColors(sheetId) {
47690
+ const tables = this.getters.getTables(sheetId);
47691
+ return tables.flatMap((table) => {
47692
+ const config = table.config;
47693
+ const style = TABLE_PRESETS[config.styleId];
47694
+ return [
47695
+ this.getTableStyleElementColors(style.wholeTable),
47696
+ config.numberOfHeaders > 0 ? this.getTableStyleElementColors(style.headerRow) : [],
47697
+ config.totalRow ? this.getTableStyleElementColors(style.totalRow) : [],
47698
+ config.bandedColumns ? this.getTableStyleElementColors(style.firstColumnStripe) : [],
47699
+ config.bandedColumns ? this.getTableStyleElementColors(style.secondColumnStripe) : [],
47700
+ config.bandedRows ? this.getTableStyleElementColors(style.firstRowStripe) : [],
47701
+ config.bandedRows ? this.getTableStyleElementColors(style.secondRowStripe) : [],
47702
+ config.firstColumn ? this.getTableStyleElementColors(style.firstColumn) : [],
47703
+ config.lastColumn ? this.getTableStyleElementColors(style.lastColumn) : [],
47704
+ ].flat();
47705
+ });
47706
+ }
47707
+ getTableStyleElementColors(element) {
47708
+ if (!element) {
47709
+ return [];
47710
+ }
47711
+ return [
47712
+ element.style?.fillColor,
47713
+ element.style?.textColor,
47714
+ element.border?.bottom?.color,
47715
+ element.border?.top?.color,
47716
+ element.border?.left?.color,
47717
+ element.border?.right?.color,
47718
+ element.border?.horizontal?.color,
47719
+ element.border?.vertical?.color,
47720
+ ].filter(isDefined$1);
47721
+ }
47411
47722
  tryToAddColor(color) {
47723
+ if (!isColorValid(color)) {
47724
+ return;
47725
+ }
47412
47726
  const formattedColor = toHex(color);
47413
47727
  if (color && !COLOR_PICKER_DEFAULTS.includes(formattedColor)) {
47414
47728
  this.history.update("customColors", formattedColor, true);
@@ -47832,8 +48146,6 @@ const VALID_RESULT = { isValid: true };
47832
48146
  class EvaluationDataValidationPlugin extends UIPlugin {
47833
48147
  static getters = [
47834
48148
  "getDataValidationInvalidCriterionValueMessage",
47835
- "getDataValidationCheckBoxCellPositions",
47836
- "getDataValidationListCellsPositions",
47837
48149
  "getInvalidDataValidationMessage",
47838
48150
  "getValidationResultForCellValue",
47839
48151
  "isCellValidCheckbox",
@@ -47911,19 +48223,6 @@ class EvaluationDataValidationPlugin extends UIPlugin {
47911
48223
  const error = this.getRuleErrorForCellValue(cellValue, cellPosition, rule);
47912
48224
  return error ? { error, rule, isValid: false } : VALID_RESULT;
47913
48225
  }
47914
- getDataValidationCheckBoxCellPositions() {
47915
- const rules = this.getters
47916
- .getDataValidationRules(this.getters.getActiveSheetId())
47917
- .filter((rule) => rule.criterion.type === "isBoolean");
47918
- return getCellPositionsInRanges(rules.map((rule) => rule.ranges).flat()).filter((position) => this.isCellValidCheckbox(position));
47919
- }
47920
- getDataValidationListCellsPositions() {
47921
- const rules = this.getters
47922
- .getDataValidationRules(this.getters.getActiveSheetId())
47923
- .filter((rule) => (rule.criterion.type === "isValueInList" || rule.criterion.type === "isValueInRange") &&
47924
- rule.criterion.displayStyle === "arrow");
47925
- return getCellPositionsInRanges(rules.map((rule) => rule.ranges).flat());
47926
- }
47927
48226
  getValidationResultForCell(cellPosition) {
47928
48227
  const { col, row, sheetId } = cellPosition;
47929
48228
  if (!this.validationResults[sheetId]) {
@@ -48380,6 +48679,23 @@ class AutofillPlugin extends UIPlugin {
48380
48679
  * autofiller
48381
48680
  */
48382
48681
  autofillAuto() {
48682
+ const activePosition = this.getters.getActivePosition();
48683
+ const table = this.getters.getTable(activePosition);
48684
+ let autofillRow = table ? table.range.zone.bottom : this.getAutofillAutoLastRow();
48685
+ // Stop autofill at the next non-empty cell
48686
+ const selection = this.getters.getSelectedZone();
48687
+ for (let row = selection.bottom + 1; row <= autofillRow; row++) {
48688
+ if (this.getters.getEvaluatedCell({ ...activePosition, row }).type !== CellValueType.empty) {
48689
+ autofillRow = row - 1;
48690
+ break;
48691
+ }
48692
+ }
48693
+ if (autofillRow > selection.bottom) {
48694
+ this.select(activePosition.col, autofillRow);
48695
+ this.autofill(true);
48696
+ }
48697
+ }
48698
+ getAutofillAutoLastRow() {
48383
48699
  const zone = this.getters.getSelectedZone();
48384
48700
  const sheetId = this.getters.getActiveSheetId();
48385
48701
  let col = zone.left;
@@ -48403,10 +48719,7 @@ class AutofillPlugin extends UIPlugin {
48403
48719
  }
48404
48720
  }
48405
48721
  }
48406
- if (row !== zone.bottom) {
48407
- this.select(zone.left, row - 1);
48408
- this.autofill(true);
48409
- }
48722
+ return row - 1;
48410
48723
  }
48411
48724
  /**
48412
48725
  * Generate the next cell
@@ -49948,7 +50261,6 @@ class DataCleanupPlugin extends UIPlugin {
49948
50261
  * the search with a new value.
49949
50262
  */
49950
50263
  class FindAndReplacePlugin extends UIPlugin {
49951
- static layers = ["Search"];
49952
50264
  static getters = [];
49953
50265
  handle(cmd) {
49954
50266
  switch (cmd.type) {
@@ -51018,6 +51330,51 @@ class SplitToColumnsPlugin extends UIPlugin {
51018
51330
  }
51019
51331
  }
51020
51332
 
51333
+ class TableAutofillPlugin extends UIPlugin {
51334
+ handle(cmd) {
51335
+ switch (cmd.type) {
51336
+ case "AUTOFILL_TABLE_COLUMN":
51337
+ const table = this.getters.getTable(cmd);
51338
+ const cell = this.getters.getCell(cmd);
51339
+ if (!table || !table.config.automaticAutofill || !cell?.isFormula)
51340
+ return;
51341
+ const { col, row } = cmd;
51342
+ const tableContentZone = getTableContentZone(table.range.zone, table.config);
51343
+ if (tableContentZone && isInside(col, row, tableContentZone)) {
51344
+ this.autofillTableZone(cmd, tableContentZone);
51345
+ }
51346
+ break;
51347
+ }
51348
+ }
51349
+ autofillTableZone(autofillSource, zone) {
51350
+ if (zone.top === zone.bottom) {
51351
+ return;
51352
+ }
51353
+ const { col, row, sheetId } = autofillSource;
51354
+ for (let r = zone.top; r <= zone.bottom; r++) {
51355
+ if (r === row) {
51356
+ continue;
51357
+ }
51358
+ if (this.getters.getEvaluatedCell({ col, row: r, sheetId }).type !== CellValueType.empty) {
51359
+ return;
51360
+ }
51361
+ }
51362
+ // TODO: seems odd that autofill a table column have side effects on the selection. Autofill commands should be
51363
+ // refactored to take the selection as a parameter
51364
+ const oldSelection = {
51365
+ zone: this.getters.getSelectedZone(),
51366
+ cell: this.getters.getActivePosition(),
51367
+ };
51368
+ this.selection.selectCell(col, row);
51369
+ this.dispatch("AUTOFILL_SELECT", { col, row: zone.bottom });
51370
+ this.dispatch("AUTOFILL");
51371
+ this.selection.selectCell(col, row);
51372
+ this.dispatch("AUTOFILL_SELECT", { col, row: zone.top });
51373
+ this.dispatch("AUTOFILL");
51374
+ this.selection.selectZone(oldSelection);
51375
+ }
51376
+ }
51377
+
51021
51378
  class TableStylePlugin extends UIPlugin {
51022
51379
  static getters = ["getCellTableStyle", "getCellTableBorder"];
51023
51380
  tableStyles = {};
@@ -52885,6 +53242,8 @@ class SheetViewPlugin extends UIPlugin {
52885
53242
  "getEdgeScrollRow",
52886
53243
  "getVisibleFigures",
52887
53244
  "getVisibleRect",
53245
+ "getVisibleRectWithoutHeaders",
53246
+ "getVisibleCellPositions",
52888
53247
  "getColRowOffsetInViewport",
52889
53248
  "getMainViewportCoordinates",
52890
53249
  "getActiveSheetScrollInfo",
@@ -53130,6 +53489,26 @@ class SheetViewPlugin extends UIPlugin {
53130
53489
  const viewports = this.getSubViewports(sheetId);
53131
53490
  return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => !this.getters.isHeaderHidden(sheetId, "ROW", row));
53132
53491
  }
53492
+ /**
53493
+ * Get the positions of all the cells that are visible in the viewport, taking merges into account.
53494
+ */
53495
+ getVisibleCellPositions() {
53496
+ const visibleCols = this.getSheetViewVisibleCols();
53497
+ const visibleRows = this.getSheetViewVisibleRows();
53498
+ const sheetId = this.getters.getActiveSheetId();
53499
+ const positions = [];
53500
+ for (const col of visibleCols) {
53501
+ for (const row of visibleRows) {
53502
+ const position = { sheetId, col, row };
53503
+ const mainPosition = this.getters.getMainCellPosition(position);
53504
+ if (mainPosition.row !== row || mainPosition.col !== col) {
53505
+ continue;
53506
+ }
53507
+ positions.push(position);
53508
+ }
53509
+ }
53510
+ return positions;
53511
+ }
53133
53512
  /**
53134
53513
  * Return the main viewport maximum size relative to the client size.
53135
53514
  */
@@ -53249,6 +53628,13 @@ class SheetViewPlugin extends UIPlugin {
53249
53628
  * Computes the coordinates and size to draw the zone on the canvas
53250
53629
  */
53251
53630
  getVisibleRect(zone) {
53631
+ const rect = this.getVisibleRectWithoutHeaders(zone);
53632
+ return { ...rect, x: rect.x + this.gridOffsetX, y: rect.y + this.gridOffsetY };
53633
+ }
53634
+ /**
53635
+ * Computes the coordinates and size to draw the zone without taking the grid offset into account
53636
+ */
53637
+ getVisibleRectWithoutHeaders(zone) {
53252
53638
  const sheetId = this.getters.getActiveSheetId();
53253
53639
  const viewportRects = this.getSubViewports(sheetId)
53254
53640
  .map((viewport) => viewport.getRect(zone))
@@ -53260,12 +53646,7 @@ class SheetViewPlugin extends UIPlugin {
53260
53646
  const y = Math.min(...viewportRects.map((rect) => rect.y));
53261
53647
  const width = Math.max(...viewportRects.map((rect) => rect.x + rect.width)) - x;
53262
53648
  const height = Math.max(...viewportRects.map((rect) => rect.y + rect.height)) - y;
53263
- return {
53264
- x: x + this.gridOffsetX,
53265
- y: y + this.gridOffsetY,
53266
- width,
53267
- height,
53268
- };
53649
+ return { x, y, width, height };
53269
53650
  }
53270
53651
  /**
53271
53652
  * Returns the position of the MainViewport relatively to the start of the grid (without headers)
@@ -53519,13 +53900,8 @@ class HeaderPositionsUIPlugin extends UIPlugin {
53519
53900
  }
53520
53901
  break;
53521
53902
  case "UPDATE_CELL":
53522
- if ("content" in cmd || "format" in cmd) {
53523
- this.headerPositions = {};
53524
- this.isDirty = true;
53525
- }
53526
- else {
53527
- this.headerPositions[cmd.sheetId] = this.computeHeaderPositionsOfSheet(cmd.sheetId);
53528
- }
53903
+ this.headerPositions = {};
53904
+ this.isDirty = true;
53529
53905
  break;
53530
53906
  case "UPDATE_FILTER":
53531
53907
  case "UPDATE_TABLE":
@@ -53648,7 +54024,8 @@ const featurePluginRegistry = new Registry()
53648
54024
  .add("split_to_columns", SplitToColumnsPlugin)
53649
54025
  .add("collaborative", CollaborativePlugin)
53650
54026
  .add("history", HistoryPlugin)
53651
- .add("data_cleanup", DataCleanupPlugin);
54027
+ .add("data_cleanup", DataCleanupPlugin)
54028
+ .add("table_autofill", TableAutofillPlugin);
53652
54029
  // Plugins which have a state, but which should not be shared in collaborative
53653
54030
  const statefulUIPluginRegistry = new Registry()
53654
54031
  .add("selection", GridSelectionPlugin)
@@ -53991,6 +54368,9 @@ class BottomBarSheet extends owl.Component {
53991
54368
  this.scrollToSheet();
53992
54369
  }
53993
54370
  onDblClick() {
54371
+ if (this.env.model.getters.isReadonly()) {
54372
+ return;
54373
+ }
53994
54374
  this.startEdition();
53995
54375
  }
53996
54376
  onKeyDown(ev) {
@@ -55947,7 +56327,6 @@ class Spreadsheet extends owl.Component {
55947
56327
  }
55948
56328
  }, () => [this.env.model.getters.getActiveSheetId()]);
55949
56329
  owl.useExternalListener(window, "resize", () => this.render(true));
55950
- owl.useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
55951
56330
  this.bindModelEvents();
55952
56331
  owl.onWillUpdateProps((nextProps) => {
55953
56332
  if (nextProps.model !== this.props.model) {
@@ -59788,6 +60167,6 @@ exports.tokenColors = tokenColors;
59788
60167
  exports.tokenize = tokenize;
59789
60168
 
59790
60169
 
59791
- __info__.version = "17.2.0-alpha.6";
59792
- __info__.date = "2024-02-28T12:28:44.114Z";
59793
- __info__.hash = "318a04e";
60170
+ __info__.version = "17.2.0";
60171
+ __info__.date = "2024-03-20T08:12:44.398Z";
60172
+ __info__.hash = "1869c24";