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