@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
  import { reactive, useEnv, useSubEnv, useState, onWillUnmount, markRaw, toRaw, Component, useRef, onMounted, useEffect, onPatched, useComponent, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv } from '@odoo/owl';
@@ -189,8 +189,8 @@ const DEFAULT_GAUGE_LOWER_COLOR = "#cc0000";
189
189
  const DEFAULT_GAUGE_MIDDLE_COLOR = "#f1c232";
190
190
  const DEFAULT_GAUGE_UPPER_COLOR = "#6aa84f";
191
191
  const DEFAULT_SCORECARD_BASELINE_MODE = "difference";
192
- const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#00A04A";
193
- const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#DC6965";
192
+ const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#6aa84f";
193
+ const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#E06666";
194
194
  const LINE_FILL_TRANSPARENCY = 0.4;
195
195
  // session
196
196
  const DEBOUNCE_TIME = 200;
@@ -640,21 +640,6 @@ function isConsecutive(iterable) {
640
640
  }
641
641
  return true;
642
642
  }
643
- class JetSet extends Set {
644
- addMany(iterable) {
645
- for (const element of iterable) {
646
- super.add(element);
647
- }
648
- return this;
649
- }
650
- deleteMany(iterable) {
651
- let wasDeleted = false;
652
- for (const element of iterable) {
653
- wasDeleted ||= super.delete(element);
654
- }
655
- return wasDeleted;
656
- }
657
- }
658
643
  /**
659
644
  * Creates a version of the function that's memoized on the value of its first
660
645
  * argument, if any.
@@ -2144,7 +2129,6 @@ const LAYERS = {
2144
2129
  Background: 0,
2145
2130
  Highlights: 1,
2146
2131
  Clipboard: 2,
2147
- Search: 3,
2148
2132
  Chart: 4,
2149
2133
  Autofill: 5,
2150
2134
  Selection: 6,
@@ -4101,14 +4085,6 @@ function positions(zone) {
4101
4085
  }
4102
4086
  return positions;
4103
4087
  }
4104
- function forEachPositionsInZone(zone, callback) {
4105
- const { left, right, top, bottom } = zone;
4106
- for (let col = left; col <= right; col++) {
4107
- for (let row = top; row <= bottom; row++) {
4108
- callback(col, row);
4109
- }
4110
- }
4111
- }
4112
4088
  /**
4113
4089
  * This function returns a zone with coordinates modified according to the change
4114
4090
  * applied to the zone. It may be possible to change the zone by resizing or moving
@@ -6316,6 +6292,11 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
6316
6292
  this.pasteTableCell(sheetId, tableCell, position, clipboardOptions);
6317
6293
  }
6318
6294
  }
6295
+ if (tableCells.length === 1) {
6296
+ for (let c = 0; c < tableCells[0].length; c++) {
6297
+ this.dispatch("AUTOFILL_TABLE_COLUMN", { col: col + c, row, sheetId });
6298
+ }
6299
+ }
6319
6300
  }
6320
6301
  pasteTableCell(sheetId, tableCell, position, options) {
6321
6302
  if (tableCell.table && !options?.pasteOption) {
@@ -7442,6 +7423,7 @@ const TableTerms = {
7442
7423
  firstColumn: _t("First column"),
7443
7424
  lastColumn: _t("Last column"),
7444
7425
  bandedColumns: _t("Banded columns"),
7426
+ automaticAutofill: _t("Automatically autofill formulas"),
7445
7427
  totalRow: _t("Total row"),
7446
7428
  },
7447
7429
  Tooltips: {
@@ -8188,11 +8170,18 @@ function drawHighlight(renderingContext, highlight, rect) {
8188
8170
  }
8189
8171
  const color = highlight.color || HIGHLIGHT_COLOR;
8190
8172
  const { ctx } = renderingContext;
8191
- ctx.lineWidth = 2;
8192
- ctx.strokeStyle = color;
8193
- /** + 0.5 offset to have sharp lines. See comment in {@link RendererPlugin#drawBorders} for more details */
8194
- ctx.strokeRect(x + 0.5, y + 0.5, width, height);
8195
- ctx.globalCompositeOperation = "source-over";
8173
+ if (!highlight.noBorder) {
8174
+ ctx.strokeStyle = color;
8175
+ if (highlight.thinLine) {
8176
+ ctx.lineWidth = 1;
8177
+ ctx.strokeRect(x, y, width, height);
8178
+ }
8179
+ else {
8180
+ ctx.lineWidth = 2;
8181
+ /** + 0.5 offset to have sharp lines. See comment in {@link RendererPlugin#drawBorder} for more details */
8182
+ ctx.strokeRect(x + 0.5, y + 0.5, width, height);
8183
+ }
8184
+ }
8196
8185
  if (!highlight.noFill) {
8197
8186
  ctx.fillStyle = setColorAlpha(color, highlight.fillAlpha ?? 0.12);
8198
8187
  ctx.fillRect(x, y, width, height);
@@ -8566,6 +8555,7 @@ class ComposerStore extends SpreadsheetStore {
8566
8555
  else if (cell.link) {
8567
8556
  content = markdownLink(content, cell.link.url);
8568
8557
  }
8558
+ this.addHeadersForSpreadingFormula(content);
8569
8559
  this.model.dispatch("UPDATE_CELL", {
8570
8560
  sheetId: this.sheetId,
8571
8561
  col,
@@ -8581,6 +8571,7 @@ class ComposerStore extends SpreadsheetStore {
8581
8571
  row,
8582
8572
  });
8583
8573
  }
8574
+ this.model.dispatch("AUTOFILL_TABLE_COLUMN", { col, row, sheetId: this.sheetId });
8584
8575
  this.setContent("");
8585
8576
  }
8586
8577
  }
@@ -8783,6 +8774,38 @@ class ComposerStore extends SpreadsheetStore {
8783
8774
  }
8784
8775
  this.colorIndexByRange = colorsToKeep;
8785
8776
  }
8777
+ /** Add headers at the end of the sheet so the formula in the composer has enough space to spread */
8778
+ addHeadersForSpreadingFormula(content) {
8779
+ if (!content.startsWith("=")) {
8780
+ return;
8781
+ }
8782
+ const evaluated = this.getters.evaluateFormula(this.sheetId, content);
8783
+ if (!isMatrix(evaluated)) {
8784
+ return;
8785
+ }
8786
+ const numberOfRows = this.getters.getNumberRows(this.sheetId);
8787
+ const numberOfCols = this.getters.getNumberCols(this.sheetId);
8788
+ const missingRows = this.row + evaluated[0].length - numberOfRows;
8789
+ const missingCols = this.col + evaluated.length - numberOfCols;
8790
+ if (missingCols > 0) {
8791
+ this.model.dispatch("ADD_COLUMNS_ROWS", {
8792
+ sheetId: this.sheetId,
8793
+ dimension: "COL",
8794
+ base: numberOfCols - 1,
8795
+ position: "after",
8796
+ quantity: missingCols + 20,
8797
+ });
8798
+ }
8799
+ if (missingRows > 0) {
8800
+ this.model.dispatch("ADD_COLUMNS_ROWS", {
8801
+ sheetId: this.sheetId,
8802
+ dimension: "ROW",
8803
+ base: numberOfRows - 1,
8804
+ position: "after",
8805
+ quantity: missingRows + 50,
8806
+ });
8807
+ }
8808
+ }
8786
8809
  /**
8787
8810
  * Highlight all ranges that can be found in the composer content.
8788
8811
  */
@@ -10007,6 +10030,9 @@ function makeArg(str, description) {
10007
10030
  result.default = true;
10008
10031
  result.defaultValue = defaultValue;
10009
10032
  }
10033
+ if (types.some((t) => t.startsWith("RANGE"))) {
10034
+ result.acceptMatrix = true;
10035
+ }
10010
10036
  return result;
10011
10037
  }
10012
10038
  /**
@@ -17416,10 +17442,11 @@ const DEFAULT_IS_SORTED = true;
17416
17442
  const DEFAULT_MATCH_MODE = 0;
17417
17443
  const DEFAULT_SEARCH_MODE = 1;
17418
17444
  const DEFAULT_ABSOLUTE_RELATIVE_MODE = 1;
17419
- function assertAvailable(variable, searchKey) {
17420
- if (variable === undefined) {
17421
- throw new NotAvailableError(_t("Did not find value '%s' in [[FUNCTION_NAME]] evaluation.", toString(searchKey)));
17422
- }
17445
+ function valueNotAvailable(searchKey) {
17446
+ return {
17447
+ value: CellErrorType.NotAvailable,
17448
+ message: _t("Did not find value '%s' in [[FUNCTION_NAME]] evaluation.", toString(searchKey)),
17449
+ };
17423
17450
  }
17424
17451
  // -----------------------------------------------------------------------------
17425
17452
  // ADDRESS
@@ -17516,7 +17543,9 @@ const HLOOKUP = {
17516
17543
  ? dichotomicSearch(range, searchKey, "nextSmaller", "asc", range.length, getValueFromRange)
17517
17544
  : linearSearch(range, searchKey, "wildcard", range.length, getValueFromRange);
17518
17545
  const col = range[colIndex];
17519
- assertAvailable(col, searchKey?.value);
17546
+ if (col === undefined) {
17547
+ return valueNotAvailable(searchKey);
17548
+ }
17520
17549
  return col[_index - 1];
17521
17550
  },
17522
17551
  isExported: true,
@@ -17635,11 +17664,11 @@ const LOOKUP = {
17635
17664
  : (range, index) => range[index][0].value;
17636
17665
  const rangeLength = verticalSearch ? nbRow : nbCol;
17637
17666
  const index = dichotomicSearch(searchArray, searchKey, "nextSmaller", "asc", rangeLength, getElement);
17638
- if (index === -1)
17639
- assertAvailable(undefined, searchKey?.value);
17640
- verticalSearch
17641
- ? assertAvailable(searchArray[0][index], searchKey?.value)
17642
- : assertAvailable(searchArray[index][nbRow - 1], searchKey?.value);
17667
+ if (index === -1 ||
17668
+ (verticalSearch && searchArray[0][index] === undefined) ||
17669
+ (!verticalSearch && searchArray[index][nbRow - 1] === undefined)) {
17670
+ return valueNotAvailable(searchKey);
17671
+ }
17643
17672
  if (resultRange === undefined) {
17644
17673
  return verticalSearch ? searchArray[nbCol - 1][index] : searchArray[index][nbRow - 1];
17645
17674
  }
@@ -17689,7 +17718,10 @@ const MATCH = {
17689
17718
  index = dichotomicSearch(range, searchKey, "nextGreater", "desc", rangeLen, getElement);
17690
17719
  break;
17691
17720
  }
17692
- assertAvailable(nbCol === 1 ? range[0][index] : range[index], searchKey);
17721
+ if ((nbCol === 1 && range[0][index] === undefined) ||
17722
+ (nbCol !== 1 && range[index] === undefined)) {
17723
+ return valueNotAvailable(searchKey);
17724
+ }
17693
17725
  return index + 1;
17694
17726
  },
17695
17727
  isExported: true,
@@ -17748,7 +17780,9 @@ const VLOOKUP = {
17748
17780
  ? dichotomicSearch(range, searchKey, "nextSmaller", "asc", range[0].length, getValueFromRange)
17749
17781
  : linearSearch(range, searchKey, "wildcard", range[0].length, getValueFromRange);
17750
17782
  const value = range[_index - 1][rowIndex];
17751
- assertAvailable(value, searchKey);
17783
+ if (value === undefined) {
17784
+ return valueNotAvailable(searchKey);
17785
+ }
17752
17786
  return value;
17753
17787
  },
17754
17788
  isExported: true,
@@ -17808,7 +17842,9 @@ const XLOOKUP = {
17808
17842
  ? returnRange.map((col) => [col[index]])
17809
17843
  : [returnRange[index]];
17810
17844
  }
17811
- assertAvailable(defaultValue, searchKey);
17845
+ if (defaultValue === undefined) {
17846
+ return valueNotAvailable(searchKey);
17847
+ }
17812
17848
  return [[defaultValue]];
17813
17849
  },
17814
17850
  isExported: true,
@@ -18567,16 +18603,31 @@ class FunctionRegistry extends Registry {
18567
18603
  }
18568
18604
  const descr = addMetaInfoFromArg(addDescr);
18569
18605
  validateArguments(descr.args);
18570
- this.mapping[name] = addErrorHandling(addResultHandling(descr.compute), name);
18606
+ this.mapping[name] = addErrorHandling(addResultHandling(addInputHandling(descr)), name);
18571
18607
  super.add(name, descr);
18572
18608
  return this;
18573
18609
  }
18574
18610
  }
18611
+ function addInputHandling(descr) {
18612
+ function computeWithInputHandling(...args) {
18613
+ for (let i = 0; i < args.length; i++) {
18614
+ const argDefinition = descr.args[descr.getArgToFocus(i + 1) - 1];
18615
+ const arg = args[i];
18616
+ if (isMatrix(arg) && !argDefinition.acceptMatrix) {
18617
+ if (arg.length !== 1 || arg[0].length !== 1) {
18618
+ 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));
18619
+ }
18620
+ args[i] = arg[0][0];
18621
+ }
18622
+ }
18623
+ return descr.compute.apply(this, args);
18624
+ }
18625
+ return computeWithInputHandling;
18626
+ }
18575
18627
  function addErrorHandling(compute, functionName) {
18576
18628
  return function (...args) {
18577
18629
  try {
18578
- const computeFormula = compute.bind(this);
18579
- return computeFormula(...args);
18630
+ return compute.apply(this, args);
18580
18631
  }
18581
18632
  catch (e) {
18582
18633
  return handleError(e, functionName);
@@ -18610,8 +18661,7 @@ function hasStringMessage(obj) {
18610
18661
  }
18611
18662
  function addResultHandling(compute) {
18612
18663
  return function (...args) {
18613
- const computeResult = compute.bind(this);
18614
- const result = computeResult(...args);
18664
+ const result = compute.apply(this, args);
18615
18665
  if (!isMatrix(result)) {
18616
18666
  if (typeof result === "object" && result !== null && "value" in result) {
18617
18667
  return result;
@@ -18826,8 +18876,10 @@ function getGroup(cell, cells, filter) {
18826
18876
  if (x === cell) {
18827
18877
  found = true;
18828
18878
  }
18829
- const cellValue = evaluateLiteral(x?.content, { locale: DEFAULT_LOCALE });
18830
- if (filter(cellValue)) {
18879
+ const cellValue = x?.isFormula
18880
+ ? undefined
18881
+ : evaluateLiteral(x?.content, { locale: DEFAULT_LOCALE });
18882
+ if (cellValue && filter(cellValue)) {
18831
18883
  group.push(cellValue);
18832
18884
  }
18833
18885
  else {
@@ -19476,9 +19528,15 @@ function getChartDatasetValues(getters, dataSets) {
19476
19528
  : (label = `${ChartTerms.Series} ${parseInt(dsIndex) + 1}`);
19477
19529
  }
19478
19530
  else {
19479
- label = label = `${ChartTerms.Series} ${parseInt(dsIndex) + 1}`;
19531
+ label = `${ChartTerms.Series} ${parseInt(dsIndex) + 1}`;
19480
19532
  }
19481
19533
  let data = ds.dataRange ? getData(getters, ds) : [];
19534
+ if (data.every((e) => typeof e === "string" && !isEvaluationError(e))) {
19535
+ // In this case, we want a chart based on the string occurrences count
19536
+ // This will be done by associating each string with a value of 1 and
19537
+ // the using the classical aggregation method to sum the values.
19538
+ data.fill(1);
19539
+ }
19482
19540
  datasetValues.push({ data, label });
19483
19541
  }
19484
19542
  return datasetValues;
@@ -19577,7 +19635,7 @@ class BarChart extends AbstractChart {
19577
19635
  dataSets: context.range ? context.range : [],
19578
19636
  dataSetsHaveTitle: false,
19579
19637
  stacked: false,
19580
- aggregated: false,
19638
+ aggregated: context.aggregated ?? false,
19581
19639
  legendPosition: "top",
19582
19640
  title: context.title || "",
19583
19641
  type: "bar",
@@ -19593,6 +19651,7 @@ class BarChart extends AbstractChart {
19593
19651
  auxiliaryRange: this.labelRange
19594
19652
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
19595
19653
  : undefined,
19654
+ aggregated: this.aggregated,
19596
19655
  };
19597
19656
  }
19598
19657
  copyForSheetId(sheetId) {
@@ -20342,7 +20401,7 @@ class LineChart extends AbstractChart {
20342
20401
  verticalAxisPosition: "left",
20343
20402
  labelRange: context.auxiliaryRange || undefined,
20344
20403
  stacked: false,
20345
- aggregated: false,
20404
+ aggregated: context.aggregated ?? false,
20346
20405
  cumulative: false,
20347
20406
  };
20348
20407
  }
@@ -20375,6 +20434,7 @@ class LineChart extends AbstractChart {
20375
20434
  auxiliaryRange: this.labelRange
20376
20435
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
20377
20436
  : undefined,
20437
+ aggregated: this.aggregated,
20378
20438
  };
20379
20439
  }
20380
20440
  updateRanges(applyChange) {
@@ -20449,7 +20509,7 @@ class PieChart extends AbstractChart {
20449
20509
  title: context.title || "",
20450
20510
  type: "pie",
20451
20511
  labelRange: context.auxiliaryRange || undefined,
20452
- aggregated: false,
20512
+ aggregated: context.aggregated ?? false,
20453
20513
  };
20454
20514
  }
20455
20515
  getDefinition() {
@@ -20463,6 +20523,7 @@ class PieChart extends AbstractChart {
20463
20523
  auxiliaryRange: this.labelRange
20464
20524
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
20465
20525
  : undefined,
20526
+ aggregated: this.aggregated,
20466
20527
  };
20467
20528
  }
20468
20529
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
@@ -20654,7 +20715,7 @@ class ScatterChart extends AbstractChart {
20654
20715
  type: "scatter",
20655
20716
  verticalAxisPosition: "left",
20656
20717
  labelRange: context.auxiliaryRange || undefined,
20657
- aggregated: false,
20718
+ aggregated: context.aggregated ?? false,
20658
20719
  };
20659
20720
  }
20660
20721
  getDefinition() {
@@ -20684,6 +20745,7 @@ class ScatterChart extends AbstractChart {
20684
20745
  auxiliaryRange: this.labelRange
20685
20746
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
20686
20747
  : undefined,
20748
+ aggregated: this.aggregated,
20687
20749
  };
20688
20750
  }
20689
20751
  updateRanges(applyChange) {
@@ -22351,6 +22413,50 @@ function isCtrlKey(ev) {
22351
22413
  return isMacOS() ? ev.metaKey : ev.ctrlKey;
22352
22414
  }
22353
22415
 
22416
+ /**
22417
+ * Repeatedly calls a callback function with a time delay between calls.
22418
+ */
22419
+ function useInterval(callback, delay) {
22420
+ let intervalId;
22421
+ const { setInterval, clearInterval } = window;
22422
+ useEffect(() => {
22423
+ intervalId = setInterval(callback, delay);
22424
+ return () => clearInterval(intervalId);
22425
+ }, () => [delay]);
22426
+ return {
22427
+ pause: () => {
22428
+ clearInterval(intervalId);
22429
+ intervalId = undefined;
22430
+ },
22431
+ resume: () => {
22432
+ if (intervalId === undefined) {
22433
+ intervalId = setInterval(callback, delay);
22434
+ }
22435
+ },
22436
+ };
22437
+ }
22438
+ /**
22439
+ * Calls a callback function with a time delay
22440
+ */
22441
+ function useTimeOut() {
22442
+ let timeOutId;
22443
+ function clear() {
22444
+ if (timeOutId !== undefined) {
22445
+ clearTimeout(timeOutId);
22446
+ timeOutId = undefined;
22447
+ }
22448
+ }
22449
+ function schedule(callback, delay) {
22450
+ clear();
22451
+ timeOutId = setTimeout(callback, delay);
22452
+ }
22453
+ onWillUnmount(clear);
22454
+ return {
22455
+ clear,
22456
+ schedule,
22457
+ };
22458
+ }
22459
+
22354
22460
  //------------------------------------------------------------------------------
22355
22461
  // Context Menu Component
22356
22462
  //------------------------------------------------------------------------------
@@ -22401,6 +22507,7 @@ css /* scss */ `
22401
22507
  }
22402
22508
  }
22403
22509
  `;
22510
+ const TIMEOUT_DELAY = 250;
22404
22511
  class Menu extends Component {
22405
22512
  static template = "o-spreadsheet-Menu";
22406
22513
  static props = {
@@ -22411,6 +22518,7 @@ class Menu extends Component {
22411
22518
  onClose: Function,
22412
22519
  onMenuClicked: { type: Function, optional: true },
22413
22520
  menuId: { type: String, optional: true },
22521
+ onMouseOver: { type: Function, optional: true },
22414
22522
  };
22415
22523
  static components = { Menu, Popover };
22416
22524
  static defaultProps = {
@@ -22421,10 +22529,12 @@ class Menu extends Component {
22421
22529
  position: null,
22422
22530
  scrollOffset: 0,
22423
22531
  menuItems: [],
22532
+ isHoveringChild: false,
22424
22533
  });
22425
22534
  menuRef = useRef("menu");
22426
22535
  hoveredMenu = undefined;
22427
22536
  position = useAbsoluteBoundingRect(this.menuRef);
22537
+ openingTimeOut = useTimeOut();
22428
22538
  setup() {
22429
22539
  useExternalListener(window, "click", this.onExternalClick, { capture: true });
22430
22540
  useExternalListener(window, "contextmenu", this.onExternalClick, { capture: true });
@@ -22524,6 +22634,9 @@ class Menu extends Component {
22524
22634
  }
22525
22635
  return false;
22526
22636
  }
22637
+ isActive(menuItem) {
22638
+ return (this.subMenu?.isHoveringChild || false) && this.isParentMenu(this.subMenu, menuItem);
22639
+ }
22527
22640
  onScroll(ev) {
22528
22641
  this.subMenu.scrollOffset = ev.target.scrollTop;
22529
22642
  }
@@ -22531,10 +22644,10 @@ class Menu extends Component {
22531
22644
  * If the given menu is not disabled, open it's submenu at the
22532
22645
  * correct position according to available surrounding space.
22533
22646
  */
22534
- openSubMenu(menu, ev) {
22535
- const parentMenuEl = ev.currentTarget;
22536
- if (!parentMenuEl)
22647
+ openSubMenu(menu, parentMenuEl) {
22648
+ if (!parentMenuEl) {
22537
22649
  return;
22650
+ }
22538
22651
  const y = parentMenuEl.getBoundingClientRect().top;
22539
22652
  this.subMenu.position = {
22540
22653
  x: this.position.x + this.props.depth * MENU_WIDTH,
@@ -22548,32 +22661,50 @@ class Menu extends Component {
22548
22661
  return subMenu.parentMenu?.id === menuItem.id;
22549
22662
  }
22550
22663
  closeSubMenu() {
22664
+ if (this.subMenu.isHoveringChild) {
22665
+ return;
22666
+ }
22551
22667
  this.subMenu.isOpen = false;
22552
22668
  this.subMenu.parentMenu = undefined;
22553
22669
  }
22554
22670
  onClickMenu(menu, ev) {
22555
22671
  if (this.isEnabled(menu)) {
22556
22672
  if (this.isRoot(menu)) {
22557
- this.openSubMenu(menu, ev);
22673
+ this.openSubMenu(menu, ev.currentTarget);
22558
22674
  }
22559
22675
  else {
22560
22676
  this.activateMenu(menu);
22561
22677
  }
22562
22678
  }
22563
22679
  }
22564
- onMouseEnter(menu, ev) {
22565
- this.hoveredMenu = menu;
22566
- menu.onStartHover?.(this.env);
22680
+ onMouseOver(menu, ev) {
22567
22681
  if (this.isEnabled(menu)) {
22568
- if (this.isRoot(menu)) {
22569
- this.openSubMenu(menu, ev);
22682
+ if (this.isParentMenu(this.subMenu, menu)) {
22683
+ this.openingTimeOut.clear();
22684
+ return;
22570
22685
  }
22571
- else {
22572
- this.closeSubMenu();
22686
+ const currentTarget = ev.currentTarget;
22687
+ if (this.isRoot(menu)) {
22688
+ this.openingTimeOut.schedule(() => {
22689
+ this.openSubMenu(menu, currentTarget);
22690
+ }, TIMEOUT_DELAY);
22573
22691
  }
22574
22692
  }
22575
22693
  }
22694
+ onMouseOverMainMenu() {
22695
+ this.props.onMouseOver?.();
22696
+ this.subMenu.isHoveringChild = false;
22697
+ }
22698
+ onMouseOverChildMenu() {
22699
+ this.subMenu.isHoveringChild = true;
22700
+ this.openingTimeOut.clear();
22701
+ }
22702
+ onMouseEnter(menu, ev) {
22703
+ this.hoveredMenu = menu;
22704
+ menu.onStartHover?.(this.env);
22705
+ }
22576
22706
  onMouseLeave(menu) {
22707
+ this.openingTimeOut.schedule(this.closeSubMenu.bind(this), TIMEOUT_DELAY);
22577
22708
  this.hoveredMenu = undefined;
22578
22709
  menu.onStopHover?.(this.env);
22579
22710
  }
@@ -22825,7 +22956,8 @@ function getChartTypes() {
22825
22956
  */
22826
22957
  function getSmartChartDefinition(zone, getters) {
22827
22958
  let dataSetZone = zone;
22828
- if (zone.left !== zone.right) {
22959
+ const singleColumn = zoneToDimension(zone).numberOfCols === 1;
22960
+ if (!singleColumn) {
22829
22961
  dataSetZone = { ...zone, left: zone.left + 1 };
22830
22962
  }
22831
22963
  const dataSets = [zoneToXc(dataSetZone)];
@@ -22859,7 +22991,7 @@ function getSmartChartDefinition(zone, getters) {
22859
22991
  }
22860
22992
  }
22861
22993
  let labelRangeXc;
22862
- if (zone.left !== zone.right) {
22994
+ if (!singleColumn) {
22863
22995
  labelRangeXc = zoneToXc({
22864
22996
  ...zone,
22865
22997
  right: zone.left,
@@ -22883,6 +23015,19 @@ function getSmartChartDefinition(zone, getters) {
22883
23015
  legendPosition: newLegendPos,
22884
23016
  };
22885
23017
  }
23018
+ const _dataSets = createDataSets(getters, dataSets, sheetId, dataSetsHaveTitle);
23019
+ if (singleColumn &&
23020
+ getData(getters, _dataSets[0]).every((e) => typeof e === "string" && !isEvaluationError(e))) {
23021
+ return {
23022
+ title: "",
23023
+ dataSets,
23024
+ aggregated: true,
23025
+ labelRange: dataSets[0],
23026
+ type: "pie",
23027
+ legendPosition: "top",
23028
+ dataSetsHaveTitle: false,
23029
+ };
23030
+ }
22886
23031
  return {
22887
23032
  title,
22888
23033
  dataSets,
@@ -22910,6 +23055,7 @@ const DEFAULT_TABLE_CONFIG = {
22910
23055
  numberOfHeaders: 1,
22911
23056
  bandedRows: true,
22912
23057
  bandedColumns: false,
23058
+ automaticAutofill: true,
22913
23059
  styleId: "TableStyleMedium2",
22914
23060
  };
22915
23061
  function generateColorSet(name, highlightColor) {
@@ -26393,7 +26539,7 @@ class LineBarPieConfigPanel extends Component {
26393
26539
  {
26394
26540
  name: "aggregated",
26395
26541
  label: _t("Aggregate"),
26396
- value: this.props.definition.aggregated,
26542
+ value: this.props.definition.aggregated ?? false,
26397
26543
  onChange: this.onUpdateAggregated.bind(this),
26398
26544
  },
26399
26545
  ];
@@ -27564,15 +27710,15 @@ css /* scss */ `
27564
27710
  // -----------------------------------------------------------------------------
27565
27711
  // We need here the svg of the icons that we need to convert to images for the renderer
27566
27712
  // -----------------------------------------------------------------------------
27567
- 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>';
27568
- 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>';
27713
+ 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>';
27714
+ 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>';
27569
27715
  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>';
27570
- 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>';
27716
+ 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>';
27571
27717
  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>';
27572
- 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>';
27573
- 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>';
27718
+ 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>';
27719
+ 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>';
27574
27720
  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>';
27575
- 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>';
27721
+ 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>';
27576
27722
  function loadIconImage(svg) {
27577
27723
  /** We have to add xmlns, as it's not added by owl in the canvas */
27578
27724
  svg = `<svg xmlns="http://www.w3.org/2000/svg" ${svg.slice(4)}`;
@@ -29438,8 +29584,7 @@ class DataValidationPanel extends Component {
29438
29584
  }
29439
29585
  }
29440
29586
 
29441
- const BORDER_COLOR = "#8B008B";
29442
- const BACKGROUND_COLOR = "#8B008B33";
29587
+ const FIND_AND_REPLACE_HIGHLIGHT_COLOR = "#8B008B";
29443
29588
  var Direction;
29444
29589
  (function (Direction) {
29445
29590
  Direction[Direction["previous"] = -1] = "previous";
@@ -29470,14 +29615,14 @@ class FindAndReplaceStore extends SpreadsheetStore {
29470
29615
  super(get);
29471
29616
  this.initialShowFormulaState = this.model.getters.shouldShowFormulas();
29472
29617
  this.searchOptions.searchFormulas = this.initialShowFormulaState;
29618
+ const highlightStore = get(HighlightStore);
29619
+ highlightStore.register(toRaw(this));
29473
29620
  this.onDispose(() => {
29474
29621
  this.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.initialShowFormulaState });
29475
29622
  this.updateSearchContent.stopDebounce();
29623
+ highlightStore.unRegister(toRaw(this));
29476
29624
  });
29477
29625
  }
29478
- get renderingLayers() {
29479
- return ["Search"];
29480
- }
29481
29626
  get searchMatches() {
29482
29627
  switch (this.searchOptions.searchScope) {
29483
29628
  case "allSheets":
@@ -29704,8 +29849,8 @@ class FindAndReplaceStore extends SpreadsheetStore {
29704
29849
  // ---------------------------------------------------------------------------
29705
29850
  // Grid rendering
29706
29851
  // ---------------------------------------------------------------------------
29707
- draw(renderingContext) {
29708
- const { ctx } = renderingContext;
29852
+ get highlights() {
29853
+ const highlights = [];
29709
29854
  const sheetId = this.getters.getActiveSheetId();
29710
29855
  for (const [index, match] of this.searchMatches.entries()) {
29711
29856
  if (match.sheetId !== sheetId) {
@@ -29713,26 +29858,31 @@ class FindAndReplaceStore extends SpreadsheetStore {
29713
29858
  }
29714
29859
  const zone = positionToZone(match);
29715
29860
  const zoneWithMerge = this.getters.expandZone(sheetId, zone);
29716
- const { x, y, width, height } = this.getters.getVisibleRect(zoneWithMerge);
29861
+ const { width, height } = this.getters.getVisibleRect(zoneWithMerge);
29717
29862
  if (width > 0 && height > 0) {
29718
- ctx.fillStyle = BACKGROUND_COLOR;
29719
- ctx.fillRect(x, y, width, height);
29720
- if (index === this.selectedMatchIndex) {
29721
- ctx.strokeStyle = BORDER_COLOR;
29722
- ctx.strokeRect(x, y, width, height);
29723
- }
29863
+ highlights.push({
29864
+ sheetId,
29865
+ zone: zoneWithMerge,
29866
+ color: FIND_AND_REPLACE_HIGHLIGHT_COLOR,
29867
+ noBorder: index !== this.selectedMatchIndex,
29868
+ thinLine: true,
29869
+ fillAlpha: 0.2,
29870
+ });
29724
29871
  }
29725
29872
  }
29726
- // TODO: use Highlights helpers/stores when the Highlight PR is merged
29727
29873
  if (this.searchOptions.searchScope === "specificRange") {
29728
29874
  const range = this.searchOptions.specificRange;
29729
- if (!range || range.sheetId !== sheetId) {
29730
- return;
29875
+ if (range && range.sheetId === sheetId) {
29876
+ highlights.push({
29877
+ sheetId,
29878
+ zone: range.zone,
29879
+ color: FIND_AND_REPLACE_HIGHLIGHT_COLOR,
29880
+ noFill: true,
29881
+ thinLine: true,
29882
+ });
29731
29883
  }
29732
- const { x, y, width, height } = this.getters.getVisibleRect(range.zone);
29733
- ctx.strokeStyle = BORDER_COLOR;
29734
- ctx.strokeRect(x, y, width, height);
29735
29884
  }
29885
+ return highlights;
29736
29886
  }
29737
29887
  }
29738
29888
 
@@ -30146,6 +30296,12 @@ const TABLE_ELEMENTS_BY_PRIORITY = [
30146
30296
  "headerRow",
30147
30297
  "totalRow",
30148
30298
  ];
30299
+ /** Return the content zone of the table, ie. the table zone without the headers */
30300
+ function getTableContentZone(tableZone, tableConfig) {
30301
+ const numberOfHeaders = tableConfig.numberOfHeaders;
30302
+ const contentZone = { ...tableZone, top: tableZone.top + numberOfHeaders };
30303
+ return contentZone.top <= contentZone.bottom ? contentZone : undefined;
30304
+ }
30149
30305
  function getComputedTableStyle(tableConfig, numberOfCols, numberOfRows) {
30150
30306
  return {
30151
30307
  borders: getAllTableBorders(tableConfig, numberOfCols, numberOfRows),
@@ -30597,14 +30753,6 @@ class TablePanel extends Component {
30597
30753
  tableXc: this.env.model.getters.getRangeString(this.props.table.range, sheetId),
30598
30754
  filtersEnabledIfPossible: this.props.table.config.hasFilters,
30599
30755
  });
30600
- onWillUpdateProps((nextProps) => {
30601
- if (this.props.table.id !== nextProps.table.id) {
30602
- const sheetId = this.env.model.getters.getActiveSheetId();
30603
- this.state.tableXc = this.env.model.getters.getRangeString(nextProps.table.range, sheetId);
30604
- this.state.tableZoneErrors = [];
30605
- this.state.filtersEnabledIfPossible = nextProps.table.config.hasFilters;
30606
- }
30607
- });
30608
30756
  }
30609
30757
  updateHasFilters(hasFilters) {
30610
30758
  this.state.filtersEnabledIfPossible = hasFilters;
@@ -30753,7 +30901,11 @@ sidePanelRegistry.add("TableSidePanel", {
30753
30901
  if (!table) {
30754
30902
  return { isOpen: false };
30755
30903
  }
30756
- return { isOpen: true, props: { table } };
30904
+ return {
30905
+ isOpen: true,
30906
+ props: { table },
30907
+ key: table.id,
30908
+ };
30757
30909
  },
30758
30910
  });
30759
30911
 
@@ -32468,19 +32620,21 @@ class GridCellIcon extends Component {
32468
32620
  slots: Object,
32469
32621
  };
32470
32622
  get iconStyle() {
32471
- const x = this.getIconHorizontalPosition();
32472
- const y = this.getIconVerticalPosition();
32623
+ const cellPosition = this.props.cellPosition;
32624
+ const merge = this.env.model.getters.getMerge(cellPosition);
32625
+ const zone = merge || positionToZone(cellPosition);
32626
+ const rect = this.env.model.getters.getVisibleRectWithoutHeaders(zone);
32627
+ const x = this.getIconHorizontalPosition(rect, cellPosition);
32628
+ const y = this.getIconVerticalPosition(rect, cellPosition);
32473
32629
  return cssPropertiesToCss({
32474
32630
  top: `${y + (this.props.offset?.y || 0)}px`,
32475
32631
  left: `${x + (this.props.offset?.x || 0)}px`,
32476
32632
  });
32477
32633
  }
32478
- getIconVerticalPosition() {
32479
- const { sheetId, row } = this.props.cellPosition;
32480
- const merge = this.env.model.getters.getMerge(this.props.cellPosition);
32481
- const start = this.env.model.getters.getRowDimensionsInViewport(sheetId, row).start;
32482
- const end = this.env.model.getters.getRowDimensionsInViewport(sheetId, merge ? merge.bottom : row).end;
32483
- const cell = this.env.model.getters.getCell(this.props.cellPosition);
32634
+ getIconVerticalPosition(rect, cellPosition) {
32635
+ const start = rect.y;
32636
+ const end = rect.y + rect.height;
32637
+ const cell = this.env.model.getters.getCell(cellPosition);
32484
32638
  const align = this.props.verticalAlign || cell?.style?.verticalAlign || DEFAULT_VERTICAL_ALIGN;
32485
32639
  switch (align) {
32486
32640
  case "bottom":
@@ -32492,13 +32646,11 @@ class GridCellIcon extends Component {
32492
32646
  return end - GRID_ICON_EDGE_LENGTH - centeringOffset;
32493
32647
  }
32494
32648
  }
32495
- getIconHorizontalPosition() {
32496
- const { sheetId, col } = this.props.cellPosition;
32497
- const merge = this.env.model.getters.getMerge(this.props.cellPosition);
32498
- const start = this.env.model.getters.getColDimensionsInViewport(sheetId, col).start;
32499
- const end = this.env.model.getters.getColDimensionsInViewport(sheetId, merge ? merge.right : col).end;
32500
- const cell = this.env.model.getters.getCell(this.props.cellPosition);
32501
- const evaluatedCell = this.env.model.getters.getEvaluatedCell(this.props.cellPosition);
32649
+ getIconHorizontalPosition(rect, cellPosition) {
32650
+ const start = rect.x;
32651
+ const end = rect.x + rect.width;
32652
+ const cell = this.env.model.getters.getCell(cellPosition);
32653
+ const evaluatedCell = this.env.model.getters.getEvaluatedCell(cellPosition);
32502
32654
  const align = this.props.horizontalAlign || cell?.style?.align || evaluatedCell.defaultAlign;
32503
32655
  switch (align) {
32504
32656
  case "right":
@@ -32603,6 +32755,8 @@ css /* scss */ `
32603
32755
  height: ${CHECKBOX_WIDTH}px;
32604
32756
  accent-color: #808080;
32605
32757
  margin: ${MARGIN}px;
32758
+ /** required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
32759
+ position: absolute;
32606
32760
  }
32607
32761
  `;
32608
32762
  class DataValidationCheckbox extends Component {
@@ -32621,7 +32775,7 @@ class DataValidationCheckbox extends Component {
32621
32775
  }
32622
32776
  get isDisabled() {
32623
32777
  const cell = this.env.model.getters.getCell(this.props.cellPosition);
32624
- return !!cell?.isFormula;
32778
+ return this.env.model.getters.isReadonly() || !!cell?.isFormula;
32625
32779
  }
32626
32780
  }
32627
32781
 
@@ -32661,12 +32815,17 @@ class DataValidationOverlay extends Component {
32661
32815
  static props = {};
32662
32816
  static components = { GridCellIcon, DataValidationCheckbox, DataValidationListIcon };
32663
32817
  get checkBoxCellPositions() {
32664
- return this.env.model.getters.getDataValidationCheckBoxCellPositions();
32818
+ return this.env.model.getters
32819
+ .getVisibleCellPositions()
32820
+ .filter(this.env.model.getters.isCellValidCheckbox);
32665
32821
  }
32666
32822
  get listIconsCellPositions() {
32667
- return this.env.model.getters.isReadonly()
32668
- ? []
32669
- : this.env.model.getters.getDataValidationListCellsPositions();
32823
+ if (this.env.model.getters.isReadonly()) {
32824
+ return [];
32825
+ }
32826
+ return this.env.model.getters
32827
+ .getVisibleCellPositions()
32828
+ .filter(this.env.model.getters.cellHasListDataValidationIcon);
32670
32829
  }
32671
32830
  }
32672
32831
 
@@ -32711,7 +32870,7 @@ function dragFigureForMove({ x: mouseX, y: mouseY }, { x: mouseInitialX, y: mous
32711
32870
  const newY = clip(initialFigure.y + deltaY, minY, maxY - initialFigure.height - scrollY);
32712
32871
  return { ...initialFigure, x: newX, y: newY };
32713
32872
  }
32714
- function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, keepRatio, minFigSize) {
32873
+ function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, keepRatio, minFigSize, { scrollX, scrollY }) {
32715
32874
  let { x, y, width, height } = initialFigure;
32716
32875
  if (keepRatio && dirX != 0 && dirY != 0) {
32717
32876
  const deltaX = Math.min(dirX * (mouseInitialX - mouseX), initialFigure.width - minFigSize);
@@ -32738,14 +32897,14 @@ function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }
32738
32897
  y = initialFigure.y - deltaY;
32739
32898
  }
32740
32899
  }
32741
- // Restrict resizing if x or y reaches header boundaries
32742
- if (x < 0) {
32743
- width += x;
32744
- x = 0;
32900
+ // Adjusts figure dimensions to ensure it remains within header boundaries and viewport during resizing.
32901
+ if (x + scrollX <= 0) {
32902
+ width = width + x + scrollX;
32903
+ x = -scrollX;
32745
32904
  }
32746
- if (y < 0) {
32747
- height += y;
32748
- y = 0;
32905
+ if (y + scrollY <= 0) {
32906
+ height = height + y + scrollY;
32907
+ y = -scrollY;
32749
32908
  }
32750
32909
  return { ...initialFigure, x, y, width, height };
32751
32910
  }
@@ -33165,7 +33324,7 @@ class FiguresContainer extends Component {
33165
33324
  const minFigSize = figureRegistry.get(figure.tag).minFigSize || MIN_FIG_SIZE;
33166
33325
  const onMouseMove = (ev) => {
33167
33326
  const currentMousePosition = { x: ev.clientX, y: ev.clientY };
33168
- const draggedFigure = dragFigureForResize(initialFig, dirX, dirY, currentMousePosition, initialMousePosition, keepRatio, minFigSize);
33327
+ const draggedFigure = dragFigureForResize(initialFig, dirX, dirY, currentMousePosition, initialMousePosition, keepRatio, minFigSize, this.env.model.getters.getActiveSheetScrollInfo());
33169
33328
  const otherFigures = this.getOtherFigures(figure.id);
33170
33329
  const snapResult = snapForResize(this.env.model.getters, dirX, dirY, draggedFigure, otherFigures);
33171
33330
  this.dnd.draggedFigure = snapResult.snappedFigure;
@@ -33341,29 +33500,6 @@ class GridAddRowsFooter extends Component {
33341
33500
  }
33342
33501
  }
33343
33502
 
33344
- /**
33345
- * Repeatedly calls a callback function with a time delay between calls.
33346
- */
33347
- function useInterval(callback, delay) {
33348
- let intervalId;
33349
- const { setInterval, clearInterval } = window;
33350
- useEffect(() => {
33351
- intervalId = setInterval(callback, delay);
33352
- return () => clearInterval(intervalId);
33353
- }, () => [delay]);
33354
- return {
33355
- pause: () => {
33356
- clearInterval(intervalId);
33357
- intervalId = undefined;
33358
- },
33359
- resume: () => {
33360
- if (intervalId === undefined) {
33361
- intervalId = setInterval(callback, delay);
33362
- }
33363
- },
33364
- };
33365
- }
33366
-
33367
33503
  const CURSOR_SVG = /*xml*/ `
33368
33504
  <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>
33369
33505
  `;
@@ -35248,10 +35384,17 @@ class SidePanelStore extends SpreadsheetStore {
35248
35384
  get panelProps() {
35249
35385
  const state = this.computeState(this.componentTag, this.initialPanelProps);
35250
35386
  if (state.isOpen) {
35251
- return state.props;
35387
+ return state.props ?? {};
35252
35388
  }
35253
35389
  return {};
35254
35390
  }
35391
+ get panelKey() {
35392
+ const state = this.computeState(this.componentTag, this.initialPanelProps);
35393
+ if (state.isOpen) {
35394
+ return state.key;
35395
+ }
35396
+ return undefined;
35397
+ }
35255
35398
  open(componentTag, panelProps = {}) {
35256
35399
  const state = this.computeState(componentTag, panelProps);
35257
35400
  if (state.isOpen === false) {
@@ -35261,7 +35404,7 @@ class SidePanelStore extends SpreadsheetStore {
35261
35404
  this.initialPanelProps?.onCloseSidePanel?.();
35262
35405
  }
35263
35406
  this.componentTag = componentTag;
35264
- this.initialPanelProps = state.props;
35407
+ this.initialPanelProps = state.props ?? {};
35265
35408
  }
35266
35409
  toggle(componentTag, panelProps) {
35267
35410
  if (this.isOpen && componentTag === this.componentTag) {
@@ -40738,10 +40881,8 @@ function compileTokens(tokens) {
40738
40881
  "ref", // a function to access a certain dependency at a given index
40739
40882
  "range", // same as above, but guarantee that the result is in the form of a range
40740
40883
  "ctx", code.toString());
40741
- functionCache[cacheKey] = {
40742
- // @ts-ignore
40743
- execute: baseFunction,
40744
- };
40884
+ // @ts-ignore
40885
+ functionCache[cacheKey] = baseFunction;
40745
40886
  /**
40746
40887
  * This function compile the function arguments. It is mostly straightforward,
40747
40888
  * except that there is a non trivial transformation in one situation:
@@ -40848,7 +40989,7 @@ function compileTokens(tokens) {
40848
40989
  }
40849
40990
  }
40850
40991
  const compiledFormula = {
40851
- execute: functionCache[cacheKey].execute,
40992
+ execute: functionCache[cacheKey],
40852
40993
  dependencies,
40853
40994
  constantValues,
40854
40995
  tokens,
@@ -42333,6 +42474,15 @@ class FigurePlugin extends CorePlugin {
42333
42474
  return "Success" /* CommandResult.Success */;
42334
42475
  }
42335
42476
  }
42477
+ beforeHandle(cmd) {
42478
+ switch (cmd.type) {
42479
+ case "DELETE_SHEET":
42480
+ this.getters.getFigures(cmd.sheetId).forEach((figure) => {
42481
+ this.dispatch("DELETE_FIGURE", { id: figure.id, sheetId: cmd.sheetId });
42482
+ });
42483
+ break;
42484
+ }
42485
+ }
42336
42486
  handle(cmd) {
42337
42487
  switch (cmd.type) {
42338
42488
  case "CREATE_SHEET":
@@ -43819,7 +43969,6 @@ class SheetPlugin extends CorePlugin {
43819
43969
  "getNumberHeaders",
43820
43970
  "getGridLinesVisibility",
43821
43971
  "getNextSheetName",
43822
- "isEmpty",
43823
43972
  "getSheetSize",
43824
43973
  "getSheetZone",
43825
43974
  "getPaneDivisions",
@@ -44191,14 +44340,6 @@ class SheetPlugin extends CorePlugin {
44191
44340
  // ---------------------------------------------------------------------------
44192
44341
  // Row/Col manipulation
44193
44342
  // ---------------------------------------------------------------------------
44194
- /**
44195
- * Check if a zone only contains empty cells
44196
- */
44197
- isEmpty(sheetId, zone) {
44198
- return positions(zone)
44199
- .map(({ col, row }) => this.getCell({ sheetId, col, row }))
44200
- .every((cell) => !cell || cell.content === "");
44201
- }
44202
44343
  getCommandZones(cmd) {
44203
44344
  const zones = [];
44204
44345
  if ("zone" in cmd) {
@@ -45002,13 +45143,15 @@ class TablePlugin extends CorePlugin {
45002
45143
  if (zone.left !== zone.right) {
45003
45144
  throw new Error("Can only define a filter on a single column");
45004
45145
  }
45005
- const filteredZone = { ...zone, top: zone.top + config.numberOfHeaders };
45006
- const filteredRange = this.getters.getRangeFromZone(range.sheetId, filteredZone);
45146
+ const contentZone = getTableContentZone(zone, config);
45147
+ const filteredRange = contentZone
45148
+ ? this.getters.getRangeFromZone(range.sheetId, contentZone)
45149
+ : undefined;
45007
45150
  return {
45008
45151
  id,
45009
45152
  rangeWithHeaders: range,
45010
45153
  col: zone.left,
45011
- filteredRange: filteredZone.top > filteredZone.bottom ? undefined : filteredRange,
45154
+ filteredRange,
45012
45155
  };
45013
45156
  }
45014
45157
  copyTableForSheet(sheetId, table) {
@@ -45617,7 +45760,11 @@ class CompilationParametersBuilder {
45617
45760
  });
45618
45761
  }
45619
45762
  getParameters() {
45620
- return [this.refFn.bind(this), this.range.bind(this), this.evalContext];
45763
+ return {
45764
+ referenceDenormalizer: this.refFn.bind(this),
45765
+ ensureRange: this.range.bind(this),
45766
+ evalContext: this.evalContext,
45767
+ };
45621
45768
  }
45622
45769
  /**
45623
45770
  * Returns the value of the cell(s) used in reference
@@ -45698,6 +45845,41 @@ class CompilationParametersBuilder {
45698
45845
  }
45699
45846
  }
45700
45847
 
45848
+ class PositionMap {
45849
+ map = {};
45850
+ set({ sheetId, col, row }, value) {
45851
+ const map = this.map;
45852
+ if (!map[sheetId]) {
45853
+ map[sheetId] = {};
45854
+ }
45855
+ if (!map[sheetId][col]) {
45856
+ map[sheetId][col] = {};
45857
+ }
45858
+ map[sheetId][col][row] = value;
45859
+ }
45860
+ get({ sheetId, col, row }) {
45861
+ return this.map[sheetId]?.[col]?.[row];
45862
+ }
45863
+ has({ sheetId, col, row }) {
45864
+ return this.map[sheetId]?.[col]?.[row] !== undefined;
45865
+ }
45866
+ delete({ sheetId, col, row }) {
45867
+ delete this.map[sheetId]?.[col]?.[row];
45868
+ }
45869
+ keys() {
45870
+ const map = this.map;
45871
+ const keys = [];
45872
+ for (const sheetId in map) {
45873
+ for (const col in map[sheetId]) {
45874
+ for (const row in map[sheetId][col]) {
45875
+ keys.push({ sheetId, col: parseInt(col), row: parseInt(row) });
45876
+ }
45877
+ }
45878
+ }
45879
+ return keys;
45880
+ }
45881
+ }
45882
+
45701
45883
  function quickselect(arr, k, left, right, compare) {
45702
45884
  quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
45703
45885
  }
@@ -46424,26 +46606,26 @@ class ZoneRBush extends RBush {
46424
46606
  * It uses an R-Tree data structure to efficiently find dependent cells.
46425
46607
  */
46426
46608
  class FormulaDependencyGraph {
46427
- encoder;
46428
- dependencies = new Map();
46609
+ createEmptyPositionSet;
46610
+ dependencies = new PositionMap();
46429
46611
  rTree;
46430
- constructor(encoder, data = []) {
46431
- this.encoder = encoder;
46612
+ constructor(createEmptyPositionSet, data = []) {
46613
+ this.createEmptyPositionSet = createEmptyPositionSet;
46432
46614
  this.rTree = new SpreadsheetRTree(data);
46433
46615
  }
46434
- removeAllDependencies(formulaPositionId) {
46435
- const ranges = this.dependencies.get(formulaPositionId);
46616
+ removeAllDependencies(formulaPosition) {
46617
+ const ranges = this.dependencies.get(formulaPosition);
46436
46618
  if (!ranges) {
46437
46619
  return;
46438
46620
  }
46439
46621
  for (const range of ranges) {
46440
46622
  this.rTree.remove(range);
46441
46623
  }
46442
- this.dependencies.delete(formulaPositionId);
46624
+ this.dependencies.delete(formulaPosition);
46443
46625
  }
46444
- addDependencies(formulaPositionId, dependencies) {
46626
+ addDependencies(formulaPosition, dependencies) {
46445
46627
  const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
46446
- data: formulaPositionId,
46628
+ data: formulaPosition,
46447
46629
  boundingBox: {
46448
46630
  zone,
46449
46631
  sheetId,
@@ -46452,12 +46634,12 @@ class FormulaDependencyGraph {
46452
46634
  for (const item of rTreeItems) {
46453
46635
  this.rTree.insert(item);
46454
46636
  }
46455
- const existingDependencies = this.dependencies.get(formulaPositionId);
46637
+ const existingDependencies = this.dependencies.get(formulaPosition);
46456
46638
  if (existingDependencies) {
46457
46639
  existingDependencies.push(...rTreeItems);
46458
46640
  }
46459
46641
  else {
46460
- this.dependencies.set(formulaPositionId, rTreeItems);
46642
+ this.dependencies.set(formulaPosition, rTreeItems);
46461
46643
  }
46462
46644
  }
46463
46645
  /**
@@ -46466,23 +46648,194 @@ class FormulaDependencyGraph {
46466
46648
  * This is called a topological ordering (excluding cycles)
46467
46649
  */
46468
46650
  getCellsDependingOn(ranges) {
46469
- const visited = new JetSet();
46651
+ const visited = this.createEmptyPositionSet();
46470
46652
  const queue = Array.from(ranges).reverse();
46471
46653
  while (queue.length > 0) {
46472
46654
  const range = queue.pop();
46473
- visited.addMany(this.encoder.encodeBoundingBox(range));
46474
- const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
46475
- for (const positionId of impactedPositionIds) {
46476
- if (!visited.has(positionId)) {
46477
- queue.push(this.encoder.decodeToBoundingBox(positionId));
46655
+ visited.addMany(positions(range.zone).map((position) => ({ sheetId: range.sheetId, ...position })));
46656
+ const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
46657
+ for (const position of impactedPositions) {
46658
+ if (!visited.has(position)) {
46659
+ queue.push({ sheetId: position.sheetId, zone: positionToZone(position) });
46478
46660
  }
46479
46661
  }
46480
46662
  }
46481
- visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
46663
+ visited.deleteMany(ranges.flatMap((r) => positions(r.zone).map((position) => ({ sheetId: r.sheetId, ...position }))));
46482
46664
  return visited;
46483
46665
  }
46484
46666
  }
46485
46667
 
46668
+ /**
46669
+ * Implements a fixed-sized grid or 2D matrix of bits.
46670
+ * based on https://github.com/zandaqo/structurae
46671
+ *
46672
+ * The grid is implemented as a 1D array of 32-bit integers, where each bit represents a cell in the grid.
46673
+ * It follows row-major order, with each row stored consecutively in 32-bit blocks.
46674
+ * Pads the number of columns to the next power of 2 to allow quick lookups with bitwise operations.
46675
+ *
46676
+ * Key terminology:
46677
+ * - bucket: Index of an item in the Uint32Array, a 32-bit integer.
46678
+ * - bitPosition: The position of a bit within the bucket 32-bit integer.
46679
+ */
46680
+ class BinaryGrid extends Uint32Array {
46681
+ columnOffset = 0;
46682
+ cols = 0;
46683
+ rows = 0;
46684
+ /**
46685
+ * Creates a binary grid of specified dimensions.
46686
+ */
46687
+ static create(rows, columns) {
46688
+ const columnOffset = log2Ceil(columns);
46689
+ const length = (rows << columnOffset) >> 5;
46690
+ const grid = new this(length + 1);
46691
+ grid.columnOffset = columnOffset;
46692
+ grid.cols = columns;
46693
+ grid.rows = rows;
46694
+ return grid;
46695
+ }
46696
+ /**
46697
+ * Returns the bit at given coordinates.
46698
+ */
46699
+ getValue(position) {
46700
+ const [bucket, bitPosition] = this.getCoordinates(position);
46701
+ return ((this[bucket] >> bitPosition) & 1);
46702
+ }
46703
+ /**
46704
+ * Sets the bit at given coordinates.
46705
+ */
46706
+ setValue(position, value) {
46707
+ const [bucket, bitPosition] = this.getCoordinates(position);
46708
+ const currentValue = (this[bucket] >> bitPosition) & 1;
46709
+ const hasBeenInserted = currentValue === 0 && value === 1;
46710
+ this[bucket] = (this[bucket] & ~(1 << bitPosition)) | (value << bitPosition);
46711
+ return hasBeenInserted;
46712
+ // Let's breakdown of the above line:
46713
+ // with an example with a 4-bit integer (instead of 32-bit).
46714
+ //
46715
+ // Let's say we want to set the bit at position 2 to 1 and the existing
46716
+ // bit sequence this[bucket] is 1001. The final bit sequence should be 1101.
46717
+ //
46718
+ // First, we clear the bit at position 2 by AND-ing this[bucket] with a
46719
+ // mask having all 1s except a 0 at the bit position (~ (1 << bitPosition)).
46720
+ // 1 << bitPosition is 0100 (shifting 0001 to the left by 2)
46721
+ // Inverting the bits with ~ gives the final mask ~(1 << bitPosition): 1011
46722
+ //
46723
+ // Then, we shift the value by the bit position (value << bitPosition: 0100)
46724
+ // and OR the result with the previous step's result:
46725
+ // (1001 & 1011) | 0100 = 1101
46726
+ }
46727
+ isEmpty() {
46728
+ return !this.some((bucket) => bucket !== 0);
46729
+ }
46730
+ fillAllPositions() {
46731
+ const thirtyTwoOnes = -1 >>> 0; // same as 2 ** 32 - 1, a 32-bit number with all bits set to 1
46732
+ this.fill(thirtyTwoOnes);
46733
+ }
46734
+ clear() {
46735
+ this.fill(0);
46736
+ }
46737
+ getCoordinates(position) {
46738
+ const { row, col } = position;
46739
+ const index = (row << this.columnOffset) + col;
46740
+ const bucket = index >> 5;
46741
+ return [bucket, index - (bucket << 5)];
46742
+ }
46743
+ }
46744
+ function log2Ceil(value) {
46745
+ // A faster version of Math.ceil(Math.log2(value)).
46746
+ if (value === 0) {
46747
+ return -Infinity;
46748
+ }
46749
+ else if (value < 0) {
46750
+ return NaN;
46751
+ }
46752
+ // --value handles the case where value is a power of 2
46753
+ return 32 - Math.clz32(--value);
46754
+ }
46755
+
46756
+ class PositionSet {
46757
+ sheets = {};
46758
+ /**
46759
+ * List of positions in the order they were inserted.
46760
+ */
46761
+ insertions = [];
46762
+ maxSize = 0;
46763
+ constructor(sheetSizes) {
46764
+ for (const sheetId in sheetSizes) {
46765
+ const cols = sheetSizes[sheetId].cols;
46766
+ const rows = sheetSizes[sheetId].rows;
46767
+ this.maxSize += cols * rows;
46768
+ this.sheets[sheetId] = BinaryGrid.create(rows, cols);
46769
+ }
46770
+ }
46771
+ add(position) {
46772
+ const hasBeenInserted = this.sheets[position.sheetId].setValue(position, 1);
46773
+ if (hasBeenInserted) {
46774
+ this.insertions.push(position);
46775
+ }
46776
+ }
46777
+ addMany(positions) {
46778
+ for (const position of positions) {
46779
+ this.add(position);
46780
+ }
46781
+ }
46782
+ delete(position) {
46783
+ this.sheets[position.sheetId].setValue(position, 0);
46784
+ }
46785
+ deleteMany(positions) {
46786
+ for (const position of positions) {
46787
+ this.delete(position);
46788
+ }
46789
+ }
46790
+ has(position) {
46791
+ return this.sheets[position.sheetId].getValue(position) === 1;
46792
+ }
46793
+ clear() {
46794
+ const insertions = this.insertions;
46795
+ this.insertions = [];
46796
+ for (const sheetId in this.sheets) {
46797
+ this.sheets[sheetId].clear();
46798
+ }
46799
+ return insertions;
46800
+ }
46801
+ isEmpty() {
46802
+ if (this.insertions.length === 0) {
46803
+ return true;
46804
+ }
46805
+ for (const sheetId in this.sheets) {
46806
+ if (!this.sheets[sheetId].isEmpty()) {
46807
+ return false;
46808
+ }
46809
+ }
46810
+ return true;
46811
+ }
46812
+ fillAllPositions() {
46813
+ this.insertions = new Array(this.maxSize);
46814
+ let index = 0;
46815
+ for (const sheetId in this.sheets) {
46816
+ const grid = this.sheets[sheetId];
46817
+ grid.fillAllPositions();
46818
+ for (let i = 0; i < grid.rows; i++) {
46819
+ for (let j = 0; j < grid.cols; j++) {
46820
+ this.insertions[index++] = { sheetId, row: i, col: j };
46821
+ }
46822
+ }
46823
+ }
46824
+ }
46825
+ /**
46826
+ * Iterate over the positions in the order of insertion.
46827
+ * Note that the same position may be yielded multiple times if the value was added
46828
+ * to the set then removed and then added again.
46829
+ */
46830
+ *[Symbol.iterator]() {
46831
+ for (const position of this.insertions) {
46832
+ if (this.sheets[position.sheetId].getValue(position) === 1) {
46833
+ yield position;
46834
+ }
46835
+ }
46836
+ }
46837
+ }
46838
+
46486
46839
  /**
46487
46840
  * Contains, for each cell, the array
46488
46841
  * formulas that could potentially spread on it
@@ -46496,6 +46849,7 @@ class FormulaDependencyGraph {
46496
46849
  *
46497
46850
  */
46498
46851
  class SpreadingRelation {
46852
+ createEmptyPositionSet;
46499
46853
  /**
46500
46854
  * Internal structure:
46501
46855
  * For something like
@@ -46524,39 +46878,42 @@ class SpreadingRelation {
46524
46878
  * - (B1) --> (B2, B3, B4) meaning B1 spreads on B2, B3 and B4
46525
46879
  *
46526
46880
  */
46527
- resultsToArrayFormulas = new Map();
46528
- arrayFormulasToResults = new Map();
46529
- getFormulaPositionsSpreadingOn(resultPositionId) {
46530
- return this.resultsToArrayFormulas.get(resultPositionId) || EMPTY_ARRAY;
46881
+ resultsToArrayFormulas = new PositionMap();
46882
+ arrayFormulasToResults = new PositionMap();
46883
+ constructor(createEmptyPositionSet) {
46884
+ this.createEmptyPositionSet = createEmptyPositionSet;
46885
+ }
46886
+ getFormulaPositionsSpreadingOn(resultPosition) {
46887
+ return this.resultsToArrayFormulas.get(resultPosition) || EMPTY_ARRAY;
46531
46888
  }
46532
- getArrayResultPositionIds(formulasPositionId) {
46533
- return this.arrayFormulasToResults.get(formulasPositionId) || EMPTY_ARRAY;
46889
+ getArrayResultPositions(formulasPosition) {
46890
+ return this.arrayFormulasToResults.get(formulasPosition) || EMPTY_ARRAY;
46534
46891
  }
46535
46892
  /**
46536
46893
  * Remove a node, also remove it from other nodes adjacency list
46537
46894
  */
46538
- removeNode(positionId) {
46539
- this.resultsToArrayFormulas.delete(positionId);
46540
- this.arrayFormulasToResults.delete(positionId);
46895
+ removeNode(position) {
46896
+ this.resultsToArrayFormulas.delete(position);
46897
+ this.arrayFormulasToResults.delete(position);
46541
46898
  }
46542
46899
  /**
46543
46900
  * Create a spreading relation between two cells
46544
46901
  */
46545
- addRelation({ arrayFormulaPositionId, resultPositionId, }) {
46546
- if (!this.resultsToArrayFormulas.has(resultPositionId)) {
46547
- this.resultsToArrayFormulas.set(resultPositionId, new Set());
46902
+ addRelation({ arrayFormulaPosition, resultPosition, }) {
46903
+ if (!this.resultsToArrayFormulas.has(resultPosition)) {
46904
+ this.resultsToArrayFormulas.set(resultPosition, this.createEmptyPositionSet());
46548
46905
  }
46549
- this.resultsToArrayFormulas.get(resultPositionId)?.add(arrayFormulaPositionId);
46550
- if (!this.arrayFormulasToResults.has(arrayFormulaPositionId)) {
46551
- this.arrayFormulasToResults.set(arrayFormulaPositionId, new Set());
46906
+ this.resultsToArrayFormulas.get(resultPosition)?.add(arrayFormulaPosition);
46907
+ if (!this.arrayFormulasToResults.has(arrayFormulaPosition)) {
46908
+ this.arrayFormulasToResults.set(arrayFormulaPosition, this.createEmptyPositionSet());
46552
46909
  }
46553
- this.arrayFormulasToResults.get(arrayFormulaPositionId)?.add(resultPositionId);
46910
+ this.arrayFormulasToResults.get(arrayFormulaPosition)?.add(resultPosition);
46554
46911
  }
46555
- hasArrayFormulaResult(positionId) {
46556
- return this.resultsToArrayFormulas.has(positionId);
46912
+ hasArrayFormulaResult(position) {
46913
+ return this.resultsToArrayFormulas.has(position);
46557
46914
  }
46558
- isArrayFormula(positionId) {
46559
- return this.arrayFormulasToResults.has(positionId);
46915
+ isArrayFormula(position) {
46916
+ return this.arrayFormulasToResults.has(position);
46560
46917
  }
46561
46918
  }
46562
46919
  const EMPTY_ARRAY = [];
@@ -46564,104 +46921,103 @@ const EMPTY_ARRAY = [];
46564
46921
  const MAX_ITERATION = 30;
46565
46922
  const ERROR_CYCLE_CELL = createEvaluatedCell(new CircularDependencyError());
46566
46923
  const EMPTY_CELL = createEvaluatedCell({ value: null });
46567
- const EVAL_CONTEXT_INDEX = 2;
46568
46924
  class Evaluator {
46569
46925
  context;
46570
46926
  getters;
46571
46927
  compilationParams;
46572
- encoder = new PositionBitsEncoder();
46573
- evaluatedCells = new Map();
46574
- formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
46575
- blockedArrayFormulas = new Set();
46576
- spreadingRelations = new SpreadingRelation();
46928
+ evaluatedCells = new PositionMap();
46929
+ formulaDependencies = lazy(new FormulaDependencyGraph(this.createEmptyPositionSet.bind(this)));
46930
+ blockedArrayFormulas = new PositionSet({});
46931
+ spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
46577
46932
  constructor(context, getters) {
46578
46933
  this.context = context;
46579
46934
  this.getters = getters;
46580
46935
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
46581
46936
  }
46582
46937
  getEvaluatedCell(position) {
46583
- return this.evaluatedCells.get(this.encoder.encode(position)) || EMPTY_CELL;
46938
+ return this.evaluatedCells.get(position) || EMPTY_CELL;
46584
46939
  }
46585
46940
  getSpreadPositionsOf(position) {
46586
- const positionId = this.encoder.encode(position);
46587
- if (!this.spreadingRelations.isArrayFormula(positionId)) {
46941
+ if (!this.spreadingRelations.isArrayFormula(position)) {
46588
46942
  return [];
46589
46943
  }
46590
- return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map((positionId) => this.encoder.decode(positionId));
46591
- }
46592
- getArrayFormulaSpreadingOn(position) {
46593
- const positionId = this.encoder.encode(position);
46594
- const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
46595
- return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
46944
+ return Array.from(this.spreadingRelations.getArrayResultPositions(position));
46596
46945
  }
46597
46946
  getEvaluatedPositions() {
46598
- return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
46947
+ return this.evaluatedCells.keys();
46599
46948
  }
46600
- getArrayFormulaSpreadingOnId(positionId) {
46601
- if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
46949
+ getArrayFormulaSpreadingOn(position) {
46950
+ if (!this.spreadingRelations.hasArrayFormulaResult(position)) {
46602
46951
  return undefined;
46603
46952
  }
46604
- const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
46605
- return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
46953
+ const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(position);
46954
+ return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
46606
46955
  }
46607
46956
  updateDependencies(position) {
46608
- const positionId = this.encoder.encode(position);
46609
- this.formulaDependencies().removeAllDependencies(positionId);
46610
- const dependencies = this.getDirectDependencies(positionId);
46611
- this.formulaDependencies().addDependencies(positionId, dependencies);
46957
+ this.formulaDependencies().removeAllDependencies(position);
46958
+ const dependencies = this.getDirectDependencies(position);
46959
+ this.formulaDependencies().addDependencies(position, dependencies);
46612
46960
  }
46613
46961
  addDependencies(position, dependencies) {
46614
- const positionId = this.encoder.encode(position);
46615
- this.formulaDependencies().addDependencies(positionId, dependencies);
46962
+ this.formulaDependencies().addDependencies(position, dependencies);
46616
46963
  }
46617
46964
  updateCompilationParameters() {
46618
46965
  // rebuild the compilation parameters (with a clean cache)
46619
46966
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
46620
- this.compilationParams[EVAL_CONTEXT_INDEX].updateDependencies =
46621
- this.updateDependencies.bind(this);
46622
- this.compilationParams[EVAL_CONTEXT_INDEX].addDependencies = this.addDependencies.bind(this);
46967
+ this.compilationParams.evalContext.updateDependencies = this.updateDependencies.bind(this);
46968
+ this.compilationParams.evalContext.addDependencies = this.addDependencies.bind(this);
46969
+ }
46970
+ createEmptyPositionSet() {
46971
+ const sheetSizes = {};
46972
+ for (const sheetId of this.getters.getSheetIds()) {
46973
+ sheetSizes[sheetId] = {
46974
+ rows: this.getters.getNumberRows(sheetId),
46975
+ cols: this.getters.getNumberCols(sheetId),
46976
+ };
46977
+ }
46978
+ return new PositionSet(sheetSizes);
46623
46979
  }
46624
46980
  evaluateCells(positions) {
46625
- const cells = positions.map((p) => this.encoder.encode(p));
46626
- const cellsToCompute = new JetSet(cells);
46627
- const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
46628
- cellsToCompute.addMany(this.getCellsDependingOn(cells));
46629
- cellsToCompute.addMany(arrayFormulasPositionIds);
46630
- cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositionIds));
46981
+ const cellsToCompute = this.createEmptyPositionSet();
46982
+ cellsToCompute.addMany(positions);
46983
+ const arrayFormulasPositions = this.getArrayFormulasImpactedByChangesOf(positions);
46984
+ cellsToCompute.addMany(this.getCellsDependingOn(positions));
46985
+ cellsToCompute.addMany(arrayFormulasPositions);
46986
+ cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
46631
46987
  this.evaluate(cellsToCompute);
46632
46988
  }
46633
- getArrayFormulasImpactedByChangesOf(positionIds) {
46634
- const impactedPositionIds = new JetSet();
46635
- for (const positionId of positionIds) {
46636
- const content = this.getCell(positionId)?.content;
46637
- const arrayFormulaPositionId = this.getArrayFormulaSpreadingOnId(positionId);
46638
- if (arrayFormulaPositionId !== undefined) {
46989
+ getArrayFormulasImpactedByChangesOf(positions) {
46990
+ const impactedPositions = this.createEmptyPositionSet();
46991
+ for (const position of positions) {
46992
+ const content = this.getters.getCell(position)?.content;
46993
+ const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
46994
+ if (arrayFormulaPosition !== undefined) {
46639
46995
  // take into account new collisions.
46640
- impactedPositionIds.add(arrayFormulaPositionId);
46996
+ impactedPositions.add(arrayFormulaPosition);
46641
46997
  }
46642
46998
  if (!content) {
46643
46999
  // The previous content could have blocked some array formulas
46644
- impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
47000
+ impactedPositions.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(position));
46645
47001
  }
46646
47002
  }
46647
- return impactedPositionIds;
47003
+ return impactedPositions;
46648
47004
  }
46649
47005
  buildDependencyGraph() {
46650
- this.blockedArrayFormulas = new Set();
46651
- this.spreadingRelations = new SpreadingRelation();
47006
+ this.blockedArrayFormulas = this.createEmptyPositionSet();
47007
+ this.spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
46652
47008
  this.formulaDependencies = lazy(() => {
46653
- const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
46654
- data: positionId,
47009
+ const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position).map((range) => ({
47010
+ data: position,
46655
47011
  boundingBox: {
46656
47012
  zone: range.zone,
46657
47013
  sheetId: range.sheetId,
46658
47014
  },
46659
47015
  })));
46660
- return new FormulaDependencyGraph(this.encoder, dependencies);
47016
+ return new FormulaDependencyGraph(this.createEmptyPositionSet.bind(this), dependencies);
46661
47017
  });
46662
47018
  }
46663
47019
  evaluateAllCells() {
46664
- this.evaluatedCells = new Map();
47020
+ this.evaluatedCells = new PositionMap();
46665
47021
  this.evaluate(this.getAllCells());
46666
47022
  }
46667
47023
  evaluateFormula(sheetId, formulaString) {
@@ -46678,57 +47034,50 @@ class Evaluator {
46678
47034
  return result.value;
46679
47035
  }
46680
47036
  getAllCells() {
46681
- const positionIds = new JetSet();
46682
- for (const sheetId of this.getters.getSheetIds()) {
46683
- const cellIds = this.getters.getCells(sheetId);
46684
- for (const cellId in cellIds) {
46685
- positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
46686
- }
46687
- }
46688
- return positionIds;
47037
+ const positions = this.createEmptyPositionSet();
47038
+ positions.fillAllPositions();
47039
+ return positions;
46689
47040
  }
46690
- getArrayFormulasBlockedByOrSpreadingOn(positionId) {
46691
- if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
47041
+ getArrayFormulasBlockedByOrSpreadingOn(position) {
47042
+ if (!this.spreadingRelations.hasArrayFormulaResult(position)) {
46692
47043
  return [];
46693
47044
  }
46694
- const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
46695
- const cells = new JetSet(arrayFormulas);
46696
- cells.addMany(this.getCellsDependingOn(arrayFormulas));
46697
- return cells;
47045
+ const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(position);
47046
+ const positions = this.createEmptyPositionSet();
47047
+ positions.addMany(arrayFormulas);
47048
+ positions.addMany(this.getCellsDependingOn(arrayFormulas));
47049
+ return positions;
46698
47050
  }
46699
- nextPositionsToUpdate = new JetSet();
47051
+ nextPositionsToUpdate = new PositionSet({});
46700
47052
  cellsBeingComputed = new Set();
46701
- evaluate(cells) {
47053
+ evaluate(positions) {
46702
47054
  this.cellsBeingComputed = new Set();
46703
- this.nextPositionsToUpdate = cells;
47055
+ this.nextPositionsToUpdate = positions;
46704
47056
  let currentIteration = 0;
46705
- while (this.nextPositionsToUpdate.size && currentIteration++ < MAX_ITERATION) {
47057
+ while (!this.nextPositionsToUpdate.isEmpty() && currentIteration++ < MAX_ITERATION) {
46706
47058
  this.updateCompilationParameters();
46707
- const positionIds = Array.from(this.nextPositionsToUpdate);
46708
- this.nextPositionsToUpdate.clear();
46709
- for (let i = 0; i < positionIds.length; ++i) {
46710
- const cell = positionIds[i];
46711
- this.evaluatedCells.delete(cell);
47059
+ const positions = this.nextPositionsToUpdate.clear();
47060
+ for (let i = 0; i < positions.length; ++i) {
47061
+ this.evaluatedCells.delete(positions[i]);
46712
47062
  }
46713
- for (let i = 0; i < positionIds.length; ++i) {
46714
- const cell = positionIds[i];
46715
- this.setEvaluatedCell(cell, this.computeCell(cell));
47063
+ for (let i = 0; i < positions.length; ++i) {
47064
+ const position = positions[i];
47065
+ const evaluatedCell = this.computeCell(position);
47066
+ if (evaluatedCell !== EMPTY_CELL) {
47067
+ this.evaluatedCells.set(position, evaluatedCell);
47068
+ }
46716
47069
  }
46717
47070
  }
46718
47071
  }
46719
- setEvaluatedCell(positionId, evaluatedCell) {
46720
- this.evaluatedCells.set(positionId, evaluatedCell);
46721
- }
46722
- computeCell(positionId) {
46723
- const evaluation = this.evaluatedCells.get(positionId);
47072
+ computeCell(position) {
47073
+ const evaluation = this.evaluatedCells.get(position);
46724
47074
  if (evaluation) {
46725
47075
  return evaluation; // already computed
46726
47076
  }
46727
- if (!this.blockedArrayFormulas.has(positionId)) {
46728
- this.invalidateSpreading(positionId);
47077
+ if (!this.blockedArrayFormulas.has(position)) {
47078
+ this.invalidateSpreading(position);
46729
47079
  }
46730
- const cellPosition = this.encoder.decode(positionId);
46731
- const cell = this.getters.getCell(cellPosition);
47080
+ const cell = this.getters.getCell(position);
46732
47081
  if (cell === undefined) {
46733
47082
  return EMPTY_CELL;
46734
47083
  }
@@ -46740,10 +47089,12 @@ class Evaluator {
46740
47089
  }
46741
47090
  this.cellsBeingComputed.add(cellId);
46742
47091
  return cell.isFormula
46743
- ? this.computeFormulaCell(cellPosition.sheetId, cell)
47092
+ ? this.computeFormulaCell(position.sheetId, cell)
46744
47093
  : evaluateLiteral(cell.content, localeFormat);
46745
47094
  }
46746
47095
  catch (e) {
47096
+ e.value = e?.value || CellErrorType.GenericError;
47097
+ e.message = e?.message || implementationErrorMessage;
46747
47098
  return createEvaluatedCell(e);
46748
47099
  }
46749
47100
  finally {
@@ -46751,10 +47102,9 @@ class Evaluator {
46751
47102
  }
46752
47103
  }
46753
47104
  computeAndSave(position) {
46754
- const positionId = this.encoder.encode(position);
46755
- const evaluatedCell = this.computeCell(positionId);
46756
- if (!this.evaluatedCells.has(positionId)) {
46757
- this.setEvaluatedCell(positionId, evaluatedCell);
47105
+ const evaluatedCell = this.computeCell(position);
47106
+ if (!this.evaluatedCells.has(position)) {
47107
+ this.evaluatedCells.set(position, evaluatedCell);
46758
47108
  }
46759
47109
  return evaluatedCell;
46760
47110
  }
@@ -46792,24 +47142,23 @@ class Evaluator {
46792
47142
  throw new EvaluationError(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
46793
47143
  }
46794
47144
  updateSpreadRelation({ sheetId, col, row, }) {
46795
- const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
47145
+ const arrayFormulaPosition = { sheetId, col, row };
46796
47146
  return (i, j) => {
46797
47147
  const position = { sheetId, col: i + col, row: j + row };
46798
- const resultPositionId = this.encoder.encode(position);
46799
- this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
47148
+ this.spreadingRelations.addRelation({ resultPosition: position, arrayFormulaPosition });
46800
47149
  };
46801
47150
  }
46802
- checkCollision({ sheetId, col, row }) {
46803
- const formulaPositionId = this.encoder.encode({ sheetId, col, row });
47151
+ checkCollision(formulaPosition) {
47152
+ const { sheetId, col, row } = formulaPosition;
46804
47153
  return (i, j) => {
46805
47154
  const position = { sheetId: sheetId, col: i + col, row: j + row };
46806
47155
  const rawCell = this.getters.getCell(position);
46807
47156
  if (rawCell?.content ||
46808
47157
  this.getters.getEvaluatedCell(position).type !== CellValueType.empty) {
46809
- this.blockedArrayFormulas.add(formulaPositionId);
47158
+ this.blockedArrayFormulas.add(formulaPosition);
46810
47159
  throw new EvaluationError(_t("Array result was not expanded because it would overwrite data in %s.", toXC(position.col, position.row)));
46811
47160
  }
46812
- this.blockedArrayFormulas.delete(formulaPositionId);
47161
+ this.blockedArrayFormulas.delete(formulaPosition);
46813
47162
  };
46814
47163
  }
46815
47164
  spreadValues({ sheetId, col, row }, matrixResult) {
@@ -46817,19 +47166,18 @@ class Evaluator {
46817
47166
  const position = { sheetId, col: i + col, row: j + row };
46818
47167
  const cell = this.getters.getCell(position);
46819
47168
  const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(matrixResult[i][j]), this.getters.getLocale(), cell);
46820
- const positionId = this.encoder.encode(position);
46821
- this.setEvaluatedCell(positionId, evaluatedCell);
47169
+ this.evaluatedCells.set(position, evaluatedCell);
46822
47170
  // check if formula dependencies present in the spread zone
46823
47171
  // if so, they need to be recomputed
46824
- this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([positionId]));
47172
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([position]));
46825
47173
  };
46826
47174
  }
46827
- invalidateSpreading(positionId) {
46828
- if (!this.spreadingRelations.isArrayFormula(positionId)) {
47175
+ invalidateSpreading(position) {
47176
+ if (!this.spreadingRelations.isArrayFormula(position)) {
46829
47177
  return;
46830
47178
  }
46831
- for (const child of this.spreadingRelations.getArrayResultPositionIds(positionId)) {
46832
- const content = this.getCell(child)?.content;
47179
+ for (const child of this.spreadingRelations.getArrayResultPositions(position)) {
47180
+ const content = this.getters.getCell(child)?.content;
46833
47181
  if (content) {
46834
47182
  // there's no point at re-evaluating overlapping array formulas,
46835
47183
  // there's still a collision
@@ -46839,28 +47187,25 @@ class Evaluator {
46839
47187
  this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
46840
47188
  this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
46841
47189
  }
46842
- this.spreadingRelations.removeNode(positionId);
47190
+ this.spreadingRelations.removeNode(position);
46843
47191
  }
46844
47192
  // ----------------------------------------------------------
46845
47193
  // COMMON FUNCTIONALITY
46846
47194
  // ----------------------------------------------------------
46847
- getDirectDependencies(positionId) {
46848
- const cell = this.getCell(positionId);
47195
+ getDirectDependencies(position) {
47196
+ const cell = this.getters.getCell(position);
46849
47197
  if (!cell?.isFormula) {
46850
47198
  return [];
46851
47199
  }
46852
47200
  return cell.compiledFormula.dependencies;
46853
47201
  }
46854
- getCellsDependingOn(positionIds) {
47202
+ getCellsDependingOn(positions) {
46855
47203
  const ranges = [];
46856
- for (const positionId of positionIds) {
46857
- ranges.push(this.encoder.decodeToBoundingBox(positionId));
47204
+ for (const position of positions) {
47205
+ ranges.push({ sheetId: position.sheetId, zone: positionToZone(position) });
46858
47206
  }
46859
47207
  return this.formulaDependencies().getCellsDependingOn(ranges);
46860
47208
  }
46861
- getCell(positionId) {
46862
- return this.getters.getCell(this.encoder.decode(positionId));
46863
- }
46864
47209
  }
46865
47210
  function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
46866
47211
  for (let i = 0; i < nbColumns; ++i) {
@@ -46886,96 +47231,17 @@ function nullValueToZeroValue(fPayload) {
46886
47231
  }
46887
47232
  return fPayload;
46888
47233
  }
46889
- /**
46890
- * Encode (and decode) cell positions { sheetId, col, row }
46891
- * to a single integer.
46892
- *
46893
- * `col` and `row` values are encoded on 21 bits each (max 2^21 = 2_097_152),
46894
- * An incremental integer id is assigned to each different sheet id, starting at 0.
46895
- *
46896
- * e.g.
46897
- * Given { col: 10, row: 4, sheetId: "abcde" }
46898
- * we have:
46899
- * - row "4" encoded on 21 bits: 000000000000000000100
46900
- * - col "10" encoded on 21 bits: 000000000000000001010
46901
- * - sheetId: let's say it's the 4th sheetId met, encoded to: 11
46902
- *
46903
- * The final encoded value is found by concatenating the 3 bit sequences:
46904
- *
46905
- * sheetId: 11
46906
- * col: 000000000000000001010
46907
- * row: 000000000000000000100
46908
- * => 11000000000000000001010000000000000000000100
46909
- *
46910
- * this binary sequence is the integer 13194160504836
46911
- */
46912
- class PositionBitsEncoder {
46913
- sheetMapping = {};
46914
- inverseSheetMapping = new Map();
46915
- constructor() {
46916
- try {
46917
- // @ts-ignore
46918
- o_spreadsheet.__DEBUG__ = o_spreadsheet.__DEBUG__ || {};
46919
- // @ts-ignore
46920
- o_spreadsheet.__DEBUG__.decodePosition = this.decode.bind(this);
46921
- // @ts-ignore
46922
- o_spreadsheet.__DEBUG__.encodePosition = this.encode.bind(this);
46923
- }
46924
- catch (error) { }
46925
- }
46926
- /**
46927
- * Encode a cell position to a single integer.
46928
- */
46929
- encode({ sheetId, col, row }) {
46930
- return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
46931
- }
46932
- encodeBoundingBox({ sheetId, zone }) {
46933
- const positions = [];
46934
- forEachPositionsInZone(zone, (col, row) => {
46935
- positions.push(this.encode({ sheetId, col, row }));
46936
- });
46937
- return positions;
46938
- }
46939
- decode(id) {
46940
- // keep only the last 21 bits by AND-ing the bit sequence with 21 ones
46941
- const row = Number(id & 2097151n);
46942
- const col = Number((id >> 21n) & 2097151n);
46943
- const sheetId = this.decodeSheet(id >> 42n);
46944
- return { sheetId, col, row };
46945
- }
46946
- decodeToBoundingBox(id) {
46947
- const { sheetId, col, row } = this.decode(id);
46948
- return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
46949
- }
46950
- encodeSheet(sheetId) {
46951
- const sheetKey = this.sheetMapping[sheetId];
46952
- if (sheetKey === undefined) {
46953
- const newSheetKey = BigInt(Object.keys(this.sheetMapping).length);
46954
- this.sheetMapping[sheetId] = newSheetKey;
46955
- this.inverseSheetMapping.set(newSheetKey, sheetId);
46956
- return newSheetKey;
46957
- }
46958
- return sheetKey;
46959
- }
46960
- decodeSheet(sheetKey) {
46961
- const sheetId = this.inverseSheetMapping.get(sheetKey);
46962
- if (sheetId === undefined) {
46963
- throw new Error("Sheet id not found");
46964
- }
46965
- return sheetId;
46966
- }
46967
- }
46968
47234
  function updateEvalContextAndExecute(compiledFormula, compilationParams, sheetId, cellId) {
46969
- compilationParams[EVAL_CONTEXT_INDEX].__originCellXC = lazy(() => {
47235
+ compilationParams.evalContext.__originCellXC = lazy(() => {
46970
47236
  if (!cellId) {
46971
47237
  return undefined;
46972
47238
  }
46973
47239
  // compute the value lazily for performance reasons
46974
- const position = compilationParams[EVAL_CONTEXT_INDEX].getters.getCellPosition(cellId);
47240
+ const position = compilationParams.evalContext.getters.getCellPosition(cellId);
46975
47241
  return toXC(position.col, position.row);
46976
47242
  });
46977
- compilationParams[EVAL_CONTEXT_INDEX].__originSheetId = sheetId;
46978
- return compiledFormula.execute(compiledFormula.dependencies, ...compilationParams);
47243
+ compilationParams.evalContext.__originSheetId = sheetId;
47244
+ return compiledFormula.execute(compiledFormula.dependencies, compilationParams.referenceDenormalizer, compilationParams.ensureRange, compilationParams.evalContext);
46979
47245
  }
46980
47246
 
46981
47247
  //#region
@@ -47082,6 +47348,7 @@ class EvaluationPlugin extends UIPlugin {
47082
47348
  "getEvaluatedCellsInZone",
47083
47349
  "getSpreadPositionsOf",
47084
47350
  "getArrayFormulaSpreadingOn",
47351
+ "isEmpty",
47085
47352
  ];
47086
47353
  shouldRebuildDependenciesGraph = true;
47087
47354
  evaluator;
@@ -47110,6 +47377,10 @@ class EvaluationPlugin extends UIPlugin {
47110
47377
  this.evaluator.updateDependencies(cmd);
47111
47378
  }
47112
47379
  break;
47380
+ case "DUPLICATE_SHEET":
47381
+ case "CREATE_SHEET":
47382
+ this.shouldRebuildDependenciesGraph = true;
47383
+ break;
47113
47384
  case "EVALUATE_CELLS":
47114
47385
  this.evaluator.evaluateAllCells();
47115
47386
  break;
@@ -47187,6 +47458,14 @@ class EvaluationPlugin extends UIPlugin {
47187
47458
  getArrayFormulaSpreadingOn(position) {
47188
47459
  return this.evaluator.getArrayFormulaSpreadingOn(position);
47189
47460
  }
47461
+ /**
47462
+ * Check if a zone only contains empty cells
47463
+ */
47464
+ isEmpty(sheetId, zone) {
47465
+ return positions(zone)
47466
+ .map(({ col, row }) => this.getEvaluatedCell({ sheetId, col, row }))
47467
+ .every((cell) => cell.type === CellValueType.empty);
47468
+ }
47190
47469
  // ---------------------------------------------------------------------------
47191
47470
  // Export
47192
47471
  // ---------------------------------------------------------------------------
@@ -47304,12 +47583,13 @@ function colorDistance(color1, color2) {
47304
47583
  */
47305
47584
  class CustomColorsPlugin extends UIPlugin {
47306
47585
  customColors = {};
47307
- configCustomColors;
47308
- shouldUpdateColors = false;
47586
+ shouldUpdateColors = true;
47309
47587
  static getters = ["getCustomColors"];
47310
47588
  constructor(config) {
47311
47589
  super(config);
47312
- this.configCustomColors = config.customColors;
47590
+ for (const color of config.customColors ?? []) {
47591
+ this.tryToAddColor(color);
47592
+ }
47313
47593
  }
47314
47594
  handle(cmd) {
47315
47595
  switch (cmd.type) {
@@ -47320,31 +47600,29 @@ class CustomColorsPlugin extends UIPlugin {
47320
47600
  case "SET_BORDER":
47321
47601
  case "SET_ZONE_BORDERS":
47322
47602
  case "SET_FORMATTING":
47603
+ case "CREATE_TABLE":
47604
+ case "UPDATE_TABLE":
47323
47605
  this.history.update("shouldUpdateColors", true);
47606
+ break;
47324
47607
  }
47325
47608
  }
47326
47609
  finalize() {
47327
47610
  if (this.shouldUpdateColors) {
47328
47611
  this.history.update("shouldUpdateColors", false);
47329
- for (const color of this.getCustomColors()) {
47612
+ for (const color of this.computeCustomColors()) {
47330
47613
  this.tryToAddColor(color);
47331
47614
  }
47332
47615
  }
47333
47616
  }
47334
47617
  getCustomColors() {
47335
- let usedColors = this.configCustomColors;
47618
+ return sortWithClusters(Object.keys(this.customColors));
47619
+ }
47620
+ computeCustomColors() {
47621
+ let usedColors = [];
47336
47622
  for (const sheetId of this.getters.getSheetIds()) {
47337
- usedColors = usedColors.concat(this.getColorsFromCells(sheetId), this.getFormattingColors(sheetId), this.getChartColors(sheetId));
47338
- }
47339
- return sortWithClusters([
47340
- ...new Set(
47341
- // remove duplicates first to check validity on a reduced
47342
- // set of colors, then normalize to HEX and remove duplicates
47343
- // again
47344
- [...new Set([...usedColors, ...Object.keys(this.customColors)])]
47345
- .filter(isColorValid)
47346
- .map(toHex)),
47347
- ]).filter((color) => !COLOR_PICKER_DEFAULTS.includes(color));
47623
+ usedColors = usedColors.concat(this.getColorsFromCells(sheetId), this.getFormattingColors(sheetId), this.getChartColors(sheetId), this.getTableColors(sheetId));
47624
+ }
47625
+ return [...new Set([...usedColors])];
47348
47626
  }
47349
47627
  getColorsFromCells(sheetId) {
47350
47628
  const cells = Object.values(this.getters.getCells(sheetId));
@@ -47406,7 +47684,43 @@ class CustomColorsPlugin extends UIPlugin {
47406
47684
  }
47407
47685
  return [...chartsColors];
47408
47686
  }
47687
+ getTableColors(sheetId) {
47688
+ const tables = this.getters.getTables(sheetId);
47689
+ return tables.flatMap((table) => {
47690
+ const config = table.config;
47691
+ const style = TABLE_PRESETS[config.styleId];
47692
+ return [
47693
+ this.getTableStyleElementColors(style.wholeTable),
47694
+ config.numberOfHeaders > 0 ? this.getTableStyleElementColors(style.headerRow) : [],
47695
+ config.totalRow ? this.getTableStyleElementColors(style.totalRow) : [],
47696
+ config.bandedColumns ? this.getTableStyleElementColors(style.firstColumnStripe) : [],
47697
+ config.bandedColumns ? this.getTableStyleElementColors(style.secondColumnStripe) : [],
47698
+ config.bandedRows ? this.getTableStyleElementColors(style.firstRowStripe) : [],
47699
+ config.bandedRows ? this.getTableStyleElementColors(style.secondRowStripe) : [],
47700
+ config.firstColumn ? this.getTableStyleElementColors(style.firstColumn) : [],
47701
+ config.lastColumn ? this.getTableStyleElementColors(style.lastColumn) : [],
47702
+ ].flat();
47703
+ });
47704
+ }
47705
+ getTableStyleElementColors(element) {
47706
+ if (!element) {
47707
+ return [];
47708
+ }
47709
+ return [
47710
+ element.style?.fillColor,
47711
+ element.style?.textColor,
47712
+ element.border?.bottom?.color,
47713
+ element.border?.top?.color,
47714
+ element.border?.left?.color,
47715
+ element.border?.right?.color,
47716
+ element.border?.horizontal?.color,
47717
+ element.border?.vertical?.color,
47718
+ ].filter(isDefined$1);
47719
+ }
47409
47720
  tryToAddColor(color) {
47721
+ if (!isColorValid(color)) {
47722
+ return;
47723
+ }
47410
47724
  const formattedColor = toHex(color);
47411
47725
  if (color && !COLOR_PICKER_DEFAULTS.includes(formattedColor)) {
47412
47726
  this.history.update("customColors", formattedColor, true);
@@ -47830,8 +48144,6 @@ const VALID_RESULT = { isValid: true };
47830
48144
  class EvaluationDataValidationPlugin extends UIPlugin {
47831
48145
  static getters = [
47832
48146
  "getDataValidationInvalidCriterionValueMessage",
47833
- "getDataValidationCheckBoxCellPositions",
47834
- "getDataValidationListCellsPositions",
47835
48147
  "getInvalidDataValidationMessage",
47836
48148
  "getValidationResultForCellValue",
47837
48149
  "isCellValidCheckbox",
@@ -47909,19 +48221,6 @@ class EvaluationDataValidationPlugin extends UIPlugin {
47909
48221
  const error = this.getRuleErrorForCellValue(cellValue, cellPosition, rule);
47910
48222
  return error ? { error, rule, isValid: false } : VALID_RESULT;
47911
48223
  }
47912
- getDataValidationCheckBoxCellPositions() {
47913
- const rules = this.getters
47914
- .getDataValidationRules(this.getters.getActiveSheetId())
47915
- .filter((rule) => rule.criterion.type === "isBoolean");
47916
- return getCellPositionsInRanges(rules.map((rule) => rule.ranges).flat()).filter((position) => this.isCellValidCheckbox(position));
47917
- }
47918
- getDataValidationListCellsPositions() {
47919
- const rules = this.getters
47920
- .getDataValidationRules(this.getters.getActiveSheetId())
47921
- .filter((rule) => (rule.criterion.type === "isValueInList" || rule.criterion.type === "isValueInRange") &&
47922
- rule.criterion.displayStyle === "arrow");
47923
- return getCellPositionsInRanges(rules.map((rule) => rule.ranges).flat());
47924
- }
47925
48224
  getValidationResultForCell(cellPosition) {
47926
48225
  const { col, row, sheetId } = cellPosition;
47927
48226
  if (!this.validationResults[sheetId]) {
@@ -48378,6 +48677,23 @@ class AutofillPlugin extends UIPlugin {
48378
48677
  * autofiller
48379
48678
  */
48380
48679
  autofillAuto() {
48680
+ const activePosition = this.getters.getActivePosition();
48681
+ const table = this.getters.getTable(activePosition);
48682
+ let autofillRow = table ? table.range.zone.bottom : this.getAutofillAutoLastRow();
48683
+ // Stop autofill at the next non-empty cell
48684
+ const selection = this.getters.getSelectedZone();
48685
+ for (let row = selection.bottom + 1; row <= autofillRow; row++) {
48686
+ if (this.getters.getEvaluatedCell({ ...activePosition, row }).type !== CellValueType.empty) {
48687
+ autofillRow = row - 1;
48688
+ break;
48689
+ }
48690
+ }
48691
+ if (autofillRow > selection.bottom) {
48692
+ this.select(activePosition.col, autofillRow);
48693
+ this.autofill(true);
48694
+ }
48695
+ }
48696
+ getAutofillAutoLastRow() {
48381
48697
  const zone = this.getters.getSelectedZone();
48382
48698
  const sheetId = this.getters.getActiveSheetId();
48383
48699
  let col = zone.left;
@@ -48401,10 +48717,7 @@ class AutofillPlugin extends UIPlugin {
48401
48717
  }
48402
48718
  }
48403
48719
  }
48404
- if (row !== zone.bottom) {
48405
- this.select(zone.left, row - 1);
48406
- this.autofill(true);
48407
- }
48720
+ return row - 1;
48408
48721
  }
48409
48722
  /**
48410
48723
  * Generate the next cell
@@ -49946,7 +50259,6 @@ class DataCleanupPlugin extends UIPlugin {
49946
50259
  * the search with a new value.
49947
50260
  */
49948
50261
  class FindAndReplacePlugin extends UIPlugin {
49949
- static layers = ["Search"];
49950
50262
  static getters = [];
49951
50263
  handle(cmd) {
49952
50264
  switch (cmd.type) {
@@ -51016,6 +51328,51 @@ class SplitToColumnsPlugin extends UIPlugin {
51016
51328
  }
51017
51329
  }
51018
51330
 
51331
+ class TableAutofillPlugin extends UIPlugin {
51332
+ handle(cmd) {
51333
+ switch (cmd.type) {
51334
+ case "AUTOFILL_TABLE_COLUMN":
51335
+ const table = this.getters.getTable(cmd);
51336
+ const cell = this.getters.getCell(cmd);
51337
+ if (!table || !table.config.automaticAutofill || !cell?.isFormula)
51338
+ return;
51339
+ const { col, row } = cmd;
51340
+ const tableContentZone = getTableContentZone(table.range.zone, table.config);
51341
+ if (tableContentZone && isInside(col, row, tableContentZone)) {
51342
+ this.autofillTableZone(cmd, tableContentZone);
51343
+ }
51344
+ break;
51345
+ }
51346
+ }
51347
+ autofillTableZone(autofillSource, zone) {
51348
+ if (zone.top === zone.bottom) {
51349
+ return;
51350
+ }
51351
+ const { col, row, sheetId } = autofillSource;
51352
+ for (let r = zone.top; r <= zone.bottom; r++) {
51353
+ if (r === row) {
51354
+ continue;
51355
+ }
51356
+ if (this.getters.getEvaluatedCell({ col, row: r, sheetId }).type !== CellValueType.empty) {
51357
+ return;
51358
+ }
51359
+ }
51360
+ // TODO: seems odd that autofill a table column have side effects on the selection. Autofill commands should be
51361
+ // refactored to take the selection as a parameter
51362
+ const oldSelection = {
51363
+ zone: this.getters.getSelectedZone(),
51364
+ cell: this.getters.getActivePosition(),
51365
+ };
51366
+ this.selection.selectCell(col, row);
51367
+ this.dispatch("AUTOFILL_SELECT", { col, row: zone.bottom });
51368
+ this.dispatch("AUTOFILL");
51369
+ this.selection.selectCell(col, row);
51370
+ this.dispatch("AUTOFILL_SELECT", { col, row: zone.top });
51371
+ this.dispatch("AUTOFILL");
51372
+ this.selection.selectZone(oldSelection);
51373
+ }
51374
+ }
51375
+
51019
51376
  class TableStylePlugin extends UIPlugin {
51020
51377
  static getters = ["getCellTableStyle", "getCellTableBorder"];
51021
51378
  tableStyles = {};
@@ -52883,6 +53240,8 @@ class SheetViewPlugin extends UIPlugin {
52883
53240
  "getEdgeScrollRow",
52884
53241
  "getVisibleFigures",
52885
53242
  "getVisibleRect",
53243
+ "getVisibleRectWithoutHeaders",
53244
+ "getVisibleCellPositions",
52886
53245
  "getColRowOffsetInViewport",
52887
53246
  "getMainViewportCoordinates",
52888
53247
  "getActiveSheetScrollInfo",
@@ -53128,6 +53487,26 @@ class SheetViewPlugin extends UIPlugin {
53128
53487
  const viewports = this.getSubViewports(sheetId);
53129
53488
  return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => !this.getters.isHeaderHidden(sheetId, "ROW", row));
53130
53489
  }
53490
+ /**
53491
+ * Get the positions of all the cells that are visible in the viewport, taking merges into account.
53492
+ */
53493
+ getVisibleCellPositions() {
53494
+ const visibleCols = this.getSheetViewVisibleCols();
53495
+ const visibleRows = this.getSheetViewVisibleRows();
53496
+ const sheetId = this.getters.getActiveSheetId();
53497
+ const positions = [];
53498
+ for (const col of visibleCols) {
53499
+ for (const row of visibleRows) {
53500
+ const position = { sheetId, col, row };
53501
+ const mainPosition = this.getters.getMainCellPosition(position);
53502
+ if (mainPosition.row !== row || mainPosition.col !== col) {
53503
+ continue;
53504
+ }
53505
+ positions.push(position);
53506
+ }
53507
+ }
53508
+ return positions;
53509
+ }
53131
53510
  /**
53132
53511
  * Return the main viewport maximum size relative to the client size.
53133
53512
  */
@@ -53247,6 +53626,13 @@ class SheetViewPlugin extends UIPlugin {
53247
53626
  * Computes the coordinates and size to draw the zone on the canvas
53248
53627
  */
53249
53628
  getVisibleRect(zone) {
53629
+ const rect = this.getVisibleRectWithoutHeaders(zone);
53630
+ return { ...rect, x: rect.x + this.gridOffsetX, y: rect.y + this.gridOffsetY };
53631
+ }
53632
+ /**
53633
+ * Computes the coordinates and size to draw the zone without taking the grid offset into account
53634
+ */
53635
+ getVisibleRectWithoutHeaders(zone) {
53250
53636
  const sheetId = this.getters.getActiveSheetId();
53251
53637
  const viewportRects = this.getSubViewports(sheetId)
53252
53638
  .map((viewport) => viewport.getRect(zone))
@@ -53258,12 +53644,7 @@ class SheetViewPlugin extends UIPlugin {
53258
53644
  const y = Math.min(...viewportRects.map((rect) => rect.y));
53259
53645
  const width = Math.max(...viewportRects.map((rect) => rect.x + rect.width)) - x;
53260
53646
  const height = Math.max(...viewportRects.map((rect) => rect.y + rect.height)) - y;
53261
- return {
53262
- x: x + this.gridOffsetX,
53263
- y: y + this.gridOffsetY,
53264
- width,
53265
- height,
53266
- };
53647
+ return { x, y, width, height };
53267
53648
  }
53268
53649
  /**
53269
53650
  * Returns the position of the MainViewport relatively to the start of the grid (without headers)
@@ -53517,13 +53898,8 @@ class HeaderPositionsUIPlugin extends UIPlugin {
53517
53898
  }
53518
53899
  break;
53519
53900
  case "UPDATE_CELL":
53520
- if ("content" in cmd || "format" in cmd) {
53521
- this.headerPositions = {};
53522
- this.isDirty = true;
53523
- }
53524
- else {
53525
- this.headerPositions[cmd.sheetId] = this.computeHeaderPositionsOfSheet(cmd.sheetId);
53526
- }
53901
+ this.headerPositions = {};
53902
+ this.isDirty = true;
53527
53903
  break;
53528
53904
  case "UPDATE_FILTER":
53529
53905
  case "UPDATE_TABLE":
@@ -53646,7 +54022,8 @@ const featurePluginRegistry = new Registry()
53646
54022
  .add("split_to_columns", SplitToColumnsPlugin)
53647
54023
  .add("collaborative", CollaborativePlugin)
53648
54024
  .add("history", HistoryPlugin)
53649
- .add("data_cleanup", DataCleanupPlugin);
54025
+ .add("data_cleanup", DataCleanupPlugin)
54026
+ .add("table_autofill", TableAutofillPlugin);
53650
54027
  // Plugins which have a state, but which should not be shared in collaborative
53651
54028
  const statefulUIPluginRegistry = new Registry()
53652
54029
  .add("selection", GridSelectionPlugin)
@@ -53989,6 +54366,9 @@ class BottomBarSheet extends Component {
53989
54366
  this.scrollToSheet();
53990
54367
  }
53991
54368
  onDblClick() {
54369
+ if (this.env.model.getters.isReadonly()) {
54370
+ return;
54371
+ }
53992
54372
  this.startEdition();
53993
54373
  }
53994
54374
  onKeyDown(ev) {
@@ -55945,7 +56325,6 @@ class Spreadsheet extends Component {
55945
56325
  }
55946
56326
  }, () => [this.env.model.getters.getActiveSheetId()]);
55947
56327
  useExternalListener(window, "resize", () => this.render(true));
55948
- useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
55949
56328
  this.bindModelEvents();
55950
56329
  onWillUpdateProps((nextProps) => {
55951
56330
  if (nextProps.model !== this.props.model) {
@@ -59745,6 +60124,6 @@ const constants = {
59745
60124
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
59746
60125
 
59747
60126
 
59748
- __info__.version = "17.2.0-alpha.6";
59749
- __info__.date = "2024-02-28T12:28:44.114Z";
59750
- __info__.hash = "318a04e";
60127
+ __info__.version = "17.2.0";
60128
+ __info__.date = "2024-03-20T08:12:44.398Z";
60129
+ __info__.hash = "1869c24";