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