@odoo/o-spreadsheet 17.3.0-alpha.0 → 17.3.0-alpha.1

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.3.0-alpha.0
7
- * @date 2024-03-20T13:42:32.042Z
8
- * @hash 073e154
6
+ * @version 17.3.0-alpha.1
7
+ * @date 2024-03-25T09:43:36.072Z
8
+ * @hash 4095c41
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';
@@ -29,7 +29,6 @@ const DEFAULT_COLOR_SCALE_MIDPOINT_COLOR = 0xb6d7a8;
29
29
  const LINK_COLOR = "#017E84";
30
30
  const FILTERS_COLOR = "#188038";
31
31
  const BACKGROUND_HEADER_FILTER_COLOR = "#E6F4EA";
32
- const BACKGROUND_HEADER_SELECTED_FILTER_COLOR = "#CEEAD6";
33
32
  const SEPARATOR_COLOR = "#E0E2E4";
34
33
  const ICONS_COLOR = "#4A4F59";
35
34
  const HEADER_GROUPING_BACKGROUND_COLOR = "#F5F5F5";
@@ -4202,6 +4201,10 @@ function organizeZone(zone) {
4202
4201
  function positionToZone(position) {
4203
4202
  return { left: position.col, right: position.col, top: position.row, bottom: position.row };
4204
4203
  }
4204
+ /** Transform a zone into a zone with only its top-left position */
4205
+ function zoneToTopLeft(zone) {
4206
+ return { ...zone, right: zone.left, bottom: zone.top };
4207
+ }
4205
4208
  function isFullRow(zone) {
4206
4209
  return zone.right === undefined;
4207
4210
  }
@@ -4241,6 +4244,16 @@ function getZonesRows(zones) {
4241
4244
  }
4242
4245
  return set;
4243
4246
  }
4247
+ function unionPositionsToZone(positions) {
4248
+ const zone = { top: Infinity, left: Infinity, bottom: -Infinity, right: -Infinity };
4249
+ for (const { col, row } of positions) {
4250
+ zone.top = Math.min(zone.top, row);
4251
+ zone.left = Math.min(zone.left, col);
4252
+ zone.bottom = Math.max(zone.bottom, row);
4253
+ zone.right = Math.max(zone.right, col);
4254
+ }
4255
+ return zone;
4256
+ }
4244
4257
 
4245
4258
  class RangeImpl {
4246
4259
  getSheetSize;
@@ -5570,7 +5583,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
5570
5583
  const spreader = this.getters.getArrayFormulaSpreadingOn(position);
5571
5584
  let cell = this.getters.getCell(position);
5572
5585
  const evaluatedCell = this.getters.getEvaluatedCell(position);
5573
- if (spreader) {
5586
+ if (spreader && !deepEquals(spreader, position)) {
5574
5587
  const isSpreaderCopied = rowsIndexes.includes(spreader.row) && columnsIndexes.includes(spreader.col);
5575
5588
  const content = isSpreaderCopied
5576
5589
  ? ""
@@ -6217,19 +6230,25 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
6217
6230
  tableCellsInRow.push({});
6218
6231
  continue;
6219
6232
  }
6233
+ const coreTable = this.getters.getCoreTable(position);
6234
+ const tableZone = coreTable?.range.zone;
6220
6235
  // Copy whole table
6221
- if (zones.some((z) => isZoneInside(table.range.zone, z))) {
6222
- copiedTablesIds.add(table.id);
6236
+ if (coreTable && tableZone && zones.some((z) => isZoneInside(tableZone, z))) {
6237
+ copiedTablesIds.add(coreTable.id);
6223
6238
  const values = [];
6224
- for (const col of range(table.range.zone.left, table.range.zone.right + 1)) {
6225
- values.push(this.getters.getFilterHiddenValues({ sheetId, col, row: table.range.zone.top }));
6239
+ for (const col of range(tableZone.left, tableZone.right + 1)) {
6240
+ values.push(this.getters.getFilterHiddenValues({ sheetId, col, row: tableZone.top }));
6226
6241
  }
6227
6242
  tableCellsInRow.push({
6228
- table: { filtersValues: values, range: table.range, config: table.config },
6243
+ table: {
6244
+ range: coreTable.range,
6245
+ config: coreTable.config,
6246
+ type: coreTable.type,
6247
+ },
6229
6248
  });
6230
6249
  }
6231
6250
  // Copy only style of cell
6232
- else {
6251
+ else if (table) {
6233
6252
  tableCellsInRow.push({ style: this.getTableStyleToCopy(position) });
6234
6253
  }
6235
6254
  }
@@ -6300,7 +6319,7 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
6300
6319
  }
6301
6320
  pasteTableCell(sheetId, tableCell, position, options) {
6302
6321
  if (tableCell.table && !options?.pasteOption) {
6303
- const { range: tableRange, filtersValues } = tableCell.table;
6322
+ const { range: tableRange } = tableCell.table;
6304
6323
  const zoneDims = zoneToDimension(tableRange.zone);
6305
6324
  const newTableZone = {
6306
6325
  left: position.col,
@@ -6312,18 +6331,13 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
6312
6331
  sheetId: position.sheetId,
6313
6332
  ranges: [this.getters.getRangeDataFromZone(sheetId, newTableZone)],
6314
6333
  config: tableCell.table.config,
6334
+ tableType: tableCell.table.type,
6315
6335
  });
6316
- for (const i of range(0, filtersValues.length)) {
6317
- this.dispatch("UPDATE_FILTER", {
6318
- sheetId: position.sheetId,
6319
- col: newTableZone.left + i,
6320
- row: newTableZone.top,
6321
- hiddenValues: filtersValues[i],
6322
- });
6323
- }
6324
6336
  }
6325
6337
  // Do not paste table style if we're inside another table
6326
- if (!this.getters.getTable(position)) {
6338
+ // We cannot check for dynamic tables, because at this point the paste can have changed the evaluation, and the
6339
+ // dynamic tables are not yet computed
6340
+ if (!this.getters.getCoreTable(position)) {
6327
6341
  if (tableCell.style?.style && options?.pasteOption !== "asValue") {
6328
6342
  this.dispatch("UPDATE_CELL", { ...position, style: tableCell.style.style });
6329
6343
  }
@@ -7425,9 +7439,11 @@ const TableTerms = {
7425
7439
  bandedColumns: _t("Banded columns"),
7426
7440
  automaticAutofill: _t("Automatically autofill formulas"),
7427
7441
  totalRow: _t("Total row"),
7442
+ isDynamic: _t("Auto-adjust to formula result"),
7428
7443
  },
7429
7444
  Tooltips: {
7430
7445
  filterWithoutHeader: _t("Cannot have filters without a header row"),
7446
+ isDynamic: _t("For tables based on array formulas only"),
7431
7447
  },
7432
7448
  };
7433
7449
 
@@ -8003,6 +8019,9 @@ class DependencyContainer {
8003
8019
  instantiate(Store, ...args) {
8004
8020
  return this.factory.build(Store, ...args);
8005
8021
  }
8022
+ resetStores() {
8023
+ this.dependencies.clear();
8024
+ }
8006
8025
  }
8007
8026
  class StoreFactory {
8008
8027
  get;
@@ -22225,6 +22244,7 @@ class LinkDisplay extends Component {
22225
22244
  }
22226
22245
  edit() {
22227
22246
  const { col, row } = this.props.cellPosition;
22247
+ this.env.model.selection.selectCell(col, row);
22228
22248
  this.cellPopovers.open({ col, row }, "LinkEditor");
22229
22249
  }
22230
22250
  unlink() {
@@ -23341,13 +23361,20 @@ const TABLE_PRESETS = {
23341
23361
  * If a single cell is selected, expand the selection to non-empty adjacent cells to create a table.
23342
23362
  */
23343
23363
  function interactiveCreateTable(env, sheetId, tableConfig) {
23344
- const selection = env.model.getters.getSelectedZones();
23345
- if (selection.length === 1 && getZoneArea(selection[0]) === 1) {
23364
+ let target = env.model.getters.getSelectedZones();
23365
+ let isDynamic = env.model.getters.canCreateDynamicTableOnZones(sheetId, target);
23366
+ if (target.length === 1 && !isDynamic && getZoneArea(target[0]) === 1) {
23346
23367
  env.model.selection.selectTableAroundSelection();
23368
+ target = env.model.getters.getSelectedZones();
23369
+ isDynamic = env.model.getters.canCreateDynamicTableOnZones(sheetId, target);
23347
23370
  }
23348
- const target = env.model.getters.getSelectedZones();
23349
23371
  const ranges = target.map((zone) => env.model.getters.getRangeDataFromZone(sheetId, zone));
23350
- const result = env.model.dispatch("CREATE_TABLE", { ranges, sheetId, config: tableConfig });
23372
+ const result = env.model.dispatch("CREATE_TABLE", {
23373
+ ranges,
23374
+ sheetId,
23375
+ config: tableConfig,
23376
+ tableType: isDynamic ? "dynamic" : "static",
23377
+ });
23351
23378
  if (result.isCancelledBecause("TableOverlap" /* CommandResult.TableOverlap */)) {
23352
23379
  env.raiseError(TableTerms.Errors.TableOverlap);
23353
23380
  }
@@ -23795,9 +23822,13 @@ const INSERT_TABLE = (env) => {
23795
23822
  };
23796
23823
  const DELETE_SELECTED_TABLE = (env) => {
23797
23824
  const position = env.model.getters.getActivePosition();
23825
+ const table = env.model.getters.getTable(position);
23826
+ if (!table) {
23827
+ return;
23828
+ }
23798
23829
  env.model.dispatch("REMOVE_TABLE", {
23799
23830
  sheetId: position.sheetId,
23800
- target: [positionToZone(position)],
23831
+ target: [table.range.zone],
23801
23832
  });
23802
23833
  };
23803
23834
  //------------------------------------------------------------------------------
@@ -24213,14 +24244,19 @@ const categorieFunctionAll = {
24213
24244
  children: [allFunctionListMenuBuilder],
24214
24245
  };
24215
24246
  function allFunctionListMenuBuilder() {
24216
- const fnNames = functionRegistry.getKeys();
24247
+ const fnNames = functionRegistry.getKeys().filter((key) => !functionRegistry.get(key).hidden);
24217
24248
  return createFormulaFunctions(fnNames);
24218
24249
  }
24219
24250
  const categoriesFunctionListMenuBuilder = () => {
24220
24251
  const functions = functionRegistry.content;
24221
- const categories = [...new Set(functionRegistry.getAll().map((fn) => fn.category))].filter(isDefined$1);
24252
+ const categories = [
24253
+ ...new Set(functionRegistry
24254
+ .getAll()
24255
+ .filter((fn) => !fn.hidden)
24256
+ .map((fn) => fn.category)),
24257
+ ].filter(isDefined$1);
24222
24258
  return categories.sort().map((category, i) => {
24223
- const functionsInCategory = Object.keys(functions).filter((key) => functions[key].category === category);
24259
+ const functionsInCategory = Object.keys(functions).filter((key) => functions[key].category === category && !functions[key].hidden);
24224
24260
  return {
24225
24261
  name: category,
24226
24262
  children: createFormulaFunctions(functionsInCategory),
@@ -28378,11 +28414,9 @@ css /* scss */ `
28378
28414
  }
28379
28415
  .o-cell-is-operator {
28380
28416
  margin-bottom: 5px;
28381
- width: 96%;
28382
28417
  }
28383
28418
  .o-cell-is-value {
28384
28419
  margin-bottom: 5px;
28385
- width: 96%;
28386
28420
  }
28387
28421
  .o-color-picker-widget .o-color-picker-button {
28388
28422
  pointer-events: all;
@@ -30302,6 +30336,24 @@ function getTableContentZone(tableZone, tableConfig) {
30302
30336
  const contentZone = { ...tableZone, top: tableZone.top + numberOfHeaders };
30303
30337
  return contentZone.top <= contentZone.bottom ? contentZone : undefined;
30304
30338
  }
30339
+ function getTableTopLeft(table) {
30340
+ const range = table.range;
30341
+ return { row: range.zone.top, col: range.zone.left, sheetId: range.sheetId };
30342
+ }
30343
+ function createFilter(id, range, config, createRange) {
30344
+ const zone = range.zone;
30345
+ if (zone.left !== zone.right) {
30346
+ throw new Error("Can only define a filter on a single column");
30347
+ }
30348
+ const filteredZone = { ...zone, top: zone.top + config.numberOfHeaders };
30349
+ const filteredRange = createRange(range.sheetId, filteredZone);
30350
+ return {
30351
+ id,
30352
+ rangeWithHeaders: range,
30353
+ col: zone.left,
30354
+ filteredRange: filteredZone.top > filteredZone.bottom ? undefined : filteredRange,
30355
+ };
30356
+ }
30305
30357
  function getComputedTableStyle(tableConfig, numberOfCols, numberOfRows) {
30306
30358
  return {
30307
30359
  borders: getAllTableBorders(tableConfig, numberOfCols, numberOfRows),
@@ -30739,6 +30791,11 @@ css /* scss */ `
30739
30791
  color: #ffffff;
30740
30792
  background: #d94b4b;
30741
30793
  }
30794
+
30795
+ .o-info-icon {
30796
+ width: 14px;
30797
+ height: 14px;
30798
+ }
30742
30799
  }
30743
30800
  `;
30744
30801
  class TablePanel extends Component {
@@ -30770,6 +30827,29 @@ class TablePanel extends Component {
30770
30827
  const numberOfHeaders = hasHeaders ? 1 : 0;
30771
30828
  this.updateNumberOfHeaders(numberOfHeaders);
30772
30829
  }
30830
+ updateTableIsDynamic(isDynamic) {
30831
+ const newTableType = isDynamic ? "dynamic" : "forceStatic";
30832
+ if (newTableType === this.props.table.type) {
30833
+ return;
30834
+ }
30835
+ const uiTable = this.env.model.getters.getTable(getTableTopLeft(this.props.table));
30836
+ if (!uiTable) {
30837
+ return;
30838
+ }
30839
+ const sheetId = this.env.model.getters.getActiveSheetId();
30840
+ const result = this.env.model.dispatch("UPDATE_TABLE", {
30841
+ sheetId,
30842
+ zone: this.props.table.range.zone,
30843
+ newTableRange: uiTable.range.rangeData,
30844
+ tableType: newTableType,
30845
+ });
30846
+ const updatedTable = this.env.model.getters.getCoreTable(getTableTopLeft(this.props.table));
30847
+ if (result.isSuccessful && updatedTable) {
30848
+ const newTableRange = updatedTable.range;
30849
+ this.state.tableXc = this.env.model.getters.getRangeString(newTableRange, sheetId);
30850
+ this.state.tableZoneErrors = [];
30851
+ }
30852
+ }
30773
30853
  onChangeNumberOfHeaders(ev) {
30774
30854
  const input = ev.target;
30775
30855
  const numberOfHeaders = parseInt(input.value);
@@ -30793,35 +30873,59 @@ class TablePanel extends Component {
30793
30873
  }
30794
30874
  const sheetId = this.env.model.getters.getActiveSheetId();
30795
30875
  this.state.tableXc = ranges[0];
30876
+ const newTableRange = this.env.model.getters.getRangeFromSheetXC(sheetId, this.state.tableXc);
30796
30877
  this.state.tableZoneErrors = this.env.model.canDispatch("UPDATE_TABLE", {
30797
30878
  sheetId,
30798
30879
  zone: this.props.table.range.zone,
30799
30880
  newTableRange: this.env.model.getters.getRangeDataFromXc(sheetId, this.state.tableXc),
30881
+ tableType: this.getNewTableType(newTableRange.zone),
30800
30882
  }).reasons;
30801
30883
  }
30802
30884
  onRangeConfirmed() {
30803
30885
  const sheetId = this.env.model.getters.getActiveSheetId();
30804
- const newRange = this.env.model.getters.getRangeFromSheetXC(sheetId, this.state.tableXc);
30886
+ let newRange = this.env.model.getters.getRangeFromSheetXC(sheetId, this.state.tableXc);
30887
+ if (getZoneArea(newRange.zone) === 1) {
30888
+ const extendedZone = this.env.model.getters.getContiguousZone(sheetId, newRange.zone);
30889
+ newRange = this.env.model.getters.getRangeFromZone(sheetId, extendedZone);
30890
+ }
30805
30891
  const result = this.env.model.dispatch("UPDATE_TABLE", {
30806
30892
  sheetId,
30807
30893
  zone: this.props.table.range.zone,
30808
30894
  newTableRange: newRange.rangeData,
30895
+ tableType: this.getNewTableType(newRange.zone),
30809
30896
  });
30810
- if (result.isSuccessful) {
30811
- const position = { col: newRange.zone.left, row: newRange.zone.top };
30897
+ const position = { sheetId, col: newRange.zone.left, row: newRange.zone.top };
30898
+ const updatedTable = this.env.model.getters.getCoreTable(position);
30899
+ if (result.isSuccessful && updatedTable) {
30900
+ const newTopLeft = getTableTopLeft(updatedTable);
30812
30901
  this.env.model.selection.selectZone({
30813
- zone: positionToZone(position),
30814
- cell: position,
30902
+ zone: positionToZone(newTopLeft),
30903
+ cell: newTopLeft,
30815
30904
  });
30905
+ const newTableRange = updatedTable.range;
30906
+ this.state.tableXc = this.env.model.getters.getRangeString(newTableRange, sheetId);
30907
+ }
30908
+ else {
30909
+ const oldTableRange = this.props.table.range;
30910
+ this.state.tableXc = this.env.model.getters.getRangeString(oldTableRange, sheetId);
30816
30911
  }
30817
30912
  this.state.tableZoneErrors = [];
30818
- this.state.tableXc = result.isSuccessful
30819
- ? this.state.tableXc
30820
- : this.env.model.getters.getRangeString(this.props.table.range, sheetId);
30821
30913
  }
30822
30914
  deleteTable() {
30823
30915
  const sheetId = this.env.model.getters.getActiveSheetId();
30824
- this.env.model.dispatch("REMOVE_TABLE", { sheetId, target: [this.props.table.range.zone] });
30916
+ this.env.model.dispatch("REMOVE_TABLE", {
30917
+ sheetId,
30918
+ target: [this.props.table.range.zone],
30919
+ });
30920
+ }
30921
+ getNewTableType(newTableZone) {
30922
+ if (this.props.table.type === "forceStatic") {
30923
+ return "forceStatic";
30924
+ }
30925
+ const sheetId = this.env.model.getters.getActiveSheetId();
30926
+ return this.env.model.getters.canCreateDynamicTableOnZones(sheetId, [newTableZone])
30927
+ ? "dynamic"
30928
+ : "static";
30825
30929
  }
30826
30930
  get tableConfig() {
30827
30931
  return this.props.table.config;
@@ -30839,6 +30943,14 @@ class TablePanel extends Component {
30839
30943
  get hasFilterCheckboxTooltip() {
30840
30944
  return this.canHaveFilters ? undefined : TableTerms.Tooltips.filterWithoutHeader;
30841
30945
  }
30946
+ get canBeDynamic() {
30947
+ const sheetId = this.env.model.getters.getActiveSheetId();
30948
+ return (this.props.table.type === "dynamic" ||
30949
+ this.env.model.getters.canCreateDynamicTableOnZones(sheetId, [this.props.table.range.zone]));
30950
+ }
30951
+ get dynamicTableTooltip() {
30952
+ return TableTerms.Tooltips.isDynamic;
30953
+ }
30842
30954
  }
30843
30955
 
30844
30956
  const sidePanelRegistry = new Registry();
@@ -30901,11 +31013,8 @@ sidePanelRegistry.add("TableSidePanel", {
30901
31013
  if (!table) {
30902
31014
  return { isOpen: false };
30903
31015
  }
30904
- return {
30905
- isOpen: true,
30906
- props: { table },
30907
- key: table.id,
30908
- };
31016
+ const coreTable = getters.getCoreTable(getTableTopLeft(table));
31017
+ return { isOpen: true, props: { table: coreTable }, key: table.id };
30909
31018
  },
30910
31019
  });
30911
31020
 
@@ -31084,6 +31193,9 @@ class FigureComponent extends Component {
31084
31193
  el?.focus({ preventScroll: true });
31085
31194
  }
31086
31195
  }, () => [this.env.model.getters.getSelectedFigureId(), this.props.figure.id, this.figureRef.el]);
31196
+ onWillUnmount(() => {
31197
+ this.props.onFigureDeleted();
31198
+ });
31087
31199
  }
31088
31200
  clickAnchor(dirX, dirY, ev) {
31089
31201
  this.props.onClickAnchor(dirX, dirY, ev);
@@ -31102,6 +31214,7 @@ class FigureComponent extends Component {
31102
31214
  this.props.onFigureDeleted();
31103
31215
  ev.stopPropagation();
31104
31216
  ev.preventDefault();
31217
+ ev.stopPropagation();
31105
31218
  break;
31106
31219
  case "ArrowDown":
31107
31220
  case "ArrowLeft":
@@ -31122,6 +31235,7 @@ class FigureComponent extends Component {
31122
31235
  });
31123
31236
  ev.stopPropagation();
31124
31237
  ev.preventDefault();
31238
+ ev.stopPropagation();
31125
31239
  break;
31126
31240
  }
31127
31241
  }
@@ -31789,6 +31903,7 @@ function compareContentToSpanElement(content, node) {
31789
31903
  // -----------------------------------------------------------------------------
31790
31904
  css /* scss */ `
31791
31905
  .o-formula-assistant {
31906
+ background: #ffffff;
31792
31907
  .o-formula-assistant-head {
31793
31908
  background-color: #f2f2f2;
31794
31909
  padding: 10px;
@@ -32741,8 +32856,7 @@ class FilterIconsOverlay extends Component {
32741
32856
  };
32742
32857
  getFilterHeadersPositions() {
32743
32858
  const sheetId = this.env.model.getters.getActiveSheetId();
32744
- const headerPositions = this.env.model.getters.getFilterHeaders(sheetId);
32745
- return headerPositions.map((position) => ({ sheetId, ...position }));
32859
+ return this.env.model.getters.getFilterHeaders(sheetId);
32746
32860
  }
32747
32861
  }
32748
32862
 
@@ -34613,19 +34727,16 @@ class GridRenderer {
34613
34727
  for (let col = left; col <= right; col++) {
34614
34728
  const colZone = { left: col, right: col, top: 0, bottom: numberOfRows - 1 };
34615
34729
  const { x, width } = this.getters.getVisibleRect(colZone);
34616
- const colHasFilter = this.getters.doesZonesContainFilter(sheetId, [colZone]);
34617
34730
  const isColActive = activeCols.has(col);
34618
34731
  const isColSelected = selectedCols.has(col);
34619
34732
  if (isColActive) {
34620
- ctx.fillStyle = colHasFilter ? FILTERS_COLOR : BACKGROUND_HEADER_ACTIVE_COLOR;
34733
+ ctx.fillStyle = BACKGROUND_HEADER_ACTIVE_COLOR;
34621
34734
  }
34622
34735
  else if (isColSelected) {
34623
- ctx.fillStyle = colHasFilter
34624
- ? BACKGROUND_HEADER_SELECTED_FILTER_COLOR
34625
- : BACKGROUND_HEADER_SELECTED_COLOR;
34736
+ ctx.fillStyle = BACKGROUND_HEADER_SELECTED_COLOR;
34626
34737
  }
34627
34738
  else {
34628
- ctx.fillStyle = colHasFilter ? BACKGROUND_HEADER_FILTER_COLOR : BACKGROUND_HEADER_COLOR;
34739
+ ctx.fillStyle = BACKGROUND_HEADER_COLOR;
34629
34740
  }
34630
34741
  ctx.fillRect(x, 0, width, HEADER_HEIGHT);
34631
34742
  }
@@ -34633,19 +34744,16 @@ class GridRenderer {
34633
34744
  for (let row = top; row <= bottom; row++) {
34634
34745
  const rowZone = { top: row, bottom: row, left: 0, right: numberOfCols - 1 };
34635
34746
  const { y, height } = this.getters.getVisibleRect(rowZone);
34636
- const rowHasFilter = this.getters.doesZonesContainFilter(sheetId, [rowZone]);
34637
34747
  const isRowActive = activeRows.has(row);
34638
34748
  const isRowSelected = selectedRows.has(row);
34639
34749
  if (isRowActive) {
34640
- ctx.fillStyle = rowHasFilter ? FILTERS_COLOR : BACKGROUND_HEADER_ACTIVE_COLOR;
34750
+ ctx.fillStyle = BACKGROUND_HEADER_ACTIVE_COLOR;
34641
34751
  }
34642
34752
  else if (isRowSelected) {
34643
- ctx.fillStyle = rowHasFilter
34644
- ? BACKGROUND_HEADER_SELECTED_FILTER_COLOR
34645
- : BACKGROUND_HEADER_SELECTED_COLOR;
34753
+ ctx.fillStyle = BACKGROUND_HEADER_SELECTED_COLOR;
34646
34754
  }
34647
34755
  else {
34648
- ctx.fillStyle = rowHasFilter ? BACKGROUND_HEADER_FILTER_COLOR : BACKGROUND_HEADER_COLOR;
34756
+ ctx.fillStyle = BACKGROUND_HEADER_COLOR;
34649
34757
  }
34650
34758
  ctx.fillRect(0, y, HEADER_WIDTH, height);
34651
34759
  }
@@ -44817,23 +44925,12 @@ class SheetPlugin extends CorePlugin {
44817
44925
  }
44818
44926
 
44819
44927
  class TablePlugin extends CorePlugin {
44820
- static getters = [
44821
- "doesZonesContainFilter",
44822
- "getFilter",
44823
- "getFilters",
44824
- "getTable",
44825
- "getTables",
44826
- "getTablesInZone",
44827
- "getTablesOverlappingZones",
44828
- "getFilterId",
44829
- "getFilterHeaders",
44830
- "isFilterHeader",
44831
- ];
44928
+ static getters = ["getCoreTable", "getCoreTables"];
44832
44929
  tables = {};
44833
44930
  adaptRanges(applyChange, sheetId) {
44834
44931
  const sheetIds = sheetId ? [sheetId] : this.getters.getSheetIds();
44835
44932
  for (const sheetId of sheetIds) {
44836
- for (const table of this.getTables(sheetId)) {
44933
+ for (const table of this.getCoreTables(sheetId)) {
44837
44934
  this.applyRangeChangeOnTable(sheetId, table, applyChange);
44838
44935
  }
44839
44936
  }
@@ -44849,15 +44946,16 @@ class TablePlugin extends CorePlugin {
44849
44946
  ? "TableOverlap" /* CommandResult.TableOverlap */
44850
44947
  : "Success" /* CommandResult.Success */, (cmd) => this.checkTableConfigUpdateIsValid(cmd.config));
44851
44948
  case "UPDATE_TABLE":
44852
- const updatedTable = this.getTables(cmd.sheetId).find((table) => deepEquals(table.range.zone, cmd.zone));
44949
+ const updatedTable = this.getTableFromZone(cmd.sheetId, cmd.zone);
44853
44950
  if (!updatedTable) {
44854
44951
  return "TableNotFound" /* CommandResult.TableNotFound */;
44855
44952
  }
44856
44953
  return this.checkValidations(cmd, this.checkUpdatedTableZoneIsValid, (cmd) => this.checkTableConfigUpdateIsValid(cmd.config));
44857
44954
  case "ADD_MERGE":
44858
- for (const merge of cmd.target) {
44859
- for (const table of this.getTables(cmd.sheetId)) {
44860
- if (overlap(table.range.zone, merge)) {
44955
+ for (const table of this.getCoreTables(cmd.sheetId)) {
44956
+ const tableZone = table.range.zone;
44957
+ for (const merge of cmd.target) {
44958
+ if (overlap(tableZone, merge)) {
44861
44959
  return "MergeInTable" /* CommandResult.MergeInTable */;
44862
44960
  }
44863
44961
  }
@@ -44879,8 +44977,11 @@ class TablePlugin extends CorePlugin {
44879
44977
  }
44880
44978
  case "DUPLICATE_SHEET": {
44881
44979
  const newTables = {};
44882
- for (const table of this.getTables(cmd.sheetId)) {
44883
- newTables[table.id] = this.copyTableForSheet(cmd.sheetIdTo, table);
44980
+ for (const table of this.getCoreTables(cmd.sheetId)) {
44981
+ newTables[table.id] =
44982
+ table.type === "dynamic"
44983
+ ? this.copyDynamicTableForSheet(cmd.sheetIdTo, table)
44984
+ : this.copyStaticTableForSheet(cmd.sheetIdTo, table);
44884
44985
  }
44885
44986
  this.history.update("tables", cmd.sheetIdTo, newTables);
44886
44987
  break;
@@ -44891,14 +44992,17 @@ class TablePlugin extends CorePlugin {
44891
44992
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
44892
44993
  this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
44893
44994
  const id = this.uuidGenerator.uuidv4();
44894
- const newTable = this.createTable(id, union, cmd.config || DEFAULT_TABLE_CONFIG);
44995
+ const config = cmd.config || DEFAULT_TABLE_CONFIG;
44996
+ const newTable = cmd.tableType === "dynamic"
44997
+ ? this.createDynamicTable(id, union, config)
44998
+ : this.createStaticTable(id, cmd.tableType, union, config);
44895
44999
  this.history.update("tables", cmd.sheetId, newTable.id, newTable);
44896
45000
  break;
44897
45001
  }
44898
45002
  case "REMOVE_TABLE": {
44899
45003
  const tables = {};
44900
- for (const table of this.getTables(cmd.sheetId)) {
44901
- if (cmd.target.every((zone) => !intersection(zone, table.range.zone))) {
45004
+ for (const table of this.getCoreTables(cmd.sheetId)) {
45005
+ if (cmd.target.every((zone) => !intersection(table.range.zone, zone))) {
44902
45006
  tables[table.id] = table;
44903
45007
  }
44904
45008
  }
@@ -44906,23 +45010,15 @@ class TablePlugin extends CorePlugin {
44906
45010
  break;
44907
45011
  }
44908
45012
  case "UPDATE_TABLE": {
44909
- const table = this.getTables(cmd.sheetId).find((table) => deepEquals(table.range.zone, cmd.zone));
44910
- if (table) {
44911
- const newTableRange = cmd.newTableRange
44912
- ? this.getters.getRangeFromRangeData(cmd.newTableRange)
44913
- : undefined;
44914
- if (newTableRange) {
44915
- const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, newTableRange.zone);
44916
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
44917
- }
44918
- const newTable = this.updateTable(table, newTableRange, cmd.config);
44919
- this.history.update("tables", cmd.sheetId, table.id, newTable);
44920
- }
45013
+ this.updateTable(cmd);
44921
45014
  break;
44922
45015
  }
44923
45016
  case "UPDATE_CELL": {
44924
45017
  const sheetId = cmd.sheetId;
44925
- for (const table of this.getTables(sheetId)) {
45018
+ for (const table of this.getCoreTables(sheetId)) {
45019
+ if (table.type === "dynamic") {
45020
+ continue;
45021
+ }
44926
45022
  const direction = this.canUpdateCellCmdExtendTable(cmd, table);
44927
45023
  if (direction === "down") {
44928
45024
  this.extendTableDown(sheetId, table);
@@ -44946,66 +45042,24 @@ class TablePlugin extends CorePlugin {
44946
45042
  }
44947
45043
  }
44948
45044
  }
44949
- getFilters(sheetId) {
44950
- return this.getTables(sheetId)
44951
- .filter((table) => table.config.hasFilters)
44952
- .map((table) => table.filters)
44953
- .flat();
44954
- }
44955
- getTables(sheetId) {
45045
+ getCoreTables(sheetId) {
44956
45046
  return this.tables[sheetId] ? Object.values(this.tables[sheetId]).filter(isDefined$1) : [];
44957
45047
  }
44958
- getFilter(position) {
44959
- const table = this.getTable(position);
44960
- if (!table || !table.config.hasFilters) {
44961
- return undefined;
44962
- }
44963
- return table.filters.find((filter) => filter.col === position.col);
44964
- }
44965
- getFilterId(position) {
44966
- return this.getFilter(position)?.id;
44967
- }
44968
- getTable({ sheetId, col, row }) {
44969
- return this.getTables(sheetId).find((table) => isInside(col, row, table.range.zone));
44970
- }
44971
- /** Get the filter tables that are fully inside the given zone */
44972
- getTablesInZone(sheetId, zone) {
44973
- return this.getTables(sheetId).filter((table) => isZoneInside(table.range.zone, zone));
45048
+ getCoreTable({ sheetId, col, row }) {
45049
+ return this.getCoreTables(sheetId).find((table) => isInside(col, row, table.range.zone));
44974
45050
  }
44975
45051
  getTablesOverlappingZones(sheetId, zones) {
44976
- return this.getTables(sheetId).filter((table) => zones.some((zone) => overlap(table.range.zone, zone)));
44977
- }
44978
- doesZonesContainFilter(sheetId, zones) {
44979
- return (this.getTablesOverlappingZones(sheetId, zones).filter((table) => table.config.hasFilters)
44980
- .length > 0);
44981
- }
44982
- getFilterHeaders(sheetId) {
44983
- const headers = [];
44984
- for (const table of this.getTables(sheetId)) {
44985
- if (!table.config.hasFilters) {
44986
- continue;
44987
- }
44988
- const zone = table.range.zone;
44989
- const row = zone.top;
44990
- for (let col = zone.left; col <= zone.right; col++) {
44991
- headers.push({ col, row });
44992
- }
44993
- }
44994
- return headers;
44995
- }
44996
- isFilterHeader({ sheetId, col, row }) {
44997
- const headers = this.getFilterHeaders(sheetId);
44998
- return headers.some((header) => header.col === col && header.row === row);
45052
+ return this.getCoreTables(sheetId).filter((table) => zones.some((zone) => overlap(table.range.zone, zone)));
44999
45053
  }
45000
45054
  /** Extend a table down one row */
45001
45055
  extendTableDown(sheetId, table) {
45002
45056
  const newRange = this.getters.extendRange(table.range, "ROW", 1);
45003
- this.history.update("tables", sheetId, table.id, this.updateTable(table, newRange));
45057
+ this.history.update("tables", sheetId, table.id, this.updateStaticTable(table, newRange));
45004
45058
  }
45005
45059
  /** Extend a table right one col */
45006
45060
  extendTableRight(sheetId, table) {
45007
45061
  const newRange = this.getters.extendRange(table.range, "COL", 1);
45008
- this.history.update("tables", sheetId, table.id, this.updateTable(table, newRange));
45062
+ this.history.update("tables", sheetId, table.id, this.updateStaticTable(table, newRange));
45009
45063
  }
45010
45064
  /**
45011
45065
  * Check if an UpdateCell command should cause the given table to be extended by one row or col.
@@ -45042,12 +45096,22 @@ class TablePlugin extends CorePlugin {
45042
45096
  const cellContent = this.getters.getCell(cellPosition)?.content;
45043
45097
  if (cellContent ||
45044
45098
  this.getters.isInMerge(cellPosition) ||
45045
- this.getters.getTable(cellPosition)) {
45099
+ this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
45046
45100
  return "none";
45047
45101
  }
45048
45102
  }
45049
45103
  return direction;
45050
45104
  }
45105
+ getTableFromZone(sheetId, zone) {
45106
+ for (const table of this.getCoreTables(sheetId)) {
45107
+ const tableZone = table.range.zone;
45108
+ // Only check top left to match dynamic tables
45109
+ if (tableZone.left === zone.left && tableZone.top === zone.top) {
45110
+ return table;
45111
+ }
45112
+ }
45113
+ return undefined;
45114
+ }
45051
45115
  checkUpdatedTableZoneIsValid(cmd) {
45052
45116
  if (!cmd.newTableRange) {
45053
45117
  return "Success" /* CommandResult.Success */;
@@ -45057,7 +45121,11 @@ class TablePlugin extends CorePlugin {
45057
45121
  if (zoneIsInSheet !== "Success" /* CommandResult.Success */) {
45058
45122
  return zoneIsInSheet;
45059
45123
  }
45060
- const overlappingTables = this.getTablesOverlappingZones(cmd.sheetId, [newTableZone]).filter((table) => !deepEquals(table.range.zone, cmd.zone));
45124
+ const updatedTable = this.getTableFromZone(cmd.sheetId, cmd.zone);
45125
+ if (!updatedTable) {
45126
+ return "TableNotFound" /* CommandResult.TableNotFound */;
45127
+ }
45128
+ const overlappingTables = this.getTablesOverlappingZones(cmd.sheetId, [newTableZone]).filter((table) => table.id !== updatedTable.id);
45061
45129
  return overlappingTables.length ? "TableOverlap" /* CommandResult.TableOverlap */ : "Success" /* CommandResult.Success */;
45062
45130
  }
45063
45131
  checkTableConfigUpdateIsValid(config) {
@@ -45075,7 +45143,7 @@ class TablePlugin extends CorePlugin {
45075
45143
  }
45076
45144
  return "Success" /* CommandResult.Success */;
45077
45145
  }
45078
- createTable(id, tableRange, config, filters) {
45146
+ createStaticTable(id, type, tableRange, config, filters) {
45079
45147
  const zone = tableRange.zone;
45080
45148
  if (!filters) {
45081
45149
  filters = [];
@@ -45090,9 +45158,51 @@ class TablePlugin extends CorePlugin {
45090
45158
  range: tableRange,
45091
45159
  filters,
45092
45160
  config,
45161
+ type,
45093
45162
  };
45094
45163
  }
45095
- updateTable(table, newRange, configUpdate) {
45164
+ createDynamicTable(id, tableRange, config) {
45165
+ const zone = zoneToTopLeft(tableRange.zone);
45166
+ return {
45167
+ id,
45168
+ range: this.getters.getRangeFromZone(tableRange.sheetId, zone),
45169
+ config,
45170
+ type: "dynamic",
45171
+ };
45172
+ }
45173
+ updateTable(cmd) {
45174
+ const table = this.getTableFromZone(cmd.sheetId, cmd.zone);
45175
+ if (!table) {
45176
+ return;
45177
+ }
45178
+ const newTableRange = cmd.newTableRange
45179
+ ? this.getters.getRangeFromRangeData(cmd.newTableRange)
45180
+ : undefined;
45181
+ if (newTableRange) {
45182
+ const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, newTableRange.zone);
45183
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
45184
+ }
45185
+ const range = newTableRange || table.range;
45186
+ const newConfig = this.updateTableConfig(cmd.config, table.config);
45187
+ const newTableType = cmd.tableType ?? table.type;
45188
+ if ((newTableType === "dynamic" && table.type !== "dynamic") ||
45189
+ (newTableType !== "dynamic" && table.type === "dynamic")) {
45190
+ const newTable = newTableType === "dynamic"
45191
+ ? this.createDynamicTable(table.id, range, newConfig)
45192
+ : this.createStaticTable(table.id, newTableType, range, newConfig);
45193
+ this.history.update("tables", cmd.sheetId, table.id, newTable);
45194
+ }
45195
+ else {
45196
+ const updatedTable = table.type === "dynamic"
45197
+ ? this.updateDynamicTable(table, range, newConfig)
45198
+ : this.updateStaticTable(table, range, newConfig, newTableType);
45199
+ this.history.update("tables", cmd.sheetId, table.id, updatedTable);
45200
+ }
45201
+ }
45202
+ updateStaticTable(table, newRange, configUpdate, newTableType = table.type) {
45203
+ if (newTableType === "dynamic") {
45204
+ throw new Error("Cannot use updateStaticTable to update a dynamic table");
45205
+ }
45096
45206
  const tableRange = newRange ? newRange : table.range;
45097
45207
  const tableZone = tableRange.zone;
45098
45208
  const newConfig = this.updateTableConfig(configUpdate, table.config);
@@ -45113,8 +45223,16 @@ class TablePlugin extends CorePlugin {
45113
45223
  range: tableRange,
45114
45224
  config,
45115
45225
  filters: filters.length ? filters : table.filters,
45226
+ type: newTableType,
45116
45227
  };
45117
45228
  }
45229
+ updateDynamicTable(table, newRange, newConfig) {
45230
+ const range = newRange
45231
+ ? this.getters.getRangeFromZone(newRange.sheetId, zoneToTopLeft(newRange.zone))
45232
+ : table.range;
45233
+ const config = newConfig ? newConfig : table.config;
45234
+ return { ...table, range, config };
45235
+ }
45118
45236
  /**
45119
45237
  * Update the old config of a table with the new partial config from an UpdateTable command.
45120
45238
  *
@@ -45136,35 +45254,29 @@ class TablePlugin extends CorePlugin {
45136
45254
  }
45137
45255
  createFilterFromZone(id, sheetId, zone, config) {
45138
45256
  const range = this.getters.getRangeFromZone(sheetId, zone);
45139
- return this.createFilter(id, range, config);
45257
+ return createFilter(id, range, config, this.getters.getRangeFromZone);
45140
45258
  }
45141
- createFilter(id, range, config) {
45142
- const zone = range.zone;
45143
- if (zone.left !== zone.right) {
45144
- throw new Error("Can only define a filter on a single column");
45145
- }
45146
- const contentZone = getTableContentZone(zone, config);
45147
- const filteredRange = contentZone
45148
- ? this.getters.getRangeFromZone(range.sheetId, contentZone)
45149
- : undefined;
45150
- return {
45151
- id,
45152
- rangeWithHeaders: range,
45153
- col: zone.left,
45154
- filteredRange,
45155
- };
45156
- }
45157
- copyTableForSheet(sheetId, table) {
45259
+ copyStaticTableForSheet(sheetId, table) {
45158
45260
  const newRange = this.getters.getRangeFromZone(sheetId, table.range.zone);
45159
45261
  const newFilters = table.filters.map((filter) => {
45160
45262
  const newFilterRange = this.getters.getRangeFromZone(sheetId, filter.rangeWithHeaders.zone);
45161
- return this.createFilter(filter.id, newFilterRange, table.config);
45263
+ return createFilter(filter.id, newFilterRange, table.config, this.getters.getRangeFromZone);
45162
45264
  });
45163
45265
  return {
45164
45266
  id: table.id,
45165
45267
  range: newRange,
45166
45268
  filters: newFilters,
45167
45269
  config: deepCopy(table.config),
45270
+ type: table.type,
45271
+ };
45272
+ }
45273
+ copyDynamicTableForSheet(sheetId, table) {
45274
+ const newRange = this.getters.getRangeFromZone(sheetId, table.range.zone);
45275
+ return {
45276
+ id: table.id,
45277
+ range: newRange,
45278
+ config: deepCopy(table.config),
45279
+ type: "dynamic",
45168
45280
  };
45169
45281
  }
45170
45282
  applyRangeChangeOnTable(sheetId, table, applyChange) {
@@ -45179,6 +45291,11 @@ class TablePlugin extends CorePlugin {
45179
45291
  default:
45180
45292
  newTableRange = tableRangeChange.range;
45181
45293
  }
45294
+ if (table.type === "dynamic") {
45295
+ const newTable = this.updateDynamicTable(table, newTableRange);
45296
+ this.history.update("tables", sheetId, table.id, newTable);
45297
+ return;
45298
+ }
45182
45299
  const filters = [];
45183
45300
  for (const filter of table.filters) {
45184
45301
  const filterRangeChange = applyChange(filter.rangeWithHeaders);
@@ -45190,7 +45307,7 @@ class TablePlugin extends CorePlugin {
45190
45307
  break;
45191
45308
  default:
45192
45309
  const newFilterRange = filterRangeChange.range;
45193
- const newFilter = this.createFilter(filter.id, newFilterRange, table.config);
45310
+ const newFilter = createFilter(filter.id, newFilterRange, table.config, this.getters.getRangeFromZone);
45194
45311
  filters.push(newFilter);
45195
45312
  }
45196
45313
  }
@@ -45205,7 +45322,7 @@ class TablePlugin extends CorePlugin {
45205
45322
  }
45206
45323
  filters.sort((f1, f2) => f1.col - f2.col);
45207
45324
  }
45208
- const newTable = this.createTable(table.id, newTableRange, table.config, filters);
45325
+ const newTable = this.createStaticTable(table.id, table.type, newTableRange, table.config, filters);
45209
45326
  this.history.update("tables", sheetId, table.id, newTable);
45210
45327
  }
45211
45328
  // ---------------------------------------------------------------------------
@@ -45216,16 +45333,20 @@ class TablePlugin extends CorePlugin {
45216
45333
  for (const tableData of sheet.tables || []) {
45217
45334
  const uuid = this.uuidGenerator.uuidv4();
45218
45335
  const tableConfig = tableData.config || DEFAULT_TABLE_CONFIG;
45219
- const tableRange = this.getters.getRangeFromSheetXC(sheet.id, tableData.range);
45220
- const table = this.createTable(uuid, tableRange, tableConfig);
45336
+ const range = this.getters.getRangeFromSheetXC(sheet.id, tableData.range);
45337
+ const tableType = tableData.type || "static";
45338
+ const table = tableType === "dynamic"
45339
+ ? this.createDynamicTable(uuid, range, tableConfig)
45340
+ : this.createStaticTable(uuid, tableType, range, tableConfig);
45221
45341
  this.history.update("tables", sheet.id, table.id, table);
45222
45342
  }
45223
45343
  }
45224
45344
  }
45225
45345
  export(data) {
45226
45346
  for (const sheet of data.sheets) {
45227
- for (const table of this.getTables(sheet.id)) {
45228
- const tableData = { range: zoneToXc(table.range.zone) };
45347
+ for (const table of this.getCoreTables(sheet.id)) {
45348
+ const range = zoneToXc(table.range.zone);
45349
+ const tableData = { range, type: table.type };
45229
45350
  if (!deepEquals(table.config, DEFAULT_TABLE_CONFIG)) {
45230
45351
  tableData.config = table.config;
45231
45352
  }
@@ -46941,14 +47062,18 @@ class Evaluator {
46941
47062
  if (!this.spreadingRelations.isArrayFormula(position)) {
46942
47063
  return [];
46943
47064
  }
46944
- return Array.from(this.spreadingRelations.getArrayResultPositions(position));
47065
+ if (this.evaluatedCells.get(position)?.type === CellValueType.error) {
47066
+ return [position];
47067
+ }
47068
+ const spreadPositions = Array.from(this.spreadingRelations.getArrayResultPositions(position));
47069
+ return [position, ...spreadPositions];
46945
47070
  }
46946
47071
  getEvaluatedPositions() {
46947
47072
  return this.evaluatedCells.keys();
46948
47073
  }
46949
47074
  getArrayFormulaSpreadingOn(position) {
46950
47075
  if (!this.spreadingRelations.hasArrayFormulaResult(position)) {
46951
- return undefined;
47076
+ return this.spreadingRelations.isArrayFormula(position) ? position : undefined;
46952
47077
  }
46953
47078
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(position);
46954
47079
  return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
@@ -47077,6 +47202,7 @@ class Evaluator {
47077
47202
  if (!this.blockedArrayFormulas.has(position)) {
47078
47203
  this.invalidateSpreading(position);
47079
47204
  }
47205
+ this.spreadingRelations.removeNode(position);
47080
47206
  const cell = this.getters.getCell(position);
47081
47207
  if (cell === undefined) {
47082
47208
  return EMPTY_CELL;
@@ -47187,7 +47313,6 @@ class Evaluator {
47187
47313
  this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
47188
47314
  this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
47189
47315
  }
47190
- this.spreadingRelations.removeNode(position);
47191
47316
  }
47192
47317
  // ----------------------------------------------------------
47193
47318
  // COMMON FUNCTIONALITY
@@ -48285,6 +48410,181 @@ class EvaluationDataValidationPlugin extends UIPlugin {
48285
48410
  }
48286
48411
  }
48287
48412
 
48413
+ class DynamicTablesPlugin extends UIPlugin {
48414
+ static getters = [
48415
+ "canCreateDynamicTableOnZones",
48416
+ "doesZonesContainFilter",
48417
+ "getFilter",
48418
+ "getFilters",
48419
+ "getTable",
48420
+ "getTables",
48421
+ "getTablesOverlappingZones",
48422
+ "getFilterId",
48423
+ "getFilterHeaders",
48424
+ "isFilterHeader",
48425
+ ];
48426
+ tables = {};
48427
+ handle(cmd) {
48428
+ if (invalidateEvaluationCommands.has(cmd.type) ||
48429
+ (cmd.type === "UPDATE_CELL" && "content" in cmd) ||
48430
+ cmd.type === "EVALUATE_CELLS") {
48431
+ this.tables = {};
48432
+ return;
48433
+ }
48434
+ switch (cmd.type) {
48435
+ case "CREATE_TABLE":
48436
+ case "REMOVE_TABLE":
48437
+ case "UPDATE_TABLE":
48438
+ case "DELETE_CONTENT":
48439
+ this.tables = {};
48440
+ break;
48441
+ }
48442
+ }
48443
+ finalize() {
48444
+ for (const sheetId of this.getters.getSheetIds()) {
48445
+ if (!this.tables[sheetId]) {
48446
+ this.tables[sheetId] = this.computeTables(sheetId);
48447
+ }
48448
+ }
48449
+ }
48450
+ computeTables(sheetId) {
48451
+ const tables = [];
48452
+ const coreTables = this.getters.getCoreTables(sheetId);
48453
+ // First we create the static tables, so we can use them to compute collision with dynamic tables
48454
+ for (const table of coreTables) {
48455
+ if (table.type === "dynamic")
48456
+ continue;
48457
+ tables.push(table);
48458
+ }
48459
+ const staticTables = [...tables];
48460
+ // Then we create the dynamic tables
48461
+ for (const coreTable of coreTables) {
48462
+ if (coreTable.type !== "dynamic")
48463
+ continue;
48464
+ const table = this.coreTableToTable(sheetId, coreTable);
48465
+ let tableZone = table.range.zone;
48466
+ // Reduce the zone to avoid collision with static tables. Per design, dynamic tables can't overlap with other
48467
+ // dynamic tables, because formulas cannot spread on the same area, so we don't need to check for that.
48468
+ for (const staticTable of staticTables) {
48469
+ if (overlap(tableZone, staticTable.range.zone)) {
48470
+ tableZone = { ...tableZone, right: staticTable.range.zone.left - 1 };
48471
+ }
48472
+ }
48473
+ tables.push({ ...table, range: this.getters.getRangeFromZone(sheetId, tableZone) });
48474
+ }
48475
+ return tables;
48476
+ }
48477
+ getFilters(sheetId) {
48478
+ return this.getTables(sheetId)
48479
+ .filter((table) => table.config.hasFilters)
48480
+ .map((table) => table.filters)
48481
+ .flat();
48482
+ }
48483
+ getTables(sheetId) {
48484
+ return this.tables[sheetId] || [];
48485
+ }
48486
+ getFilter(position) {
48487
+ const table = this.getTable(position);
48488
+ if (!table || !table.config.hasFilters) {
48489
+ return undefined;
48490
+ }
48491
+ return table.filters.find((filter) => filter.col === position.col);
48492
+ }
48493
+ getFilterId(position) {
48494
+ return this.getFilter(position)?.id;
48495
+ }
48496
+ getTable({ sheetId, col, row }) {
48497
+ return this.getTables(sheetId).find((table) => isInside(col, row, table.range.zone));
48498
+ }
48499
+ getTablesOverlappingZones(sheetId, zones) {
48500
+ return this.getTables(sheetId).filter((table) => zones.some((zone) => overlap(table.range.zone, zone)));
48501
+ }
48502
+ doesZonesContainFilter(sheetId, zones) {
48503
+ return this.getTablesOverlappingZones(sheetId, zones).some((table) => table.config.hasFilters);
48504
+ }
48505
+ getFilterHeaders(sheetId) {
48506
+ const headers = [];
48507
+ for (const table of this.getTables(sheetId)) {
48508
+ if (!table.config.hasFilters) {
48509
+ continue;
48510
+ }
48511
+ const zone = table.range.zone;
48512
+ const row = zone.top;
48513
+ for (let col = zone.left; col <= zone.right; col++) {
48514
+ headers.push({ sheetId, col, row });
48515
+ }
48516
+ }
48517
+ return headers;
48518
+ }
48519
+ isFilterHeader({ sheetId, col, row }) {
48520
+ const headers = this.getFilterHeaders(sheetId);
48521
+ return headers.some((header) => header.col === col && header.row === row);
48522
+ }
48523
+ /**
48524
+ * Check if we can create a dynamic table on the given zones.
48525
+ * - The zones must be continuous
48526
+ * - The union of the zones must be either:
48527
+ * - A single cell that contains an array formula
48528
+ * - All the spread cells of a single array formula
48529
+ */
48530
+ canCreateDynamicTableOnZones(sheetId, zones) {
48531
+ if (!areZonesContinuous(zones)) {
48532
+ return false;
48533
+ }
48534
+ const unionZone = union(...zones);
48535
+ const topLeft = { col: unionZone.left, row: unionZone.top, sheetId };
48536
+ const parentSpreadingCell = this.getters.getArrayFormulaSpreadingOn(topLeft);
48537
+ if (!parentSpreadingCell) {
48538
+ return false;
48539
+ }
48540
+ else if (deepEquals(parentSpreadingCell, topLeft) && getZoneArea(unionZone) === 1) {
48541
+ return true;
48542
+ }
48543
+ const spreadPositions = this.getters.getSpreadPositionsOf(parentSpreadingCell);
48544
+ return deepEquals(unionZone, unionPositionsToZone(spreadPositions));
48545
+ }
48546
+ coreTableToTable(sheetId, table) {
48547
+ if (table.type !== "dynamic") {
48548
+ return table;
48549
+ }
48550
+ const tableZone = table.range.zone;
48551
+ const tablePosition = { sheetId, col: tableZone.left, row: tableZone.top };
48552
+ const spreadPositions = this.getters.getSpreadPositionsOf(tablePosition);
48553
+ const zone = spreadPositions.length ? unionPositionsToZone(spreadPositions) : table.range.zone;
48554
+ const range = this.getters.getRangeFromZone(sheetId, zone);
48555
+ const filters = this.getDynamicTableFilters(sheetId, table, zone);
48556
+ return { id: table.id, range, filters, config: table.config };
48557
+ }
48558
+ getDynamicTableFilters(sheetId, table, tableZone) {
48559
+ const filters = [];
48560
+ const { top, bottom, left, right } = tableZone;
48561
+ for (let col = left; col <= right; col++) {
48562
+ const tableColIndex = col - left;
48563
+ const zone = { left: col, right: col, top, bottom };
48564
+ const filter = createFilter(this.getDynamicTableFilterId(table.id, tableColIndex), this.getters.getRangeFromZone(sheetId, zone), table.config, this.getters.getRangeFromZone);
48565
+ filters.push(filter);
48566
+ }
48567
+ return filters;
48568
+ }
48569
+ getDynamicTableFilterId(tableId, tableCol) {
48570
+ return tableId + "_" + tableCol;
48571
+ }
48572
+ exportForExcel(data) {
48573
+ for (const sheet of data.sheets) {
48574
+ for (const tableData of sheet.tables) {
48575
+ const zone = toZone(tableData.range);
48576
+ const topLeft = { sheetId: sheet.id, col: zone.left, row: zone.top };
48577
+ const coreTable = this.getters.getCoreTable(topLeft);
48578
+ const table = this.getTable(topLeft);
48579
+ if (coreTable?.type !== "dynamic" || !table) {
48580
+ continue;
48581
+ }
48582
+ tableData.range = zoneToXc(table.range.zone);
48583
+ }
48584
+ }
48585
+ }
48586
+ }
48587
+
48288
48588
  class HeaderSizeUIPlugin extends UIPlugin {
48289
48589
  static getters = ["getRowSize", "getHeaderSize"];
48290
48590
  tallestCellInRow = {};
@@ -51332,10 +51632,11 @@ class TableAutofillPlugin extends UIPlugin {
51332
51632
  handle(cmd) {
51333
51633
  switch (cmd.type) {
51334
51634
  case "AUTOFILL_TABLE_COLUMN":
51335
- const table = this.getters.getTable(cmd);
51635
+ const table = this.getters.getCoreTable(cmd);
51336
51636
  const cell = this.getters.getCell(cmd);
51337
- if (!table || !table.config.automaticAutofill || !cell?.isFormula)
51637
+ if (!table?.config.automaticAutofill || table.type === "dynamic" || !cell?.isFormula) {
51338
51638
  return;
51639
+ }
51339
51640
  const { col, row } = cmd;
51340
51641
  const tableContentZone = getTableContentZone(table.range.zone, table.config);
51341
51642
  if (tableContentZone && isInside(col, row, tableContentZone)) {
@@ -52064,9 +52365,6 @@ class FilterEvaluationPlugin extends UIPlugin {
52064
52365
  case "START":
52065
52366
  for (const sheetId of this.getters.getSheetIds()) {
52066
52367
  this.filterValues[sheetId] = {};
52067
- for (const filter of this.getters.getFilters(sheetId)) {
52068
- this.filterValues[sheetId][filter.id] = [];
52069
- }
52070
52368
  }
52071
52369
  break;
52072
52370
  case "CREATE_SHEET":
@@ -52087,16 +52385,7 @@ class FilterEvaluationPlugin extends UIPlugin {
52087
52385
  this.updateHiddenRows();
52088
52386
  break;
52089
52387
  case "DUPLICATE_SHEET":
52090
- const filterValues = {};
52091
- for (const newFilter of this.getters.getFilters(cmd.sheetIdTo)) {
52092
- const zone = newFilter.rangeWithHeaders.zone;
52093
- filterValues[newFilter.id] = this.getFilterHiddenValues({
52094
- sheetId: cmd.sheetId,
52095
- col: zone.left,
52096
- row: zone.top,
52097
- });
52098
- }
52099
- this.filterValues[cmd.sheetIdTo] = filterValues;
52388
+ this.filterValues[cmd.sheetIdTo] = deepCopy(this.filterValues[cmd.sheetId]);
52100
52389
  break;
52101
52390
  // If we don't handle DELETE_SHEET, on one hand we will have some residual data, on the other hand we keep the data
52102
52391
  // on DELETE_SHEET followed by undo
@@ -52473,6 +52762,7 @@ class GridSelectionPlugin extends UIPlugin {
52473
52762
  this.gridSelection.zones = this.gridSelection.zones.map((z) => this.getters.expandZone(sheetId, z));
52474
52763
  this.gridSelection.anchor.zone = this.getters.expandZone(sheetId, this.gridSelection.anchor.zone);
52475
52764
  this.setSelectionMixin(this.gridSelection.anchor, this.gridSelection.zones);
52765
+ this.selectedFigureId = null;
52476
52766
  break;
52477
52767
  }
52478
52768
  /** Any change to the selection has to be reflected in the selection processor. */
@@ -54039,8 +54329,9 @@ const coreViewsPluginRegistry = new Registry()
54039
54329
  .add("evaluation_chart", EvaluationChartPlugin)
54040
54330
  .add("evaluation_cf", EvaluationConditionalFormatPlugin)
54041
54331
  .add("row_size", HeaderSizeUIPlugin)
54042
- .add("custom_colors", CustomColorsPlugin)
54043
- .add("data_validation_ui", EvaluationDataValidationPlugin);
54332
+ .add("data_validation_ui", EvaluationDataValidationPlugin)
54333
+ .add("dynamic_tables", DynamicTablesPlugin)
54334
+ .add("custom_colors", CustomColorsPlugin);
54044
54335
 
54045
54336
  const clickableCellRegistry = new Registry();
54046
54337
  clickableCellRegistry.add("link", {
@@ -56091,9 +56382,6 @@ css /* scss */ `
56091
56382
  .text-muted {
56092
56383
  color: grey !important;
56093
56384
  }
56094
- button {
56095
- color: #333;
56096
- }
56097
56385
  .o-disabled {
56098
56386
  opacity: 0.4;
56099
56387
  pointer: default;
@@ -56214,17 +56502,17 @@ css /* scss */ `
56214
56502
  }
56215
56503
 
56216
56504
  .o-button {
56217
- border: 1px solid lightgrey;
56505
+ border: 1px solid;
56218
56506
  padding: 0px 20px 0px 20px;
56219
56507
  border-radius: 4px;
56220
56508
  font-weight: 500;
56221
56509
  font-size: 14px;
56222
56510
  height: 30px;
56223
56511
  line-height: 16px;
56224
- background: white;
56225
56512
  margin-right: 8px;
56226
- &:hover:enabled {
56227
- background-color: rgba(0, 0, 0, 0.08);
56513
+
56514
+ &:not(:hover) {
56515
+ background-color: transparent;
56228
56516
  }
56229
56517
 
56230
56518
  &:enabled {
@@ -56238,6 +56526,15 @@ css /* scss */ `
56238
56526
  &:last-child {
56239
56527
  margin-right: 0px;
56240
56528
  }
56529
+
56530
+ &.o-button-grey {
56531
+ border-color: lightgrey;
56532
+ background: #ffffff;
56533
+ color: #333;
56534
+ &:hover:enabled {
56535
+ background-color: rgba(0, 0, 0, 0.08);
56536
+ }
56537
+ }
56241
56538
  }
56242
56539
 
56243
56540
  .o-input {
@@ -60124,6 +60421,6 @@ const constants = {
60124
60421
  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 };
60125
60422
 
60126
60423
 
60127
- __info__.version = "17.3.0-alpha.0";
60128
- __info__.date = "2024-03-20T13:42:32.042Z";
60129
- __info__.hash = "073e154";
60424
+ __info__.version = "17.3.0-alpha.1";
60425
+ __info__.date = "2024-03-25T09:43:36.072Z";
60426
+ __info__.hash = "4095c41";