@odoo/o-spreadsheet 16.2.0-alpha.2 → 16.2.0-alpha.4

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.
@@ -274,7 +274,6 @@
274
274
  ComponentsImportance[ComponentsImportance["Dropdown"] = 16] = "Dropdown";
275
275
  ComponentsImportance[ComponentsImportance["GridComposer"] = 20] = "GridComposer";
276
276
  ComponentsImportance[ComponentsImportance["TopBarComposer"] = 21] = "TopBarComposer";
277
- ComponentsImportance[ComponentsImportance["ColorPicker"] = 25] = "ColorPicker";
278
277
  ComponentsImportance[ComponentsImportance["IconPicker"] = 25] = "IconPicker";
279
278
  ComponentsImportance[ComponentsImportance["Popover"] = 30] = "Popover";
280
279
  ComponentsImportance[ComponentsImportance["ChartAnchor"] = 1000] = "ChartAnchor";
@@ -2027,10 +2026,10 @@
2027
2026
  if (right !== undefined && bottom !== undefined)
2028
2027
  return { left, top, right, bottom };
2029
2028
  else if (bottom === undefined && right !== undefined) {
2030
- return { right, top, left, bottom: this.getSheetSize(this.sheetId).height - 1 };
2029
+ return { right, top, left, bottom: this.getSheetSize(this.sheetId).numberOfRows - 1 };
2031
2030
  }
2032
2031
  else if (right === undefined && bottom !== undefined) {
2033
- return { bottom, left, top, right: this.getSheetSize(this.sheetId).width - 1 };
2032
+ return { bottom, left, top, right: this.getSheetSize(this.sheetId).numberOfCols - 1 };
2034
2033
  }
2035
2034
  throw new Error(_lt("Bad zone format"));
2036
2035
  }
@@ -2653,13 +2652,13 @@
2653
2652
  }
2654
2653
  function zoneToDimension(zone) {
2655
2654
  return {
2656
- height: zone.bottom - zone.top + 1,
2657
- width: zone.right - zone.left + 1,
2655
+ numberOfRows: zone.bottom - zone.top + 1,
2656
+ numberOfCols: zone.right - zone.left + 1,
2658
2657
  };
2659
2658
  }
2660
2659
  function isOneDimensional(zone) {
2661
- const { width, height } = zoneToDimension(zone);
2662
- return width === 1 || height === 1;
2660
+ const { numberOfCols, numberOfRows } = zoneToDimension(zone);
2661
+ return numberOfCols === 1 || numberOfRows === 1;
2663
2662
  }
2664
2663
  /**
2665
2664
  * Array of all positions in the zone.
@@ -2844,7 +2843,7 @@
2844
2843
  return `background-color: ${this.background}`;
2845
2844
  }
2846
2845
  get chartRuntime() {
2847
- const runtime = this.env.model.getters.getChartRuntime(this.env.model.getters.getActiveSheetId(), this.props.figure.id);
2846
+ const runtime = this.env.model.getters.getChartRuntime(this.props.figure.id);
2848
2847
  if (!("chartJsConfig" in runtime)) {
2849
2848
  throw new Error("Unsupported chart runtime");
2850
2849
  }
@@ -3109,7 +3108,7 @@
3109
3108
  this.ctx = document.createElement("canvas").getContext("2d");
3110
3109
  }
3111
3110
  get runtime() {
3112
- return this.env.model.getters.getChartRuntime(this.env.model.getters.getActiveSheetId(), this.props.figure.id);
3111
+ return this.env.model.getters.getChartRuntime(this.props.figure.id);
3113
3112
  }
3114
3113
  get title() {
3115
3114
  return this.runtime.title;
@@ -3134,7 +3133,7 @@
3134
3133
  return this.runtime.fontColor;
3135
3134
  }
3136
3135
  get secondaryFontColor() {
3137
- return relativeLuminance(this.primaryFontColor) <= 0.3 ? "#757575" : "#bbbbbb";
3136
+ return relativeLuminance(this.backgroundColor) > 0.3 ? "#525252" : "#C8C8C8";
3138
3137
  }
3139
3138
  get figure() {
3140
3139
  return this.props.figure;
@@ -3498,7 +3497,7 @@
3498
3497
  CommandResult[CommandResult["InvalidRange"] = 25] = "InvalidRange";
3499
3498
  CommandResult[CommandResult["InvalidZones"] = 26] = "InvalidZones";
3500
3499
  CommandResult[CommandResult["InvalidSheetId"] = 27] = "InvalidSheetId";
3501
- CommandResult[CommandResult["DuplicatedImageId"] = 28] = "DuplicatedImageId";
3500
+ CommandResult[CommandResult["InvalidFigureId"] = 28] = "InvalidFigureId";
3502
3501
  CommandResult[CommandResult["InputAlreadyFocused"] = 29] = "InputAlreadyFocused";
3503
3502
  CommandResult[CommandResult["MaximumRangesReached"] = 30] = "MaximumRangesReached";
3504
3503
  CommandResult[CommandResult["InvalidChartDefinition"] = 31] = "InvalidChartDefinition";
@@ -3555,6 +3554,7 @@
3555
3554
  CommandResult[CommandResult["DuplicatedFigureId"] = 82] = "DuplicatedFigureId";
3556
3555
  CommandResult[CommandResult["InvalidSelectionStep"] = 83] = "InvalidSelectionStep";
3557
3556
  CommandResult[CommandResult["DuplicatedChartId"] = 84] = "DuplicatedChartId";
3557
+ CommandResult[CommandResult["ChartDoesNotExist"] = 85] = "ChartDoesNotExist";
3558
3558
  })(exports.CommandResult || (exports.CommandResult = {}));
3559
3559
 
3560
3560
  var DIRECTION;
@@ -3745,11 +3745,11 @@
3745
3745
  const labelZone = (_a = ds.labelCell) === null || _a === void 0 ? void 0 : _a.zone;
3746
3746
  let dataZone = ds.dataRange.zone;
3747
3747
  if (labelZone) {
3748
- const { height, width } = zoneToDimension(dataZone);
3749
- if (height === 1) {
3748
+ const { numberOfRows, numberOfCols } = zoneToDimension(dataZone);
3749
+ if (numberOfRows === 1) {
3750
3750
  dataZone = { ...dataZone, left: dataZone.left + 1 };
3751
3751
  }
3752
- else if (width === 1) {
3752
+ else if (numberOfCols === 1) {
3753
3753
  dataZone = { ...dataZone, top: dataZone.top + 1 };
3754
3754
  }
3755
3755
  }
@@ -5462,6 +5462,76 @@
5462
5462
  chartComponentRegistry.add("gauge", ChartJsComponent);
5463
5463
  chartComponentRegistry.add("scorecard", ScorecardChart$1);
5464
5464
 
5465
+ // -----------------------------------------------------------------------------
5466
+ // STYLE
5467
+ // -----------------------------------------------------------------------------
5468
+ css /* scss */ `
5469
+ .o-chart-container {
5470
+ width: 100%;
5471
+ height: 100%;
5472
+ position: relative;
5473
+ }
5474
+ `;
5475
+ class ChartFigure extends owl.Component {
5476
+ get chartType() {
5477
+ return this.env.model.getters.getChartType(this.props.figure.id);
5478
+ }
5479
+ get chartComponent() {
5480
+ const type = this.chartType;
5481
+ const component = chartComponentRegistry.get(type);
5482
+ if (!component) {
5483
+ throw new Error(`Component is not defined for type ${type}`);
5484
+ }
5485
+ return component;
5486
+ }
5487
+ }
5488
+ ChartFigure.template = "o-spreadsheet-ChartFigure";
5489
+ ChartFigure.components = {};
5490
+ ChartFigure.props = {
5491
+ figure: Object,
5492
+ onFigureDeleted: Function,
5493
+ };
5494
+
5495
+ function interactiveCut(env) {
5496
+ const result = env.model.dispatch("CUT");
5497
+ if (!result.isSuccessful) {
5498
+ if (result.isCancelledBecause(19 /* CommandResult.WrongCutSelection */)) {
5499
+ env.raiseError(_lt("This operation is not allowed with multiple selections."));
5500
+ }
5501
+ }
5502
+ }
5503
+
5504
+ const PasteInteractiveContent = {
5505
+ wrongPasteSelection: _lt("This operation is not allowed with multiple selections."),
5506
+ willRemoveExistingMerge: _lt("This operation is not possible due to a merge. Please remove the merges first than try again."),
5507
+ wrongFigurePasteOption: _lt("Cannot do a special paste of a figure."),
5508
+ frozenPaneOverlap: _lt("Cannot paste merged cells over a frozen pane."),
5509
+ };
5510
+ function handlePasteResult(env, result) {
5511
+ if (!result.isSuccessful) {
5512
+ if (result.reasons.includes(20 /* CommandResult.WrongPasteSelection */)) {
5513
+ env.raiseError(PasteInteractiveContent.wrongPasteSelection);
5514
+ }
5515
+ else if (result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
5516
+ env.raiseError(PasteInteractiveContent.willRemoveExistingMerge);
5517
+ }
5518
+ else if (result.reasons.includes(22 /* CommandResult.WrongFigurePasteOption */)) {
5519
+ env.raiseError(PasteInteractiveContent.wrongFigurePasteOption);
5520
+ }
5521
+ else if (result.reasons.includes(75 /* CommandResult.FrozenPaneOverlap */)) {
5522
+ env.raiseError(PasteInteractiveContent.frozenPaneOverlap);
5523
+ }
5524
+ }
5525
+ }
5526
+ function interactivePaste(env, target, pasteOption) {
5527
+ const result = env.model.dispatch("PASTE", { target, pasteOption });
5528
+ handlePasteResult(env, result);
5529
+ }
5530
+ function interactivePasteFromOS(env, target, text) {
5531
+ const result = env.model.dispatch("PASTE_FROM_OS_CLIPBOARD", { target, text });
5532
+ handlePasteResult(env, result);
5533
+ }
5534
+
5465
5535
  function createMenu(menuItems) {
5466
5536
  return menuItems.map(createMenuItem).sort((a, b) => a.sequence - b.sequence);
5467
5537
  }
@@ -5542,984 +5612,281 @@
5542
5612
  }
5543
5613
 
5544
5614
  /**
5545
- * Return the o-spreadsheet element position relative
5546
- * to the browser viewport.
5615
+ * Create a function used to create a Chart based on the definition
5547
5616
  */
5548
- function useSpreadsheetRect() {
5549
- const position = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
5550
- let spreadsheetElement = document.querySelector(".o-spreadsheet");
5551
- updatePosition();
5552
- function updatePosition() {
5553
- if (!spreadsheetElement) {
5554
- spreadsheetElement = document.querySelector(".o-spreadsheet");
5555
- }
5556
- if (spreadsheetElement) {
5557
- const { top, left, width, height } = spreadsheetElement.getBoundingClientRect();
5558
- position.x = left;
5559
- position.y = top;
5560
- position.width = width;
5561
- position.height = height;
5617
+ function chartFactory(getters) {
5618
+ const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
5619
+ function createChart(id, definition, sheetId) {
5620
+ const builder = builders.find((builder) => builder.match(definition.type));
5621
+ if (!builder) {
5622
+ throw new Error(`No builder for this chart: ${definition.type}`);
5562
5623
  }
5624
+ return builder.createChart(definition, sheetId, getters);
5563
5625
  }
5564
- owl.onMounted(updatePosition);
5565
- owl.onPatched(updatePosition);
5566
- return position;
5626
+ return createChart;
5567
5627
  }
5568
5628
  /**
5569
- * Return the component (or ref's component) BoundingRect, relative
5570
- * to the upper left corner of the screen (<body> element).
5571
- *
5572
- * Note: when used with a <Portal/> component, it will
5573
- * return the portal position, not the teleported position.
5629
+ * Create a function used to create a Chart Runtime based on the chart class
5630
+ * instance
5574
5631
  */
5575
- function useAbsoluteBoundingRect(ref) {
5576
- const rect = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
5577
- function updateElRect() {
5578
- const el = ref.el;
5579
- if (el === null) {
5580
- return;
5632
+ function chartRuntimeFactory(getters) {
5633
+ const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
5634
+ function createRuntimeChart(chart) {
5635
+ const builder = builders.find((builder) => builder.match(chart.type));
5636
+ if (!builder) {
5637
+ throw new Error("No runtime builder for this chart.");
5581
5638
  }
5582
- const { top, left, width, height } = el.getBoundingClientRect();
5583
- rect.x = left;
5584
- rect.y = top;
5585
- rect.width = width;
5586
- rect.height = height;
5639
+ return builder.getChartRuntime(chart, getters);
5587
5640
  }
5588
- owl.onMounted(updateElRect);
5589
- owl.onPatched(updateElRect);
5590
- return rect;
5641
+ return createRuntimeChart;
5591
5642
  }
5592
5643
  /**
5593
- * Get the rectangle inside which a popover should stay when being displayed.
5594
- * It's the value defined in `env.getPopoverContainerRect`, or the Rect of the "o-spreadsheet"
5595
- * element by default.
5596
- *
5597
- * Coordinates are expressed expressed as absolute DOM position.
5644
+ * Validate the chart definition given in arguments
5598
5645
  */
5599
- function usePopoverContainer() {
5600
- const container = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
5601
- const component = owl.useComponent();
5602
- const spreadsheetRect = useSpreadsheetRect();
5603
- function updateRect() {
5604
- const env = component.env;
5605
- const newRect = "getPopoverContainerRect" in env ? env.getPopoverContainerRect() : spreadsheetRect;
5606
- container.x = newRect.x;
5607
- container.y = newRect.y;
5608
- container.width = newRect.width;
5609
- container.height = newRect.height;
5646
+ function validateChartDefinition(validator, definition) {
5647
+ const validators = chartRegistry.getAll().find((validator) => validator.match(definition.type));
5648
+ if (!validators) {
5649
+ throw new Error("Unknown chart type.");
5610
5650
  }
5611
- updateRect();
5612
- owl.onMounted(updateRect);
5613
- owl.onPatched(updateRect);
5614
- return container;
5615
- }
5616
-
5651
+ return validators.validateChartDefinition(validator, definition);
5652
+ }
5617
5653
  /**
5618
- * Return true if the event was triggered from
5619
- * a child element.
5654
+ * Get a new chart definition transformed with the executed command. This
5655
+ * functions will be called during operational transform process
5620
5656
  */
5621
- function isChildEvent(parent, ev) {
5622
- return !!ev.target && parent.contains(ev.target);
5623
- }
5624
- function gridOverlayPosition() {
5625
- const spreadsheetElement = document.querySelector(".o-grid-overlay");
5626
- if (spreadsheetElement) {
5627
- const { top, left } = spreadsheetElement === null || spreadsheetElement === void 0 ? void 0 : spreadsheetElement.getBoundingClientRect();
5628
- return { top, left };
5657
+ function transformDefinition(definition, executed) {
5658
+ const transformation = chartRegistry.getAll().find((factory) => factory.match(definition.type));
5659
+ if (!transformation) {
5660
+ throw new Error("Unknown chart type.");
5629
5661
  }
5630
- throw new Error("Can't find spreadsheet position");
5631
- }
5632
- function getBoundingRectAsPOJO(el) {
5633
- const rect = el.getBoundingClientRect();
5634
- return {
5635
- x: rect.x,
5636
- y: rect.y,
5637
- width: rect.width,
5638
- height: rect.height,
5639
- };
5662
+ return transformation.transformDefinition(definition, executed);
5640
5663
  }
5641
5664
  /**
5642
- * Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
5665
+ * Get an empty definition based on the given context and the given type
5643
5666
  */
5644
- function* iterateChildren(el) {
5645
- yield el;
5646
- if (el.hasChildNodes()) {
5647
- for (let child of el.childNodes) {
5648
- yield* iterateChildren(child);
5649
- }
5650
- }
5651
- }
5652
- function getElementScrollTop(el) {
5653
- return (el === null || el === void 0 ? void 0 : el.scrollTop) || 0;
5667
+ function getChartDefinitionFromContextCreation(context, type) {
5668
+ const chartClass = chartRegistry.get(type);
5669
+ return chartClass.getChartDefinitionFromContextCreation(context);
5654
5670
  }
5655
- function setElementScrollTop(el, scroll) {
5656
- if (!el)
5657
- return;
5658
- el.scrollTop = scroll;
5671
+ function getChartTypes() {
5672
+ const result = {};
5673
+ for (const key of chartRegistry.getKeys()) {
5674
+ result[key] = chartRegistry.get(key).name;
5675
+ }
5676
+ return result;
5659
5677
  }
5660
- function getOpenedMenus() {
5661
- return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
5662
- }
5663
-
5664
5678
  /**
5665
- * Compute the intersection of two rectangles. Returns nothing if the two rectangles don't overlap
5679
+ * Return a "smart" chart definition in the given zone. The definition is "smart" because it will
5680
+ * use the best type of chart to display the data of the zone.
5681
+ *
5682
+ * It will also try to find labels and datasets in the range, and try to find title for the datasets.
5683
+ *
5684
+ * The type of chart will be :
5685
+ * - If the zone is a single non-empty cell, returns a scorecard
5686
+ * - If the all the labels are numbers/date, returns a line chart
5687
+ * - Else returns a bar chart
5666
5688
  */
5667
- function rectIntersection(rect1, rect2) {
5668
- return zoneToRect(intersection(rectToZone(rect1), rectToZone(rect2)));
5669
- }
5670
- function rectToZone(rect) {
5671
- return {
5672
- left: rect.x,
5673
- top: rect.y,
5674
- right: rect.x + rect.width,
5675
- bottom: rect.y + rect.height,
5676
- };
5677
- }
5678
- function zoneToRect(zone) {
5679
- if (!zone)
5680
- return undefined;
5689
+ function getSmartChartDefinition(zone, getters) {
5690
+ var _a;
5691
+ let dataSetZone = zone;
5692
+ if (zone.left !== zone.right) {
5693
+ dataSetZone = { ...zone, left: zone.left + 1 };
5694
+ }
5695
+ const dataSets = [zoneToXc(dataSetZone)];
5696
+ const sheetId = getters.getActiveSheetId();
5697
+ const topLeftCell = getters.getCell({ sheetId, col: zone.left, row: zone.top });
5698
+ if (getZoneArea(zone) === 1 && (topLeftCell === null || topLeftCell === void 0 ? void 0 : topLeftCell.content)) {
5699
+ return {
5700
+ type: "scorecard",
5701
+ title: "",
5702
+ background: ((_a = topLeftCell.style) === null || _a === void 0 ? void 0 : _a.fillColor) || undefined,
5703
+ keyValue: zoneToXc(zone),
5704
+ baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
5705
+ baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
5706
+ baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
5707
+ };
5708
+ }
5709
+ let title = "";
5710
+ const cellsInFirstRow = getters.getEvaluatedCellsInZone(sheetId, {
5711
+ ...dataSetZone,
5712
+ bottom: dataSetZone.top,
5713
+ });
5714
+ const dataSetsHaveTitle = !!cellsInFirstRow.find((cell) => cell.type !== CellValueType.empty && cell.type !== CellValueType.number);
5715
+ if (dataSetsHaveTitle) {
5716
+ const texts = cellsInFirstRow
5717
+ .filter((cell) => cell.type !== CellValueType.error && cell.type !== CellValueType.empty)
5718
+ .map((cell) => cell.formattedValue);
5719
+ const lastElement = texts.splice(-1)[0];
5720
+ title = texts.join(", ");
5721
+ if (lastElement) {
5722
+ title += (title ? " " + _t("and") + " " : "") + lastElement;
5723
+ }
5724
+ }
5725
+ let labelRangeXc;
5726
+ if (zone.left !== zone.right) {
5727
+ labelRangeXc = zoneToXc({
5728
+ ...zone,
5729
+ right: zone.left,
5730
+ top: dataSetsHaveTitle ? zone.top + 1 : zone.top,
5731
+ });
5732
+ }
5733
+ // Only display legend for several datasets.
5734
+ const newLegendPos = dataSetZone.right === dataSetZone.left ? "none" : "top";
5735
+ const labelRange = labelRangeXc ? getters.getRangeFromSheetXC(sheetId, labelRangeXc) : undefined;
5736
+ if (canChartParseLabels(labelRange, getters)) {
5737
+ return {
5738
+ title,
5739
+ dataSets,
5740
+ labelsAsText: false,
5741
+ stacked: false,
5742
+ aggregated: false,
5743
+ labelRange: labelRangeXc,
5744
+ type: "line",
5745
+ dataSetsHaveTitle,
5746
+ verticalAxisPosition: "left",
5747
+ legendPosition: newLegendPos,
5748
+ };
5749
+ }
5681
5750
  return {
5682
- x: zone.left,
5683
- y: zone.top,
5684
- width: zone.right - zone.left,
5685
- height: zone.bottom - zone.top,
5751
+ title,
5752
+ dataSets,
5753
+ labelRange: labelRangeXc,
5754
+ type: "bar",
5755
+ stacked: false,
5756
+ aggregated: false,
5757
+ dataSetsHaveTitle,
5758
+ verticalAxisPosition: "left",
5759
+ legendPosition: newLegendPos,
5686
5760
  };
5687
5761
  }
5688
5762
 
5689
- css /* scss */ `
5690
- .o-popover {
5691
- position: absolute;
5692
- z-index: ${ComponentsImportance.Popover};
5693
- overflow: auto;
5694
- box-shadow: 1px 2px 5px 2px rgb(51 51 51 / 15%);
5695
- width: fit-content;
5696
- height: fit-content;
5763
+ function centerFigurePosition(getters, size) {
5764
+ const { x: offsetCorrectionX, y: offsetCorrectionY } = getters.getMainViewportCoordinates();
5765
+ const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
5766
+ const dim = getters.getSheetViewDimension();
5767
+ const rect = getters.getVisibleRect(getters.getActiveMainViewport());
5768
+ const scrollableViewportWidth = Math.min(rect.width, dim.width - offsetCorrectionX);
5769
+ const scrollableViewportHeight = Math.min(rect.height, dim.height - offsetCorrectionY);
5770
+ const position = {
5771
+ x: offsetCorrectionX + scrollX + Math.max(0, (scrollableViewportWidth - size.width) / 2),
5772
+ y: offsetCorrectionY + scrollY + Math.max(0, (scrollableViewportHeight - size.height) / 2),
5773
+ }; // Position at the center of the scrollable viewport
5774
+ return position;
5775
+ }
5776
+ function getMaxFigureSize(getters, figureSize) {
5777
+ const size = deepCopy(figureSize);
5778
+ const dim = getters.getSheetViewDimension();
5779
+ const maxWidth = dim.width;
5780
+ const maxHeight = dim.height;
5781
+ if (size.width > maxWidth) {
5782
+ const ratio = maxWidth / size.width;
5783
+ size.width = maxWidth;
5784
+ size.height = size.height * ratio;
5785
+ }
5786
+ if (size.height > maxHeight) {
5787
+ const ratio = maxHeight / size.height;
5788
+ size.height = maxHeight;
5789
+ size.width = size.width * ratio;
5790
+ }
5791
+ return size;
5697
5792
  }
5698
- `;
5699
- class Popover extends owl.Component {
5700
- constructor() {
5701
- super(...arguments);
5702
- this.popoverRef = owl.useRef("popover");
5703
- this.currentPosition = undefined;
5704
- this.currentDisplayValue = undefined;
5705
- this.spreadsheetRect = useSpreadsheetRect();
5793
+
5794
+ const SORT_TYPES = [
5795
+ CellValueType.number,
5796
+ CellValueType.error,
5797
+ CellValueType.text,
5798
+ CellValueType.boolean,
5799
+ ];
5800
+ function sortCells(cells, sortDirection, emptyCellAsZero) {
5801
+ const cellsWithIndex = cells.map((cell, index) => ({
5802
+ index,
5803
+ type: cell.type,
5804
+ value: cell.value,
5805
+ }));
5806
+ let emptyCells = cellsWithIndex.filter((x) => x.type === CellValueType.empty);
5807
+ let nonEmptyCells = cellsWithIndex.filter((x) => x.type !== CellValueType.empty);
5808
+ if (emptyCellAsZero) {
5809
+ nonEmptyCells.push(...emptyCells.map((emptyCell) => ({ ...emptyCell, type: CellValueType.number, value: 0 })));
5810
+ emptyCells = [];
5706
5811
  }
5707
- setup() {
5708
- this.containerRect = usePopoverContainer();
5709
- // useEffect occurs after the DOM is created and the element width/height are computed, but before
5710
- // the element in rendered, so we can still set its position
5711
- owl.useEffect(() => {
5712
- var _a, _b, _c, _d;
5713
- if (!this.containerRect)
5714
- throw new Error("Popover container is not defined");
5715
- const el = this.popoverRef.el;
5716
- const anchor = rectIntersection(this.props.anchorRect, this.containerRect);
5717
- const newDisplay = anchor ? "block" : "none";
5718
- if (this.currentDisplayValue !== "none" && newDisplay === "none") {
5719
- (_b = (_a = this.props).onPopoverHidden) === null || _b === void 0 ? void 0 : _b.call(_a);
5812
+ const inverse = sortDirection === "descending" ? -1 : 1;
5813
+ return nonEmptyCells
5814
+ .sort((left, right) => {
5815
+ let typeOrder = SORT_TYPES.indexOf(left.type) - SORT_TYPES.indexOf(right.type);
5816
+ if (typeOrder === 0) {
5817
+ if (left.type === CellValueType.text || left.type === CellValueType.error) {
5818
+ typeOrder = left.value.localeCompare(right.value);
5720
5819
  }
5721
- el.style.display = newDisplay;
5722
- this.currentDisplayValue = newDisplay;
5723
- if (!anchor)
5724
- return;
5725
- const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };
5726
- const elDims = {
5727
- width: el.getBoundingClientRect().width,
5728
- height: el.getBoundingClientRect().height,
5729
- };
5730
- const spreadsheetRect = this.spreadsheetRect;
5731
- const popoverPositionHelper = this.props.positioning === "BottomLeft"
5732
- ? new BottomLeftPopoverContext(anchor, this.containerRect, propsMaxSize, spreadsheetRect)
5733
- : new TopRightPopoverContext(anchor, this.containerRect, propsMaxSize, spreadsheetRect);
5734
- const style = popoverPositionHelper.getCss(elDims, this.props.verticalOffset);
5735
- for (const property of Object.keys(style)) {
5736
- el.style[property] = style[property];
5820
+ else
5821
+ typeOrder = left.value - right.value;
5822
+ }
5823
+ return inverse * typeOrder;
5824
+ })
5825
+ .concat(emptyCells);
5826
+ }
5827
+ function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
5828
+ let result = DispatchResult.Success;
5829
+ //several columns => bypass the contiguity check
5830
+ let multiColumns = zone.right > zone.left;
5831
+ if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
5832
+ multiColumns = false;
5833
+ let table;
5834
+ for (let row = zone.top; row <= zone.bottom; row++) {
5835
+ table = [];
5836
+ for (let col = zone.left; col <= zone.right; col++) {
5837
+ let merge = env.model.getters.getMerge({ sheetId, col, row });
5838
+ if (merge && !table.includes(merge.id.toString())) {
5839
+ table.push(merge.id.toString());
5840
+ }
5737
5841
  }
5738
- const newPosition = popoverPositionHelper.getCurrentPosition(elDims);
5739
- if (this.currentPosition && newPosition !== this.currentPosition) {
5740
- (_d = (_c = this.props).onPopoverMoved) === null || _d === void 0 ? void 0 : _d.call(_c);
5842
+ if (table.length >= 2) {
5843
+ multiColumns = true;
5844
+ break;
5741
5845
  }
5742
- this.currentPosition = newPosition;
5743
- });
5846
+ }
5744
5847
  }
5745
- }
5746
- Popover.template = "o-spreadsheet-Popover";
5747
- Popover.defaultProps = {
5748
- positioning: "BottomLeft",
5749
- verticalOffset: 0,
5750
- onMouseWheel: () => { },
5751
- onPopoverMoved: () => { },
5752
- onPopoverHidden: () => { },
5753
- };
5754
- Popover.props = {
5755
- anchorRect: Object,
5756
- containerRect: { type: Object, optional: true },
5757
- positioning: { type: String, optional: true },
5758
- maxWidth: { type: Number, optional: true },
5759
- maxHeight: { type: Number, optional: true },
5760
- verticalOffset: { type: Number, optional: true },
5761
- onMouseWheel: { type: Function, optional: true },
5762
- onPopoverHidden: { type: Function, optional: true },
5763
- onPopoverMoved: { type: Function, optional: true },
5764
- slots: Object,
5765
- };
5766
- class PopoverPositionContext {
5767
- constructor(anchorRect, containerRect, propsMaxSize, spreadsheetOffset) {
5768
- this.anchorRect = anchorRect;
5769
- this.containerRect = containerRect;
5770
- this.propsMaxSize = propsMaxSize;
5771
- this.spreadsheetOffset = spreadsheetOffset;
5848
+ const { col, row } = anchor;
5849
+ if (multiColumns) {
5850
+ result = env.model.dispatch("SORT_CELLS", { sheetId, col, row, zone, sortDirection });
5772
5851
  }
5773
- /** Check if there is enough space for the popover to be rendered at the bottom of the anchorRect */
5774
- shouldRenderAtBottom(elementHeight) {
5775
- return (elementHeight <= this.availableHeightDown ||
5776
- this.availableHeightDown >= this.availableHeightUp);
5852
+ else {
5853
+ // check contiguity
5854
+ const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
5855
+ if (isEqual(contiguousZone, zone)) {
5856
+ // merge as it is
5857
+ result = env.model.dispatch("SORT_CELLS", {
5858
+ sheetId,
5859
+ col,
5860
+ row,
5861
+ zone,
5862
+ sortDirection,
5863
+ });
5864
+ }
5865
+ else {
5866
+ env.askConfirmation(_lt("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => {
5867
+ zone = contiguousZone;
5868
+ result = env.model.dispatch("SORT_CELLS", {
5869
+ sheetId,
5870
+ col,
5871
+ row,
5872
+ zone,
5873
+ sortDirection,
5874
+ });
5875
+ }, () => {
5876
+ result = env.model.dispatch("SORT_CELLS", {
5877
+ sheetId,
5878
+ col,
5879
+ row,
5880
+ zone,
5881
+ sortDirection,
5882
+ });
5883
+ });
5884
+ }
5777
5885
  }
5778
- /** Check if there is enough space for the popover to be rendered at the right of the anchorRect */
5779
- shouldRenderAtRight(elementWidth) {
5780
- return (elementWidth <= this.availableWidthRight ||
5781
- this.availableWidthRight >= this.availableWidthLeft);
5782
- }
5783
- getMaxHeight(elementHeight) {
5784
- const shouldRenderAtBottom = this.shouldRenderAtBottom(elementHeight);
5785
- const availableHeight = shouldRenderAtBottom
5786
- ? this.availableHeightDown
5787
- : this.availableHeightUp;
5788
- return this.propsMaxSize.height
5789
- ? Math.min(availableHeight, this.propsMaxSize.height)
5790
- : availableHeight;
5791
- }
5792
- getMaxWidth(elementWidth) {
5793
- const shouldRenderAtRight = this.shouldRenderAtRight(elementWidth);
5794
- const availableWidth = shouldRenderAtRight ? this.availableWidthRight : this.availableWidthLeft;
5795
- return this.propsMaxSize.width
5796
- ? Math.min(availableWidth, this.propsMaxSize.width)
5797
- : availableWidth;
5798
- }
5799
- getCss(elDims, verticalOffset) {
5800
- const maxHeight = this.getMaxHeight(elDims.height);
5801
- const maxWidth = this.getMaxWidth(elDims.width);
5802
- const actualHeight = Math.min(maxHeight, elDims.height);
5803
- const actualWidth = Math.min(maxWidth, elDims.width);
5804
- const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
5805
- const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);
5806
- verticalOffset = shouldRenderAtBottom ? verticalOffset : -verticalOffset;
5807
- const cssProperties = {
5808
- "max-height": maxHeight + "px",
5809
- "max-width": maxWidth + "px",
5810
- top: this.getTopCoordinate(actualHeight, shouldRenderAtBottom) -
5811
- this.spreadsheetOffset.y -
5812
- verticalOffset +
5813
- "px",
5814
- left: this.getLeftCoordinate(actualWidth, shouldRenderAtRight) - this.spreadsheetOffset.x + "px",
5815
- };
5816
- return cssProperties;
5817
- }
5818
- getCurrentPosition(elDims) {
5819
- const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
5820
- const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);
5821
- if (shouldRenderAtBottom && shouldRenderAtRight)
5822
- return "BottomRight";
5823
- if (shouldRenderAtBottom && !shouldRenderAtRight)
5824
- return "BottomLeft";
5825
- if (!shouldRenderAtBottom && shouldRenderAtRight)
5826
- return "TopRight";
5827
- return "TopLeft";
5828
- }
5829
- }
5830
- class BottomLeftPopoverContext extends PopoverPositionContext {
5831
- get availableHeightUp() {
5832
- return this.anchorRect.y - this.containerRect.y;
5833
- }
5834
- get availableHeightDown() {
5835
- return this.containerRect.height - this.availableHeightUp - this.anchorRect.height;
5836
- }
5837
- get availableWidthRight() {
5838
- return this.containerRect.x + this.containerRect.width - this.anchorRect.x;
5839
- }
5840
- get availableWidthLeft() {
5841
- return this.anchorRect.x + this.anchorRect.width - this.containerRect.x;
5842
- }
5843
- getTopCoordinate(elementHeight, shouldRenderAtBottom) {
5844
- if (shouldRenderAtBottom) {
5845
- return this.anchorRect.y + this.anchorRect.height;
5846
- }
5847
- else {
5848
- return this.anchorRect.y - elementHeight;
5849
- }
5850
- }
5851
- getLeftCoordinate(elementWidth, shouldRenderAtRight) {
5852
- if (shouldRenderAtRight) {
5853
- return this.anchorRect.x;
5854
- }
5855
- else {
5856
- return this.anchorRect.x + this.anchorRect.width - elementWidth;
5857
- }
5858
- }
5859
- }
5860
- class TopRightPopoverContext extends PopoverPositionContext {
5861
- get availableHeightUp() {
5862
- return this.anchorRect.y + this.anchorRect.height - this.containerRect.y;
5863
- }
5864
- get availableHeightDown() {
5865
- return this.containerRect.y + this.containerRect.height - this.anchorRect.y;
5866
- }
5867
- get availableWidthRight() {
5868
- return this.containerRect.width - this.anchorRect.width - this.availableWidthLeft;
5869
- }
5870
- get availableWidthLeft() {
5871
- return this.anchorRect.x - this.containerRect.x;
5872
- }
5873
- getTopCoordinate(elementHeight, shouldRenderAtBottom) {
5874
- if (shouldRenderAtBottom) {
5875
- return this.anchorRect.y;
5876
- }
5877
- else {
5878
- return this.anchorRect.y + this.anchorRect.height - elementHeight;
5879
- }
5880
- }
5881
- getLeftCoordinate(elementWidth, shouldRenderAtRight) {
5882
- if (shouldRenderAtRight) {
5883
- return this.anchorRect.x + this.anchorRect.width;
5884
- }
5885
- else {
5886
- return this.anchorRect.x - elementWidth;
5887
- }
5888
- }
5889
- }
5890
-
5891
- //------------------------------------------------------------------------------
5892
- // Context Menu Component
5893
- //------------------------------------------------------------------------------
5894
- css /* scss */ `
5895
- .o-menu {
5896
- background-color: white;
5897
- padding: ${MENU_VERTICAL_PADDING}px 0px;
5898
- width: ${MENU_WIDTH}px;
5899
- box-sizing: border-box !important;
5900
-
5901
- .o-menu-item {
5902
- display: flex;
5903
- justify-content: space-between;
5904
- align-items: center;
5905
- box-sizing: border-box;
5906
- height: ${MENU_ITEM_HEIGHT}px;
5907
- padding: 4px 16px;
5908
- cursor: pointer;
5909
- user-select: none;
5910
-
5911
- .o-menu-item-name {
5912
- overflow: hidden;
5913
- white-space: nowrap;
5914
- text-overflow: ellipsis;
5915
- }
5916
-
5917
- &.o-menu-root {
5918
- display: flex;
5919
- justify-content: space-between;
5920
- }
5921
- .o-menu-item-icon {
5922
- margin-top: auto;
5923
- margin-bottom: auto;
5924
- }
5925
- .o-icon {
5926
- width: 10px;
5927
- }
5928
-
5929
- &:not(.disabled) {
5930
- &:hover,
5931
- &.o-menu-item-active {
5932
- background-color: #ebebeb;
5933
- }
5934
- .o-menu-item-description {
5935
- color: grey;
5936
- }
5937
- }
5938
- &.disabled {
5939
- color: ${MENU_ITEM_DISABLED_COLOR};
5940
- cursor: not-allowed;
5941
- }
5942
- }
5943
- }
5944
- `;
5945
- class Menu extends owl.Component {
5946
- constructor() {
5947
- super(...arguments);
5948
- this.subMenu = owl.useState({
5949
- isOpen: false,
5950
- position: null,
5951
- scrollOffset: 0,
5952
- menuItems: [],
5953
- });
5954
- this.menuRef = owl.useRef("menu");
5955
- this.position = useAbsoluteBoundingRect(this.menuRef);
5956
- }
5957
- setup() {
5958
- owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
5959
- owl.useExternalListener(window, "contextmenu", this.onExternalClick, { capture: true });
5960
- owl.onWillUpdateProps((nextProps) => {
5961
- if (nextProps.menuItems !== this.props.menuItems) {
5962
- this.closeSubMenu();
5963
- }
5964
- });
5965
- }
5966
- get visibleMenuItems() {
5967
- return this.props.menuItems.filter((x) => x.isVisible(this.env));
5968
- }
5969
- get subMenuPosition() {
5970
- const position = Object.assign({}, this.subMenu.position);
5971
- position.y -= this.subMenu.scrollOffset || 0;
5972
- return position;
5973
- }
5974
- get menuHeight() {
5975
- const menuItems = this.visibleMenuItems;
5976
- let menuItemsHeight = this.getMenuItemsHeight(menuItems);
5977
- // We don't display separator at the end of a menu
5978
- if (menuItems[menuItems.length - 1].separator) {
5979
- menuItemsHeight -= MENU_SEPARATOR_HEIGHT;
5980
- }
5981
- const menuHeight = 2 * MENU_VERTICAL_PADDING + menuItemsHeight;
5982
- return this.props.maxHeight ? Math.min(menuHeight, this.props.maxHeight) : menuHeight;
5983
- }
5984
- get popoverProps() {
5985
- const isRoot = this.props.depth === 1;
5986
- return {
5987
- anchorRect: {
5988
- x: this.props.position.x - MENU_WIDTH * (this.props.depth - 1),
5989
- y: this.props.position.y,
5990
- width: isRoot ? 0 : MENU_WIDTH,
5991
- height: isRoot ? 0 : MENU_ITEM_HEIGHT,
5992
- },
5993
- positioning: "TopRight",
5994
- verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
5995
- onPopoverHidden: () => this.closeSubMenu(),
5996
- onPopoverMoved: () => this.closeSubMenu(),
5997
- maxHeight: this.menuHeight,
5998
- };
5999
- }
6000
- getColor(menu) {
6001
- return menu.textColor ? `color: ${menu.textColor}` : undefined;
6002
- }
6003
- async activateMenu(menu) {
6004
- var _a, _b, _c;
6005
- const result = await ((_a = menu.action) === null || _a === void 0 ? void 0 : _a.call(menu, this.env));
6006
- this.close();
6007
- (_c = (_b = this.props).onMenuClicked) === null || _c === void 0 ? void 0 : _c.call(_b, { detail: result });
6008
- }
6009
- close() {
6010
- this.closeSubMenu();
6011
- this.props.onClose();
6012
- }
6013
- /**
6014
- * Return the number of pixels between the top of the menu
6015
- * and the menu item at a given index.
6016
- */
6017
- subMenuVerticalPosition(menuIndex) {
6018
- const menusAbove = this.visibleMenuItems.slice(0, menuIndex);
6019
- return this.position.y + this.getMenuItemsHeight(menusAbove) + MENU_VERTICAL_PADDING;
6020
- }
6021
- onExternalClick(ev) {
6022
- // Don't close a root menu when clicked to open the submenus.
6023
- const el = this.menuRef.el;
6024
- if (el && getOpenedMenus().some((el) => isChildEvent(el, ev))) {
6025
- return;
6026
- }
6027
- ev.closedMenuId = this.props.menuId;
6028
- this.close();
6029
- }
6030
- getMenuItemsHeight(menuItems) {
6031
- const numberOfSeparators = menuItems.filter((m) => m.separator).length;
6032
- return MENU_ITEM_HEIGHT * menuItems.length + MENU_SEPARATOR_HEIGHT * numberOfSeparators;
6033
- }
6034
- getName(menu) {
6035
- return menu.name(this.env);
6036
- }
6037
- isRoot(menu) {
6038
- return !menu.action;
6039
- }
6040
- isEnabled(menu) {
6041
- if (menu.isEnabled(this.env)) {
6042
- return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
6043
- }
6044
- return false;
6045
- }
6046
- onScroll(ev) {
6047
- this.subMenu.scrollOffset = ev.target.scrollTop;
6048
- }
6049
- /**
6050
- * If the given menu is not disabled, open it's submenu at the
6051
- * correct position according to available surrounding space.
6052
- */
6053
- openSubMenu(menu, menuIndex) {
6054
- const y = this.subMenuVerticalPosition(menuIndex);
6055
- this.subMenu.position = {
6056
- x: this.position.x + this.props.depth * MENU_WIDTH,
6057
- y: y - (this.subMenu.scrollOffset || 0),
6058
- };
6059
- this.subMenu.menuItems = menu.children(this.env);
6060
- this.subMenu.isOpen = true;
6061
- this.subMenu.parentMenu = menu;
6062
- }
6063
- isParentMenu(subMenu, menuItem) {
6064
- var _a;
6065
- return ((_a = subMenu.parentMenu) === null || _a === void 0 ? void 0 : _a.id) === menuItem.id;
6066
- }
6067
- closeSubMenu() {
6068
- this.subMenu.isOpen = false;
6069
- this.subMenu.parentMenu = undefined;
6070
- }
6071
- onClickMenu(menu, menuIndex) {
6072
- if (this.isEnabled(menu)) {
6073
- if (this.isRoot(menu)) {
6074
- this.openSubMenu(menu, menuIndex);
6075
- }
6076
- else {
6077
- this.activateMenu(menu);
6078
- }
6079
- }
6080
- }
6081
- onMouseOver(menu, position) {
6082
- if (menu.isEnabled(this.env)) {
6083
- if (this.isRoot(menu)) {
6084
- this.openSubMenu(menu, position);
6085
- }
6086
- else {
6087
- this.closeSubMenu();
6088
- }
6089
- }
6090
- }
6091
- }
6092
- Menu.template = "o-spreadsheet-Menu";
6093
- Menu.components = { Menu, Popover };
6094
- Menu.defaultProps = {
6095
- depth: 1,
6096
- };
6097
- Menu.props = {
6098
- position: Object,
6099
- menuItems: Array,
6100
- depth: { type: Number, optional: true },
6101
- maxHeight: { type: Number, optional: true },
6102
- onClose: Function,
6103
- onMenuClicked: { type: Function, optional: true },
6104
- menuId: { type: String, optional: true },
6105
- };
6106
-
6107
- // -----------------------------------------------------------------------------
6108
- // STYLE
6109
- // -----------------------------------------------------------------------------
6110
- css /* scss */ `
6111
- .o-chart-container {
6112
- width: 100%;
6113
- height: 100%;
6114
- position: relative;
6115
- }
6116
- `;
6117
- class ChartFigure extends owl.Component {
6118
- constructor() {
6119
- super(...arguments);
6120
- this.menuState = owl.useState({ isOpen: false, position: null, menuItems: [] });
6121
- this.chartContainerRef = owl.useRef("chartContainer");
6122
- this.menuButtonRef = owl.useRef("menuButton");
6123
- this.menuButtonRect = useAbsoluteBoundingRect(this.menuButtonRef);
6124
- this.position = useAbsoluteBoundingRect(this.chartContainerRef);
6125
- }
6126
- getMenuItemRegistry() {
6127
- const registry = new MenuItemRegistry();
6128
- registry.add("edit", {
6129
- name: _lt("Edit"),
6130
- sequence: 1,
6131
- action: () => {
6132
- this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id });
6133
- this.env.openSidePanel("ChartPanel");
6134
- },
6135
- });
6136
- registry.add("copy", {
6137
- name: _lt("Copy"),
6138
- sequence: 2,
6139
- action: async () => {
6140
- this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id });
6141
- this.env.model.dispatch("COPY");
6142
- await this.env.clipboard.write(this.env.model.getters.getClipboardContent());
6143
- },
6144
- });
6145
- registry.add("cut", {
6146
- name: _lt("Cut"),
6147
- sequence: 3,
6148
- action: async () => {
6149
- this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id });
6150
- this.env.model.dispatch("CUT");
6151
- await this.env.clipboard.write(this.env.model.getters.getClipboardContent());
6152
- },
6153
- });
6154
- registry.add("delete", {
6155
- name: _lt("Delete"),
6156
- sequence: 10,
6157
- action: () => {
6158
- this.env.model.dispatch("DELETE_FIGURE", {
6159
- sheetId: this.env.model.getters.getActiveSheetId(),
6160
- id: this.props.figure.id,
6161
- });
6162
- this.props.onFigureDeleted();
6163
- },
6164
- });
6165
- return registry;
6166
- }
6167
- get chartType() {
6168
- return this.env.model.getters.getChartType(this.env.model.getters.getActiveSheetId(), this.props.figure.id);
6169
- }
6170
- onContextMenu(ev) {
6171
- const position = {
6172
- x: this.position.x + ev.offsetX,
6173
- y: this.position.y + ev.offsetY,
6174
- };
6175
- this.openContextMenu(position);
6176
- }
6177
- showMenu() {
6178
- const { x, y, width } = this.menuButtonRect;
6179
- const menuPosition = {
6180
- x: x >= MENU_WIDTH ? x - MENU_WIDTH : x + width,
6181
- y: y,
6182
- };
6183
- this.openContextMenu(menuPosition);
6184
- }
6185
- openContextMenu(position) {
6186
- const registry = this.getMenuItemRegistry();
6187
- this.menuState.isOpen = true;
6188
- this.menuState.menuItems = registry.getMenuItems();
6189
- this.menuState.position = position;
6190
- }
6191
- get chartComponent() {
6192
- const type = this.chartType;
6193
- const component = chartComponentRegistry.get(type);
6194
- if (!component) {
6195
- throw new Error(`Component is not defined for type ${type}`);
6196
- }
6197
- return component;
6198
- }
6199
- }
6200
- ChartFigure.template = "o-spreadsheet-ChartFigure";
6201
- ChartFigure.components = { Menu };
6202
- ChartFigure.props = {
6203
- figure: Object,
6204
- onFigureDeleted: Function,
6205
- };
6206
-
6207
- function interactiveCut(env) {
6208
- const result = env.model.dispatch("CUT");
6209
- if (!result.isSuccessful) {
6210
- if (result.isCancelledBecause(19 /* CommandResult.WrongCutSelection */)) {
6211
- env.raiseError(_lt("This operation is not allowed with multiple selections."));
6212
- }
6213
- }
6214
- }
6215
-
6216
- const PasteInteractiveContent = {
6217
- wrongPasteSelection: _lt("This operation is not allowed with multiple selections."),
6218
- willRemoveExistingMerge: _lt("This operation is not possible due to a merge. Please remove the merges first than try again."),
6219
- wrongFigurePasteOption: _lt("Cannot do a special paste of a figure."),
6220
- frozenPaneOverlap: _lt("Cannot paste merged cells over a frozen pane."),
6221
- };
6222
- function handlePasteResult(env, result) {
6223
- if (!result.isSuccessful) {
6224
- if (result.reasons.includes(20 /* CommandResult.WrongPasteSelection */)) {
6225
- env.raiseError(PasteInteractiveContent.wrongPasteSelection);
6226
- }
6227
- else if (result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
6228
- env.raiseError(PasteInteractiveContent.willRemoveExistingMerge);
6229
- }
6230
- else if (result.reasons.includes(22 /* CommandResult.WrongFigurePasteOption */)) {
6231
- env.raiseError(PasteInteractiveContent.wrongFigurePasteOption);
6232
- }
6233
- else if (result.reasons.includes(75 /* CommandResult.FrozenPaneOverlap */)) {
6234
- env.raiseError(PasteInteractiveContent.frozenPaneOverlap);
6235
- }
6236
- }
6237
- }
6238
- function interactivePaste(env, target, pasteOption) {
6239
- const result = env.model.dispatch("PASTE", { target, pasteOption });
6240
- handlePasteResult(env, result);
6241
- }
6242
- function interactivePasteFromOS(env, target, text) {
6243
- const result = env.model.dispatch("PASTE_FROM_OS_CLIPBOARD", { target, text });
6244
- handlePasteResult(env, result);
6245
- }
6246
-
6247
- /**
6248
- * Create a function used to create a Chart based on the definition
6249
- */
6250
- function chartFactory(getters) {
6251
- const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
6252
- function createChart(id, definition, sheetId) {
6253
- const builder = builders.find((builder) => builder.match(definition.type));
6254
- if (!builder) {
6255
- throw new Error(`No builder for this chart: ${definition.type}`);
6256
- }
6257
- return builder.createChart(definition, sheetId, getters);
6258
- }
6259
- return createChart;
6260
- }
6261
- /**
6262
- * Create a function used to create a Chart Runtime based on the chart class
6263
- * instance
6264
- */
6265
- function chartRuntimeFactory(getters) {
6266
- const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
6267
- function createRuntimeChart(chart) {
6268
- const builder = builders.find((builder) => builder.match(chart.type));
6269
- if (!builder) {
6270
- throw new Error("No runtime builder for this chart.");
6271
- }
6272
- return builder.getChartRuntime(chart, getters);
6273
- }
6274
- return createRuntimeChart;
6275
- }
6276
- /**
6277
- * Validate the chart definition given in arguments
6278
- */
6279
- function validateChartDefinition(validator, definition) {
6280
- const validators = chartRegistry.getAll().find((validator) => validator.match(definition.type));
6281
- if (!validators) {
6282
- throw new Error("Unknown chart type.");
6283
- }
6284
- return validators.validateChartDefinition(validator, definition);
6285
- }
6286
- /**
6287
- * Get a new chart definition transformed with the executed command. This
6288
- * functions will be called during operational transform process
6289
- */
6290
- function transformDefinition(definition, executed) {
6291
- const transformation = chartRegistry.getAll().find((factory) => factory.match(definition.type));
6292
- if (!transformation) {
6293
- throw new Error("Unknown chart type.");
6294
- }
6295
- return transformation.transformDefinition(definition, executed);
6296
- }
6297
- /**
6298
- * Get an empty definition based on the given context and the given type
6299
- */
6300
- function getChartDefinitionFromContextCreation(context, type) {
6301
- const chartClass = chartRegistry.get(type);
6302
- return chartClass.getChartDefinitionFromContextCreation(context);
6303
- }
6304
- function getChartTypes() {
6305
- const result = {};
6306
- for (const key of chartRegistry.getKeys()) {
6307
- result[key] = chartRegistry.get(key).name;
6308
- }
6309
- return result;
6310
- }
6311
- /**
6312
- * Return a "smart" chart definition in the given zone. The definition is "smart" because it will
6313
- * use the best type of chart to display the data of the zone.
6314
- *
6315
- * It will also try to find labels and datasets in the range, and try to find title for the datasets.
6316
- *
6317
- * The type of chart will be :
6318
- * - If the zone is a single non-empty cell, returns a scorecard
6319
- * - If the all the labels are numbers/date, returns a line chart
6320
- * - Else returns a bar chart
6321
- */
6322
- function getSmartChartDefinition(zone, getters) {
6323
- var _a;
6324
- let dataSetZone = zone;
6325
- if (zone.left !== zone.right) {
6326
- dataSetZone = { ...zone, left: zone.left + 1 };
6327
- }
6328
- const dataSets = [zoneToXc(dataSetZone)];
6329
- const sheetId = getters.getActiveSheetId();
6330
- const topLeftCell = getters.getCell({ sheetId, col: zone.left, row: zone.top });
6331
- if (getZoneArea(zone) === 1 && (topLeftCell === null || topLeftCell === void 0 ? void 0 : topLeftCell.content)) {
6332
- return {
6333
- type: "scorecard",
6334
- title: "",
6335
- background: ((_a = topLeftCell.style) === null || _a === void 0 ? void 0 : _a.fillColor) || undefined,
6336
- keyValue: zoneToXc(zone),
6337
- baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
6338
- baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
6339
- baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
6340
- };
6341
- }
6342
- let title = "";
6343
- const cellsInFirstRow = getters.getEvaluatedCellsInZone(sheetId, {
6344
- ...dataSetZone,
6345
- bottom: dataSetZone.top,
6346
- });
6347
- const dataSetsHaveTitle = !!cellsInFirstRow.find((cell) => cell.type !== CellValueType.empty && cell.type !== CellValueType.number);
6348
- if (dataSetsHaveTitle) {
6349
- const texts = cellsInFirstRow
6350
- .filter((cell) => cell.type !== CellValueType.error && cell.type !== CellValueType.empty)
6351
- .map((cell) => cell.formattedValue);
6352
- const lastElement = texts.splice(-1)[0];
6353
- title = texts.join(", ");
6354
- if (lastElement) {
6355
- title += (title ? " " + _t("and") + " " : "") + lastElement;
6356
- }
6357
- }
6358
- let labelRangeXc;
6359
- if (zone.left !== zone.right) {
6360
- labelRangeXc = zoneToXc({
6361
- ...zone,
6362
- right: zone.left,
6363
- top: dataSetsHaveTitle ? zone.top + 1 : zone.top,
6364
- });
6365
- }
6366
- // Only display legend for several datasets.
6367
- const newLegendPos = dataSetZone.right === dataSetZone.left ? "none" : "top";
6368
- const labelRange = labelRangeXc ? getters.getRangeFromSheetXC(sheetId, labelRangeXc) : undefined;
6369
- if (canChartParseLabels(labelRange, getters)) {
6370
- return {
6371
- title,
6372
- dataSets,
6373
- labelsAsText: false,
6374
- stacked: false,
6375
- aggregated: false,
6376
- labelRange: labelRangeXc,
6377
- type: "line",
6378
- dataSetsHaveTitle,
6379
- verticalAxisPosition: "left",
6380
- legendPosition: newLegendPos,
6381
- };
6382
- }
6383
- return {
6384
- title,
6385
- dataSets,
6386
- labelRange: labelRangeXc,
6387
- type: "bar",
6388
- stacked: false,
6389
- aggregated: false,
6390
- dataSetsHaveTitle,
6391
- verticalAxisPosition: "left",
6392
- legendPosition: newLegendPos,
6393
- };
6394
- }
6395
-
6396
- function centerFigurePosition(getters, size) {
6397
- const { x: offsetCorrectionX, y: offsetCorrectionY } = getters.getMainViewportCoordinates();
6398
- const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
6399
- const dim = getters.getSheetViewDimension();
6400
- const rect = getters.getVisibleRect(getters.getActiveMainViewport());
6401
- const scrollableViewportWidth = Math.min(rect.width, dim.width - offsetCorrectionX);
6402
- const scrollableViewportHeight = Math.min(rect.height, dim.height - offsetCorrectionY);
6403
- const position = {
6404
- x: offsetCorrectionX + scrollX + Math.max(0, (scrollableViewportWidth - size.width) / 2),
6405
- y: offsetCorrectionY + scrollY + Math.max(0, (scrollableViewportHeight - size.height) / 2),
6406
- }; // Position at the center of the scrollable viewport
6407
- return position;
6408
- }
6409
- function getMaxFigureSize(getters, figureSize) {
6410
- const size = deepCopy(figureSize);
6411
- const dim = getters.getSheetViewDimension();
6412
- const maxWidth = dim.width;
6413
- const maxHeight = dim.height;
6414
- if (size.width > maxWidth) {
6415
- const ratio = maxWidth / size.width;
6416
- size.width = maxWidth;
6417
- size.height = size.height * ratio;
6418
- }
6419
- if (size.height > maxHeight) {
6420
- const ratio = maxHeight / size.height;
6421
- size.height = maxHeight;
6422
- size.width = size.width * ratio;
6423
- }
6424
- return size;
6425
- }
6426
-
6427
- const SORT_TYPES = [
6428
- CellValueType.number,
6429
- CellValueType.error,
6430
- CellValueType.text,
6431
- CellValueType.boolean,
6432
- ];
6433
- function sortCells(cells, sortDirection, emptyCellAsZero) {
6434
- const cellsWithIndex = cells.map((cell, index) => ({
6435
- index,
6436
- type: cell.type,
6437
- value: cell.value,
6438
- }));
6439
- let emptyCells = cellsWithIndex.filter((x) => x.type === CellValueType.empty);
6440
- let nonEmptyCells = cellsWithIndex.filter((x) => x.type !== CellValueType.empty);
6441
- if (emptyCellAsZero) {
6442
- nonEmptyCells.push(...emptyCells.map((emptyCell) => ({ ...emptyCell, type: CellValueType.number, value: 0 })));
6443
- emptyCells = [];
6444
- }
6445
- const inverse = sortDirection === "descending" ? -1 : 1;
6446
- return nonEmptyCells
6447
- .sort((left, right) => {
6448
- let typeOrder = SORT_TYPES.indexOf(left.type) - SORT_TYPES.indexOf(right.type);
6449
- if (typeOrder === 0) {
6450
- if (left.type === CellValueType.text || left.type === CellValueType.error) {
6451
- typeOrder = left.value.localeCompare(right.value);
6452
- }
6453
- else
6454
- typeOrder = left.value - right.value;
6455
- }
6456
- return inverse * typeOrder;
6457
- })
6458
- .concat(emptyCells);
6459
- }
6460
- function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
6461
- let result = DispatchResult.Success;
6462
- //several columns => bypass the contiguity check
6463
- let multiColumns = zone.right > zone.left;
6464
- if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
6465
- multiColumns = false;
6466
- let table;
6467
- for (let row = zone.top; row <= zone.bottom; row++) {
6468
- table = [];
6469
- for (let col = zone.left; col <= zone.right; col++) {
6470
- let merge = env.model.getters.getMerge({ sheetId, col, row });
6471
- if (merge && !table.includes(merge.id.toString())) {
6472
- table.push(merge.id.toString());
6473
- }
6474
- }
6475
- if (table.length >= 2) {
6476
- multiColumns = true;
6477
- break;
6478
- }
6479
- }
6480
- }
6481
- const { col, row } = anchor;
6482
- if (multiColumns) {
6483
- result = env.model.dispatch("SORT_CELLS", { sheetId, col, row, zone, sortDirection });
6484
- }
6485
- else {
6486
- // check contiguity
6487
- const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
6488
- if (isEqual(contiguousZone, zone)) {
6489
- // merge as it is
6490
- result = env.model.dispatch("SORT_CELLS", {
6491
- sheetId,
6492
- col,
6493
- row,
6494
- zone,
6495
- sortDirection,
6496
- });
6497
- }
6498
- else {
6499
- env.askConfirmation(_lt("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => {
6500
- zone = contiguousZone;
6501
- result = env.model.dispatch("SORT_CELLS", {
6502
- sheetId,
6503
- col,
6504
- row,
6505
- zone,
6506
- sortDirection,
6507
- });
6508
- }, () => {
6509
- result = env.model.dispatch("SORT_CELLS", {
6510
- sheetId,
6511
- col,
6512
- row,
6513
- zone,
6514
- sortDirection,
6515
- });
6516
- });
6517
- }
6518
- }
6519
- if (result.isCancelledBecause(63 /* CommandResult.InvalidSortZone */)) {
6520
- const { col, row } = anchor;
6521
- env.model.selection.selectZone({ cell: { col, row }, zone });
6522
- env.raiseError(_lt("Cannot sort. To sort, select only cells or only merges that have the same size."));
5886
+ if (result.isCancelledBecause(63 /* CommandResult.InvalidSortZone */)) {
5887
+ const { col, row } = anchor;
5888
+ env.model.selection.selectZone({ cell: { col, row }, zone });
5889
+ env.raiseError(_lt("Cannot sort. To sort, select only cells or only merges that have the same size."));
6523
5890
  }
6524
5891
  }
6525
5892
 
@@ -7441,6 +6808,53 @@
7441
6808
  action: OPEN_CF_SIDEPANEL_ACTION,
7442
6809
  });
7443
6810
 
6811
+ /**
6812
+ * Return true if the event was triggered from
6813
+ * a child element.
6814
+ */
6815
+ function isChildEvent(parent, ev) {
6816
+ return !!ev.target && parent.contains(ev.target);
6817
+ }
6818
+ function gridOverlayPosition() {
6819
+ const spreadsheetElement = document.querySelector(".o-grid-overlay");
6820
+ if (spreadsheetElement) {
6821
+ const { top, left } = spreadsheetElement === null || spreadsheetElement === void 0 ? void 0 : spreadsheetElement.getBoundingClientRect();
6822
+ return { top, left };
6823
+ }
6824
+ throw new Error("Can't find spreadsheet position");
6825
+ }
6826
+ function getBoundingRectAsPOJO(el) {
6827
+ const rect = el.getBoundingClientRect();
6828
+ return {
6829
+ x: rect.x,
6830
+ y: rect.y,
6831
+ width: rect.width,
6832
+ height: rect.height,
6833
+ };
6834
+ }
6835
+ /**
6836
+ * Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
6837
+ */
6838
+ function* iterateChildren(el) {
6839
+ yield el;
6840
+ if (el.hasChildNodes()) {
6841
+ for (let child of el.childNodes) {
6842
+ yield* iterateChildren(child);
6843
+ }
6844
+ }
6845
+ }
6846
+ function getElementScrollTop(el) {
6847
+ return (el === null || el === void 0 ? void 0 : el.scrollTop) || 0;
6848
+ }
6849
+ function setElementScrollTop(el, scroll) {
6850
+ if (!el)
6851
+ return;
6852
+ el.scrollTop = scroll;
6853
+ }
6854
+ function getOpenedMenus() {
6855
+ return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
6856
+ }
6857
+
7444
6858
  function startDnd(onMouseMove, onMouseUp, onMouseDown = () => { }) {
7445
6859
  const _onMouseDown = (ev) => {
7446
6860
  ev.preventDefault();
@@ -15720,8 +15134,7 @@
15720
15134
  return false;
15721
15135
  const range = this.env.model.getters.getRangeFromSheetXC(refSheet, xc);
15722
15136
  let zone = range.zone;
15723
- const { height, width } = zoneToDimension(zone);
15724
- zone = height * width === 1 ? this.env.model.getters.expandZone(refSheet, zone) : zone;
15137
+ zone = getZoneArea(zone) === 1 ? this.env.model.getters.expandZone(refSheet, zone) : zone;
15725
15138
  return isEqual(zone, highlight.zone);
15726
15139
  });
15727
15140
  return highlight && highlight.color ? highlight.color : undefined;
@@ -16383,439 +15796,955 @@
16383
15796
  group = [];
16384
15797
  }
16385
15798
  }
16386
- return group;
15799
+ return group;
15800
+ }
15801
+ /**
15802
+ * Get the average steps between numbers
15803
+ */
15804
+ function getAverageIncrement(group) {
15805
+ const averages = [];
15806
+ let last = group[0];
15807
+ for (let i = 1; i < group.length; i++) {
15808
+ const current = group[i];
15809
+ averages.push(current - last);
15810
+ last = current;
15811
+ }
15812
+ return averages.reduce((a, b) => a + b, 0) / averages.length;
15813
+ }
15814
+ /**
15815
+ * Get the step for a group
15816
+ */
15817
+ function calculateIncrementBasedOnGroup(group) {
15818
+ let increment = 1;
15819
+ if (group.length >= 2) {
15820
+ increment = getAverageIncrement(group) * group.length;
15821
+ }
15822
+ return increment;
15823
+ }
15824
+ autofillRulesRegistry
15825
+ .add("simple_value_copy", {
15826
+ condition: (cell, cells) => {
15827
+ var _a;
15828
+ return cells.length === 1 && !cell.isFormula && !((_a = cell.format) === null || _a === void 0 ? void 0 : _a.match(DATETIME_FORMAT));
15829
+ },
15830
+ generateRule: () => {
15831
+ return { type: "COPY_MODIFIER" };
15832
+ },
15833
+ sequence: 10,
15834
+ })
15835
+ .add("increment_alphanumeric_value", {
15836
+ condition: (cell) => !cell.isFormula &&
15837
+ evaluateLiteral(cell.content).type === CellValueType.text &&
15838
+ alphaNumericValueRegExp.test(cell.content),
15839
+ generateRule: (cell, cells) => {
15840
+ const numberPostfix = parseInt(cell.content.match(numberPostfixRegExp)[0]);
15841
+ const prefix = cell.content.match(stringPrefixRegExp)[0];
15842
+ const numberPostfixLength = cell.content.length - prefix.length;
15843
+ const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.text &&
15844
+ alphaNumericValueRegExp.test(evaluatedCell.value)) // get consecutive alphanumeric cells, no matter what the prefix is
15845
+ .filter((cell) => prefix === cell.value.toString().match(stringPrefixRegExp)[0])
15846
+ .map((cell) => parseInt(cell.value.toString().match(numberPostfixRegExp)[0]));
15847
+ const increment = calculateIncrementBasedOnGroup(group);
15848
+ return {
15849
+ type: "ALPHANUMERIC_INCREMENT_MODIFIER",
15850
+ prefix,
15851
+ current: numberPostfix,
15852
+ increment,
15853
+ numberPostfixLength,
15854
+ };
15855
+ },
15856
+ sequence: 15,
15857
+ })
15858
+ .add("copy_text", {
15859
+ condition: (cell) => !cell.isFormula && evaluateLiteral(cell.content).type === CellValueType.text,
15860
+ generateRule: () => {
15861
+ return { type: "COPY_MODIFIER" };
15862
+ },
15863
+ sequence: 20,
15864
+ })
15865
+ .add("update_formula", {
15866
+ condition: (cell) => cell.isFormula,
15867
+ generateRule: (_, cells) => {
15868
+ return { type: "FORMULA_MODIFIER", increment: cells.length, current: 0 };
15869
+ },
15870
+ sequence: 30,
15871
+ })
15872
+ .add("increment_number", {
15873
+ condition: (cell) => !cell.isFormula && evaluateLiteral(cell.content).type === CellValueType.number,
15874
+ generateRule: (cell, cells) => {
15875
+ const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number).map((cell) => Number(cell.value));
15876
+ const increment = calculateIncrementBasedOnGroup(group);
15877
+ const evaluation = evaluateLiteral(cell.content);
15878
+ return {
15879
+ type: "INCREMENT_MODIFIER",
15880
+ increment,
15881
+ current: evaluation.type === CellValueType.number ? evaluation.value : 0,
15882
+ };
15883
+ },
15884
+ sequence: 40,
15885
+ });
15886
+
15887
+ const ERROR_TOOLTIP_MAX_HEIGHT = 80;
15888
+ const ERROR_TOOLTIP_WIDTH = 180;
15889
+ css /* scss */ `
15890
+ .o-error-tooltip {
15891
+ font-size: 13px;
15892
+ background-color: white;
15893
+ border-left: 3px solid red;
15894
+ padding: 10px;
15895
+ width: ${ERROR_TOOLTIP_WIDTH}px;
15896
+ box-sizing: border-box !important;
15897
+ }
15898
+ `;
15899
+ class ErrorToolTip extends owl.Component {
15900
+ }
15901
+ ErrorToolTip.maxSize = { maxHeight: ERROR_TOOLTIP_MAX_HEIGHT };
15902
+ ErrorToolTip.template = "o-spreadsheet-ErrorToolTip";
15903
+ ErrorToolTip.components = {};
15904
+ ErrorToolTip.props = {
15905
+ text: String,
15906
+ onClosed: { type: Function, optional: true },
15907
+ };
15908
+ const ErrorToolTipPopoverBuilder = {
15909
+ onHover: (position, getters) => {
15910
+ const cell = getters.getEvaluatedCell(position);
15911
+ if (cell.type === CellValueType.error && cell.error.logLevel > CellErrorLevel.silent) {
15912
+ return {
15913
+ isOpen: true,
15914
+ props: { text: cell.error.message },
15915
+ Component: ErrorToolTip,
15916
+ cellCorner: "TopRight",
15917
+ };
15918
+ }
15919
+ return { isOpen: false };
15920
+ },
15921
+ };
15922
+
15923
+ css /*SCSS*/ `
15924
+ .o-filter-menu-value {
15925
+ padding: 4px;
15926
+ line-height: 20px;
15927
+ height: 28px;
15928
+ .o-filter-menu-value-checked {
15929
+ width: 20px;
15930
+ }
15931
+ }
15932
+ `;
15933
+ class FilterMenuValueItem extends owl.Component {
15934
+ constructor() {
15935
+ super(...arguments);
15936
+ this.itemRef = owl.useRef("menuValueItem");
15937
+ }
15938
+ setup() {
15939
+ owl.onWillPatch(() => {
15940
+ if (this.props.scrolledTo) {
15941
+ this.scrollListToSelectedValue();
15942
+ }
15943
+ });
15944
+ }
15945
+ scrollListToSelectedValue() {
15946
+ var _a, _b;
15947
+ if (!this.itemRef.el) {
15948
+ return;
15949
+ }
15950
+ (_b = (_a = this.itemRef.el).scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a, {
15951
+ block: this.props.scrolledTo === "bottom" ? "end" : "start",
15952
+ });
15953
+ }
15954
+ }
15955
+ FilterMenuValueItem.template = "o-spreadsheet-FilterMenuValueItem";
15956
+ FilterMenuValueItem.props = {
15957
+ value: String,
15958
+ isChecked: Boolean,
15959
+ isSelected: Boolean,
15960
+ onMouseMove: Function,
15961
+ onClick: Function,
15962
+ scrolledTo: { type: String, optional: true },
15963
+ };
15964
+
15965
+ const FILTER_MENU_HEIGHT = 295;
15966
+ const CSS = css /* scss */ `
15967
+ .o-filter-menu {
15968
+ box-sizing: border-box;
15969
+ padding: 8px 16px;
15970
+ height: ${FILTER_MENU_HEIGHT}px;
15971
+ line-height: 1;
15972
+
15973
+ .o-filter-menu-item {
15974
+ display: flex;
15975
+ box-sizing: border-box;
15976
+ height: ${MENU_ITEM_HEIGHT}px;
15977
+ padding: 4px 4px 4px 0px;
15978
+ cursor: pointer;
15979
+ user-select: none;
15980
+
15981
+ &.selected {
15982
+ background-color: rgba(0, 0, 0, 0.08);
15983
+ }
15984
+ }
15985
+
15986
+ input {
15987
+ box-sizing: border-box;
15988
+ margin-bottom: 5px;
15989
+ border: 1px solid #949494;
15990
+ height: 24px;
15991
+ padding-right: 28px;
15992
+ }
15993
+
15994
+ .o-search-icon {
15995
+ right: 5px;
15996
+ top: 4px;
15997
+
15998
+ svg {
15999
+ height: 16px;
16000
+ width: 16px;
16001
+ vertical-align: middle;
16002
+ }
16003
+ }
16004
+
16005
+ .o-filter-menu-actions {
16006
+ display: flex;
16007
+ flex-direction: row;
16008
+ margin-bottom: 4px;
16009
+
16010
+ .o-filter-menu-action-text {
16011
+ cursor: pointer;
16012
+ margin-right: 10px;
16013
+ color: blue;
16014
+ text-decoration: underline;
16015
+ }
16016
+ }
16017
+
16018
+ .o-filter-menu-list {
16019
+ flex: auto;
16020
+ overflow-y: auto;
16021
+ border: 1px solid #949494;
16022
+
16023
+ .o-filter-menu-no-values {
16024
+ color: #949494;
16025
+ font-style: italic;
16026
+ }
16027
+ }
16028
+
16029
+ .o-filter-menu-buttons {
16030
+ margin-top: 9px;
16031
+
16032
+ .o-filter-menu-button {
16033
+ border: 1px solid lightgrey;
16034
+ padding: 6px 10px;
16035
+ cursor: pointer;
16036
+ border-radius: 4px;
16037
+ font-weight: 500;
16038
+ line-height: 16px;
16039
+ }
16040
+
16041
+ .o-filter-menu-button-cancel {
16042
+ background: white;
16043
+ &:hover {
16044
+ background-color: rgba(0, 0, 0, 0.08);
16045
+ }
16046
+ }
16047
+
16048
+ .o-filter-menu-button-primary {
16049
+ background-color: #188038;
16050
+ &:hover {
16051
+ background-color: #1d9641;
16052
+ }
16053
+ color: white;
16054
+ font-weight: bold;
16055
+ margin-left: 10px;
16056
+ }
16057
+ }
16058
+ }
16059
+ `;
16060
+ class FilterMenu extends owl.Component {
16061
+ constructor() {
16062
+ super(...arguments);
16063
+ this.state = owl.useState({
16064
+ values: [],
16065
+ textFilter: "",
16066
+ selectedValue: undefined,
16067
+ });
16068
+ this.searchBar = owl.useRef("filterMenuSearchBar");
16069
+ }
16070
+ setup() {
16071
+ owl.onWillUpdateProps((nextProps) => {
16072
+ if (!deepEquals(nextProps.filterPosition, this.props.filterPosition)) {
16073
+ this.state.values = this.getFilterValues(nextProps.filterPosition);
16074
+ }
16075
+ });
16076
+ this.state.values = this.getFilterValues(this.props.filterPosition);
16077
+ }
16078
+ getFilterValues(position) {
16079
+ const sheetId = this.env.model.getters.getActiveSheetId();
16080
+ const filter = this.env.model.getters.getFilter({ sheetId, ...position });
16081
+ if (!filter) {
16082
+ return [];
16083
+ }
16084
+ const cellValues = (filter.filteredZone ? positions(filter.filteredZone) : [])
16085
+ .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
16086
+ .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
16087
+ const filterValues = this.env.model.getters.getFilterValues({ sheetId, ...position });
16088
+ const strValues = [...cellValues, ...filterValues];
16089
+ const normalizedFilteredValues = filterValues.map(toLowerCase);
16090
+ // Set with lowercase values to avoid duplicates
16091
+ const normalizedValues = [...new Set(strValues.map(toLowerCase))];
16092
+ const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
16093
+ return sortedValues.map((normalizedValue) => {
16094
+ const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
16095
+ -1;
16096
+ return {
16097
+ checked,
16098
+ string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
16099
+ };
16100
+ });
16101
+ }
16102
+ checkValue(value) {
16103
+ var _a;
16104
+ this.state.selectedValue = value.string;
16105
+ value.checked = !value.checked;
16106
+ (_a = this.searchBar.el) === null || _a === void 0 ? void 0 : _a.focus();
16107
+ }
16108
+ onMouseMove(value) {
16109
+ this.state.selectedValue = value.string;
16110
+ }
16111
+ selectAll() {
16112
+ this.state.values.forEach((value) => (value.checked = true));
16113
+ }
16114
+ clearAll() {
16115
+ this.state.values.forEach((value) => (value.checked = false));
16116
+ }
16117
+ get filterTable() {
16118
+ const sheetId = this.env.model.getters.getActiveSheetId();
16119
+ const position = this.props.filterPosition;
16120
+ return this.env.model.getters.getFilterTable({ sheetId, ...position });
16121
+ }
16122
+ get displayedValues() {
16123
+ if (!this.state.textFilter) {
16124
+ return this.state.values;
16125
+ }
16126
+ return fuzzyLookup(this.state.textFilter, this.state.values, (val) => val.string);
16127
+ }
16128
+ confirm() {
16129
+ var _a, _b;
16130
+ const position = this.props.filterPosition;
16131
+ this.env.model.dispatch("UPDATE_FILTER", {
16132
+ ...position,
16133
+ sheetId: this.env.model.getters.getActiveSheetId(),
16134
+ values: this.state.values.filter((val) => !val.checked).map((val) => val.string),
16135
+ });
16136
+ (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
16137
+ }
16138
+ cancel() {
16139
+ var _a, _b;
16140
+ (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
16141
+ }
16142
+ onKeyDown(ev) {
16143
+ const displayedValues = this.displayedValues;
16144
+ if (displayedValues.length === 0)
16145
+ return;
16146
+ let selectedIndex = undefined;
16147
+ if (this.state.selectedValue !== undefined) {
16148
+ const index = displayedValues.findIndex((val) => val.string === this.state.selectedValue);
16149
+ selectedIndex = index === -1 ? undefined : index;
16150
+ }
16151
+ switch (ev.key) {
16152
+ case "ArrowDown":
16153
+ if (selectedIndex === undefined) {
16154
+ selectedIndex = 0;
16155
+ }
16156
+ else {
16157
+ selectedIndex = Math.min(selectedIndex + 1, displayedValues.length - 1);
16158
+ }
16159
+ ev.preventDefault();
16160
+ ev.stopPropagation();
16161
+ break;
16162
+ case "ArrowUp":
16163
+ if (selectedIndex === undefined) {
16164
+ selectedIndex = displayedValues.length - 1;
16165
+ }
16166
+ else {
16167
+ selectedIndex = Math.max(selectedIndex - 1, 0);
16168
+ }
16169
+ ev.preventDefault();
16170
+ ev.stopPropagation();
16171
+ break;
16172
+ case "Enter":
16173
+ if (selectedIndex !== undefined) {
16174
+ this.checkValue(displayedValues[selectedIndex]);
16175
+ }
16176
+ ev.stopPropagation();
16177
+ ev.preventDefault();
16178
+ break;
16179
+ }
16180
+ this.state.selectedValue =
16181
+ selectedIndex !== undefined ? displayedValues[selectedIndex].string : undefined;
16182
+ if (ev.key === "ArrowUp" || ev.key === "ArrowDown") {
16183
+ this.scrollListToSelectedValue(ev.key);
16184
+ }
16185
+ }
16186
+ clearScrolledToValue() {
16187
+ this.state.values.forEach((val) => (val.scrolledTo = undefined));
16188
+ }
16189
+ scrollListToSelectedValue(arrow) {
16190
+ this.clearScrolledToValue();
16191
+ const selectedValue = this.state.values.find((val) => val.string === this.state.selectedValue);
16192
+ if (selectedValue) {
16193
+ selectedValue.scrolledTo = arrow === "ArrowUp" ? "top" : "bottom";
16194
+ }
16195
+ }
16196
+ sortFilterZone(sortDirection) {
16197
+ var _a, _b;
16198
+ const filterPosition = this.props.filterPosition;
16199
+ const filterTable = this.filterTable;
16200
+ if (!filterPosition || !filterTable || !filterTable.contentZone) {
16201
+ return;
16202
+ }
16203
+ const sheetId = this.env.model.getters.getActiveSheetId();
16204
+ this.env.model.dispatch("SORT_CELLS", {
16205
+ sheetId,
16206
+ col: filterPosition.col,
16207
+ row: filterTable.contentZone.top,
16208
+ zone: filterTable.contentZone,
16209
+ sortDirection,
16210
+ sortOptions: { emptyCellAsZero: true, sortHeaders: true },
16211
+ });
16212
+ (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
16213
+ }
16214
+ }
16215
+ FilterMenu.size = { width: MENU_WIDTH, height: FILTER_MENU_HEIGHT };
16216
+ FilterMenu.template = "o-spreadsheet-FilterMenu";
16217
+ FilterMenu.style = CSS;
16218
+ FilterMenu.components = { FilterMenuValueItem };
16219
+ FilterMenu.props = {
16220
+ filterPosition: Object,
16221
+ onClosed: { type: Function, optional: true },
16222
+ };
16223
+ const FilterMenuPopoverBuilder = {
16224
+ onOpen: (position, getters) => {
16225
+ return {
16226
+ isOpen: true,
16227
+ props: { filterPosition: position },
16228
+ Component: FilterMenu,
16229
+ cellCorner: "BottomLeft",
16230
+ };
16231
+ },
16232
+ };
16233
+
16234
+ /**
16235
+ * Return the o-spreadsheet element position relative
16236
+ * to the browser viewport.
16237
+ */
16238
+ function useSpreadsheetRect() {
16239
+ const position = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
16240
+ let spreadsheetElement = document.querySelector(".o-spreadsheet");
16241
+ updatePosition();
16242
+ function updatePosition() {
16243
+ if (!spreadsheetElement) {
16244
+ spreadsheetElement = document.querySelector(".o-spreadsheet");
16245
+ }
16246
+ if (spreadsheetElement) {
16247
+ const { top, left, width, height } = spreadsheetElement.getBoundingClientRect();
16248
+ position.x = left;
16249
+ position.y = top;
16250
+ position.width = width;
16251
+ position.height = height;
16252
+ }
16253
+ }
16254
+ owl.onMounted(updatePosition);
16255
+ owl.onPatched(updatePosition);
16256
+ return position;
16387
16257
  }
16388
16258
  /**
16389
- * Get the average steps between numbers
16259
+ * Return the component (or ref's component) BoundingRect, relative
16260
+ * to the upper left corner of the screen (<body> element).
16261
+ *
16262
+ * Note: when used with a <Portal/> component, it will
16263
+ * return the portal position, not the teleported position.
16390
16264
  */
16391
- function getAverageIncrement(group) {
16392
- const averages = [];
16393
- let last = group[0];
16394
- for (let i = 1; i < group.length; i++) {
16395
- const current = group[i];
16396
- averages.push(current - last);
16397
- last = current;
16265
+ function useAbsoluteBoundingRect(ref) {
16266
+ const rect = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
16267
+ function updateElRect() {
16268
+ const el = ref.el;
16269
+ if (el === null) {
16270
+ return;
16271
+ }
16272
+ const { top, left, width, height } = el.getBoundingClientRect();
16273
+ rect.x = left;
16274
+ rect.y = top;
16275
+ rect.width = width;
16276
+ rect.height = height;
16398
16277
  }
16399
- return averages.reduce((a, b) => a + b, 0) / averages.length;
16278
+ owl.onMounted(updateElRect);
16279
+ owl.onPatched(updateElRect);
16280
+ return rect;
16400
16281
  }
16401
16282
  /**
16402
- * Get the step for a group
16283
+ * Get the rectangle inside which a popover should stay when being displayed.
16284
+ * It's the value defined in `env.getPopoverContainerRect`, or the Rect of the "o-spreadsheet"
16285
+ * element by default.
16286
+ *
16287
+ * Coordinates are expressed expressed as absolute DOM position.
16403
16288
  */
16404
- function calculateIncrementBasedOnGroup(group) {
16405
- let increment = 1;
16406
- if (group.length >= 2) {
16407
- increment = getAverageIncrement(group) * group.length;
16289
+ function usePopoverContainer() {
16290
+ const container = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
16291
+ const component = owl.useComponent();
16292
+ const spreadsheetRect = useSpreadsheetRect();
16293
+ function updateRect() {
16294
+ const env = component.env;
16295
+ const newRect = "getPopoverContainerRect" in env ? env.getPopoverContainerRect() : spreadsheetRect;
16296
+ container.x = newRect.x;
16297
+ container.y = newRect.y;
16298
+ container.width = newRect.width;
16299
+ container.height = newRect.height;
16408
16300
  }
16409
- return increment;
16410
- }
16411
- autofillRulesRegistry
16412
- .add("simple_value_copy", {
16413
- condition: (cell, cells) => {
16414
- var _a;
16415
- return cells.length === 1 && !cell.isFormula && !((_a = cell.format) === null || _a === void 0 ? void 0 : _a.match(DATETIME_FORMAT));
16416
- },
16417
- generateRule: () => {
16418
- return { type: "COPY_MODIFIER" };
16419
- },
16420
- sequence: 10,
16421
- })
16422
- .add("increment_alphanumeric_value", {
16423
- condition: (cell) => !cell.isFormula &&
16424
- evaluateLiteral(cell.content).type === CellValueType.text &&
16425
- alphaNumericValueRegExp.test(cell.content),
16426
- generateRule: (cell, cells) => {
16427
- const numberPostfix = parseInt(cell.content.match(numberPostfixRegExp)[0]);
16428
- const prefix = cell.content.match(stringPrefixRegExp)[0];
16429
- const numberPostfixLength = cell.content.length - prefix.length;
16430
- const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.text &&
16431
- alphaNumericValueRegExp.test(evaluatedCell.value)) // get consecutive alphanumeric cells, no matter what the prefix is
16432
- .filter((cell) => prefix === cell.value.toString().match(stringPrefixRegExp)[0])
16433
- .map((cell) => parseInt(cell.value.toString().match(numberPostfixRegExp)[0]));
16434
- const increment = calculateIncrementBasedOnGroup(group);
16435
- return {
16436
- type: "ALPHANUMERIC_INCREMENT_MODIFIER",
16437
- prefix,
16438
- current: numberPostfix,
16439
- increment,
16440
- numberPostfixLength,
16441
- };
16442
- },
16443
- sequence: 15,
16444
- })
16445
- .add("copy_text", {
16446
- condition: (cell) => !cell.isFormula && evaluateLiteral(cell.content).type === CellValueType.text,
16447
- generateRule: () => {
16448
- return { type: "COPY_MODIFIER" };
16449
- },
16450
- sequence: 20,
16451
- })
16452
- .add("update_formula", {
16453
- condition: (cell) => cell.isFormula,
16454
- generateRule: (_, cells) => {
16455
- return { type: "FORMULA_MODIFIER", increment: cells.length, current: 0 };
16456
- },
16457
- sequence: 30,
16458
- })
16459
- .add("increment_number", {
16460
- condition: (cell) => !cell.isFormula && evaluateLiteral(cell.content).type === CellValueType.number,
16461
- generateRule: (cell, cells) => {
16462
- const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number).map((cell) => Number(cell.value));
16463
- const increment = calculateIncrementBasedOnGroup(group);
16464
- const evaluation = evaluateLiteral(cell.content);
16465
- return {
16466
- type: "INCREMENT_MODIFIER",
16467
- increment,
16468
- current: evaluation.type === CellValueType.number ? evaluation.value : 0,
16469
- };
16470
- },
16471
- sequence: 40,
16472
- });
16473
-
16474
- const ERROR_TOOLTIP_MAX_HEIGHT = 80;
16475
- const ERROR_TOOLTIP_WIDTH = 180;
16476
- css /* scss */ `
16477
- .o-error-tooltip {
16478
- font-size: 13px;
16479
- background-color: white;
16480
- border-left: 3px solid red;
16481
- padding: 10px;
16482
- width: ${ERROR_TOOLTIP_WIDTH}px;
16483
- box-sizing: border-box !important;
16301
+ updateRect();
16302
+ owl.onMounted(updateRect);
16303
+ owl.onPatched(updateRect);
16304
+ return container;
16484
16305
  }
16485
- `;
16486
- class ErrorToolTip extends owl.Component {
16306
+
16307
+ /**
16308
+ * Compute the intersection of two rectangles. Returns nothing if the two rectangles don't overlap
16309
+ */
16310
+ function rectIntersection(rect1, rect2) {
16311
+ return zoneToRect(intersection(rectToZone(rect1), rectToZone(rect2)));
16487
16312
  }
16488
- ErrorToolTip.maxSize = { maxHeight: ERROR_TOOLTIP_MAX_HEIGHT };
16489
- ErrorToolTip.template = "o-spreadsheet-ErrorToolTip";
16490
- ErrorToolTip.components = {};
16491
- ErrorToolTip.props = {
16492
- text: String,
16493
- onClosed: { type: Function, optional: true },
16494
- };
16495
- const ErrorToolTipPopoverBuilder = {
16496
- onHover: (position, getters) => {
16497
- const cell = getters.getEvaluatedCell(position);
16498
- if (cell.type === CellValueType.error && cell.error.logLevel > CellErrorLevel.silent) {
16499
- return {
16500
- isOpen: true,
16501
- props: { text: cell.error.message },
16502
- Component: ErrorToolTip,
16503
- cellCorner: "TopRight",
16504
- };
16505
- }
16506
- return { isOpen: false };
16507
- },
16508
- };
16313
+ function rectToZone(rect) {
16314
+ return {
16315
+ left: rect.x,
16316
+ top: rect.y,
16317
+ right: rect.x + rect.width,
16318
+ bottom: rect.y + rect.height,
16319
+ };
16320
+ }
16321
+ function zoneToRect(zone) {
16322
+ if (!zone)
16323
+ return undefined;
16324
+ return {
16325
+ x: zone.left,
16326
+ y: zone.top,
16327
+ width: zone.right - zone.left,
16328
+ height: zone.bottom - zone.top,
16329
+ };
16330
+ }
16509
16331
 
16510
- css /*SCSS*/ `
16511
- .o-filter-menu-value {
16512
- padding: 4px;
16513
- line-height: 20px;
16514
- height: 28px;
16515
- .o-filter-menu-value-checked {
16516
- width: 20px;
16517
- }
16332
+ css /* scss */ `
16333
+ .o-popover {
16334
+ position: absolute;
16335
+ z-index: ${ComponentsImportance.Popover};
16336
+ overflow: auto;
16337
+ box-shadow: 1px 2px 5px 2px rgb(51 51 51 / 15%);
16338
+ width: fit-content;
16339
+ height: fit-content;
16518
16340
  }
16519
16341
  `;
16520
- class FilterMenuValueItem extends owl.Component {
16342
+ class Popover extends owl.Component {
16521
16343
  constructor() {
16522
16344
  super(...arguments);
16523
- this.itemRef = owl.useRef("menuValueItem");
16345
+ this.popoverRef = owl.useRef("popover");
16346
+ this.currentPosition = undefined;
16347
+ this.currentDisplayValue = undefined;
16348
+ this.spreadsheetRect = useSpreadsheetRect();
16524
16349
  }
16525
16350
  setup() {
16526
- owl.onWillPatch(() => {
16527
- if (this.props.scrolledTo) {
16528
- this.scrollListToSelectedValue();
16351
+ this.containerRect = usePopoverContainer();
16352
+ // useEffect occurs after the DOM is created and the element width/height are computed, but before
16353
+ // the element in rendered, so we can still set its position
16354
+ owl.useEffect(() => {
16355
+ var _a, _b, _c, _d;
16356
+ if (!this.containerRect)
16357
+ throw new Error("Popover container is not defined");
16358
+ const el = this.popoverRef.el;
16359
+ const anchor = rectIntersection(this.props.anchorRect, this.containerRect);
16360
+ const newDisplay = anchor ? "block" : "none";
16361
+ if (this.currentDisplayValue !== "none" && newDisplay === "none") {
16362
+ (_b = (_a = this.props).onPopoverHidden) === null || _b === void 0 ? void 0 : _b.call(_a);
16363
+ }
16364
+ el.style.display = newDisplay;
16365
+ this.currentDisplayValue = newDisplay;
16366
+ if (!anchor)
16367
+ return;
16368
+ const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };
16369
+ const elDims = {
16370
+ width: el.getBoundingClientRect().width,
16371
+ height: el.getBoundingClientRect().height,
16372
+ };
16373
+ const spreadsheetRect = this.spreadsheetRect;
16374
+ const popoverPositionHelper = this.props.positioning === "BottomLeft"
16375
+ ? new BottomLeftPopoverContext(anchor, this.containerRect, propsMaxSize, spreadsheetRect)
16376
+ : new TopRightPopoverContext(anchor, this.containerRect, propsMaxSize, spreadsheetRect);
16377
+ const style = popoverPositionHelper.getCss(elDims, this.props.verticalOffset);
16378
+ for (const property of Object.keys(style)) {
16379
+ el.style[property] = style[property];
16380
+ }
16381
+ const newPosition = popoverPositionHelper.getCurrentPosition(elDims);
16382
+ if (this.currentPosition && newPosition !== this.currentPosition) {
16383
+ (_d = (_c = this.props).onPopoverMoved) === null || _d === void 0 ? void 0 : _d.call(_c);
16529
16384
  }
16385
+ this.currentPosition = newPosition;
16530
16386
  });
16531
16387
  }
16532
- scrollListToSelectedValue() {
16533
- var _a, _b;
16534
- if (!this.itemRef.el) {
16535
- return;
16388
+ }
16389
+ Popover.template = "o-spreadsheet-Popover";
16390
+ Popover.defaultProps = {
16391
+ positioning: "BottomLeft",
16392
+ verticalOffset: 0,
16393
+ onMouseWheel: () => { },
16394
+ onPopoverMoved: () => { },
16395
+ onPopoverHidden: () => { },
16396
+ };
16397
+ Popover.props = {
16398
+ anchorRect: Object,
16399
+ containerRect: { type: Object, optional: true },
16400
+ positioning: { type: String, optional: true },
16401
+ maxWidth: { type: Number, optional: true },
16402
+ maxHeight: { type: Number, optional: true },
16403
+ verticalOffset: { type: Number, optional: true },
16404
+ onMouseWheel: { type: Function, optional: true },
16405
+ onPopoverHidden: { type: Function, optional: true },
16406
+ onPopoverMoved: { type: Function, optional: true },
16407
+ slots: Object,
16408
+ };
16409
+ class PopoverPositionContext {
16410
+ constructor(anchorRect, containerRect, propsMaxSize, spreadsheetOffset) {
16411
+ this.anchorRect = anchorRect;
16412
+ this.containerRect = containerRect;
16413
+ this.propsMaxSize = propsMaxSize;
16414
+ this.spreadsheetOffset = spreadsheetOffset;
16415
+ }
16416
+ /** Check if there is enough space for the popover to be rendered at the bottom of the anchorRect */
16417
+ shouldRenderAtBottom(elementHeight) {
16418
+ return (elementHeight <= this.availableHeightDown ||
16419
+ this.availableHeightDown >= this.availableHeightUp);
16420
+ }
16421
+ /** Check if there is enough space for the popover to be rendered at the right of the anchorRect */
16422
+ shouldRenderAtRight(elementWidth) {
16423
+ return (elementWidth <= this.availableWidthRight ||
16424
+ this.availableWidthRight >= this.availableWidthLeft);
16425
+ }
16426
+ getMaxHeight(elementHeight) {
16427
+ const shouldRenderAtBottom = this.shouldRenderAtBottom(elementHeight);
16428
+ const availableHeight = shouldRenderAtBottom
16429
+ ? this.availableHeightDown
16430
+ : this.availableHeightUp;
16431
+ return this.propsMaxSize.height
16432
+ ? Math.min(availableHeight, this.propsMaxSize.height)
16433
+ : availableHeight;
16434
+ }
16435
+ getMaxWidth(elementWidth) {
16436
+ const shouldRenderAtRight = this.shouldRenderAtRight(elementWidth);
16437
+ const availableWidth = shouldRenderAtRight ? this.availableWidthRight : this.availableWidthLeft;
16438
+ return this.propsMaxSize.width
16439
+ ? Math.min(availableWidth, this.propsMaxSize.width)
16440
+ : availableWidth;
16441
+ }
16442
+ getCss(elDims, verticalOffset) {
16443
+ const maxHeight = this.getMaxHeight(elDims.height);
16444
+ const maxWidth = this.getMaxWidth(elDims.width);
16445
+ const actualHeight = Math.min(maxHeight, elDims.height);
16446
+ const actualWidth = Math.min(maxWidth, elDims.width);
16447
+ const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
16448
+ const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);
16449
+ verticalOffset = shouldRenderAtBottom ? verticalOffset : -verticalOffset;
16450
+ const cssProperties = {
16451
+ "max-height": maxHeight + "px",
16452
+ "max-width": maxWidth + "px",
16453
+ top: this.getTopCoordinate(actualHeight, shouldRenderAtBottom) -
16454
+ this.spreadsheetOffset.y -
16455
+ verticalOffset +
16456
+ "px",
16457
+ left: this.getLeftCoordinate(actualWidth, shouldRenderAtRight) - this.spreadsheetOffset.x + "px",
16458
+ };
16459
+ return cssProperties;
16460
+ }
16461
+ getCurrentPosition(elDims) {
16462
+ const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
16463
+ const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);
16464
+ if (shouldRenderAtBottom && shouldRenderAtRight)
16465
+ return "BottomRight";
16466
+ if (shouldRenderAtBottom && !shouldRenderAtRight)
16467
+ return "BottomLeft";
16468
+ if (!shouldRenderAtBottom && shouldRenderAtRight)
16469
+ return "TopRight";
16470
+ return "TopLeft";
16471
+ }
16472
+ }
16473
+ class BottomLeftPopoverContext extends PopoverPositionContext {
16474
+ get availableHeightUp() {
16475
+ return this.anchorRect.y - this.containerRect.y;
16476
+ }
16477
+ get availableHeightDown() {
16478
+ return this.containerRect.height - this.availableHeightUp - this.anchorRect.height;
16479
+ }
16480
+ get availableWidthRight() {
16481
+ return this.containerRect.x + this.containerRect.width - this.anchorRect.x;
16482
+ }
16483
+ get availableWidthLeft() {
16484
+ return this.anchorRect.x + this.anchorRect.width - this.containerRect.x;
16485
+ }
16486
+ getTopCoordinate(elementHeight, shouldRenderAtBottom) {
16487
+ if (shouldRenderAtBottom) {
16488
+ return this.anchorRect.y + this.anchorRect.height;
16489
+ }
16490
+ else {
16491
+ return this.anchorRect.y - elementHeight;
16492
+ }
16493
+ }
16494
+ getLeftCoordinate(elementWidth, shouldRenderAtRight) {
16495
+ if (shouldRenderAtRight) {
16496
+ return this.anchorRect.x;
16497
+ }
16498
+ else {
16499
+ return this.anchorRect.x + this.anchorRect.width - elementWidth;
16536
16500
  }
16537
- (_b = (_a = this.itemRef.el).scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a, {
16538
- block: this.props.scrolledTo === "bottom" ? "end" : "start",
16539
- });
16540
16501
  }
16541
16502
  }
16542
- FilterMenuValueItem.template = "o-spreadsheet-FilterMenuValueItem";
16543
- FilterMenuValueItem.props = {
16544
- value: String,
16545
- isChecked: Boolean,
16546
- isSelected: Boolean,
16547
- onMouseMove: Function,
16548
- onClick: Function,
16549
- scrolledTo: { type: String, optional: true },
16550
- };
16503
+ class TopRightPopoverContext extends PopoverPositionContext {
16504
+ get availableHeightUp() {
16505
+ return this.anchorRect.y + this.anchorRect.height - this.containerRect.y;
16506
+ }
16507
+ get availableHeightDown() {
16508
+ return this.containerRect.y + this.containerRect.height - this.anchorRect.y;
16509
+ }
16510
+ get availableWidthRight() {
16511
+ return this.containerRect.width - this.anchorRect.width - this.availableWidthLeft;
16512
+ }
16513
+ get availableWidthLeft() {
16514
+ return this.anchorRect.x - this.containerRect.x;
16515
+ }
16516
+ getTopCoordinate(elementHeight, shouldRenderAtBottom) {
16517
+ if (shouldRenderAtBottom) {
16518
+ return this.anchorRect.y;
16519
+ }
16520
+ else {
16521
+ return this.anchorRect.y + this.anchorRect.height - elementHeight;
16522
+ }
16523
+ }
16524
+ getLeftCoordinate(elementWidth, shouldRenderAtRight) {
16525
+ if (shouldRenderAtRight) {
16526
+ return this.anchorRect.x + this.anchorRect.width;
16527
+ }
16528
+ else {
16529
+ return this.anchorRect.x - elementWidth;
16530
+ }
16531
+ }
16532
+ }
16551
16533
 
16552
- const FILTER_MENU_HEIGHT = 295;
16553
- const CSS = css /* scss */ `
16554
- .o-filter-menu {
16555
- box-sizing: border-box;
16556
- padding: 8px 16px;
16557
- height: ${FILTER_MENU_HEIGHT}px;
16558
- line-height: 1;
16534
+ //------------------------------------------------------------------------------
16535
+ // Context Menu Component
16536
+ //------------------------------------------------------------------------------
16537
+ css /* scss */ `
16538
+ .o-menu {
16539
+ background-color: white;
16540
+ padding: ${MENU_VERTICAL_PADDING}px 0px;
16541
+ width: ${MENU_WIDTH}px;
16542
+ box-sizing: border-box !important;
16559
16543
 
16560
- .o-filter-menu-item {
16544
+ .o-menu-item {
16561
16545
  display: flex;
16546
+ justify-content: space-between;
16547
+ align-items: center;
16562
16548
  box-sizing: border-box;
16563
16549
  height: ${MENU_ITEM_HEIGHT}px;
16564
- padding: 4px 4px 4px 0px;
16550
+ padding: 4px 16px;
16565
16551
  cursor: pointer;
16566
16552
  user-select: none;
16567
16553
 
16568
- &.selected {
16569
- background-color: rgba(0, 0, 0, 0.08);
16570
- }
16571
- }
16572
-
16573
- input {
16574
- box-sizing: border-box;
16575
- margin-bottom: 5px;
16576
- border: 1px solid #949494;
16577
- height: 24px;
16578
- padding-right: 28px;
16579
- }
16580
-
16581
- .o-search-icon {
16582
- right: 5px;
16583
- top: 4px;
16584
-
16585
- svg {
16586
- height: 16px;
16587
- width: 16px;
16588
- vertical-align: middle;
16554
+ .o-menu-item-name {
16555
+ overflow: hidden;
16556
+ white-space: nowrap;
16557
+ text-overflow: ellipsis;
16589
16558
  }
16590
- }
16591
16559
 
16592
- .o-filter-menu-actions {
16593
- display: flex;
16594
- flex-direction: row;
16595
- margin-bottom: 4px;
16596
-
16597
- .o-filter-menu-action-text {
16598
- cursor: pointer;
16599
- margin-right: 10px;
16600
- color: blue;
16601
- text-decoration: underline;
16560
+ &.o-menu-root {
16561
+ display: flex;
16562
+ justify-content: space-between;
16602
16563
  }
16603
- }
16604
-
16605
- .o-filter-menu-list {
16606
- flex: auto;
16607
- overflow-y: auto;
16608
- border: 1px solid #949494;
16609
-
16610
- .o-filter-menu-no-values {
16611
- color: #949494;
16612
- font-style: italic;
16564
+ .o-menu-item-icon {
16565
+ margin-top: auto;
16566
+ margin-bottom: auto;
16613
16567
  }
16614
- }
16615
-
16616
- .o-filter-menu-buttons {
16617
- margin-top: 9px;
16618
-
16619
- .o-filter-menu-button {
16620
- border: 1px solid lightgrey;
16621
- padding: 6px 10px;
16622
- cursor: pointer;
16623
- border-radius: 4px;
16624
- font-weight: 500;
16625
- line-height: 16px;
16568
+ .o-icon {
16569
+ width: 10px;
16626
16570
  }
16627
16571
 
16628
- .o-filter-menu-button-cancel {
16629
- background: white;
16630
- &:hover {
16631
- background-color: rgba(0, 0, 0, 0.08);
16572
+ &:not(.disabled) {
16573
+ &:hover,
16574
+ &.o-menu-item-active {
16575
+ background-color: #ebebeb;
16632
16576
  }
16633
- }
16634
-
16635
- .o-filter-menu-button-primary {
16636
- background-color: #188038;
16637
- &:hover {
16638
- background-color: #1d9641;
16577
+ .o-menu-item-description {
16578
+ color: grey;
16639
16579
  }
16640
- color: white;
16641
- font-weight: bold;
16642
- margin-left: 10px;
16580
+ }
16581
+ &.disabled {
16582
+ color: ${MENU_ITEM_DISABLED_COLOR};
16583
+ cursor: not-allowed;
16643
16584
  }
16644
16585
  }
16645
16586
  }
16646
16587
  `;
16647
- class FilterMenu extends owl.Component {
16588
+ class Menu extends owl.Component {
16648
16589
  constructor() {
16649
16590
  super(...arguments);
16650
- this.state = owl.useState({
16651
- values: [],
16652
- textFilter: "",
16653
- selectedValue: undefined,
16591
+ this.subMenu = owl.useState({
16592
+ isOpen: false,
16593
+ position: null,
16594
+ scrollOffset: 0,
16595
+ menuItems: [],
16654
16596
  });
16655
- this.searchBar = owl.useRef("filterMenuSearchBar");
16597
+ this.menuRef = owl.useRef("menu");
16598
+ this.position = useAbsoluteBoundingRect(this.menuRef);
16656
16599
  }
16657
16600
  setup() {
16601
+ owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
16602
+ owl.useExternalListener(window, "contextmenu", this.onExternalClick, { capture: true });
16658
16603
  owl.onWillUpdateProps((nextProps) => {
16659
- if (!deepEquals(nextProps.filterPosition, this.props.filterPosition)) {
16660
- this.state.values = this.getFilterValues(nextProps.filterPosition);
16604
+ if (nextProps.menuItems !== this.props.menuItems) {
16605
+ this.closeSubMenu();
16661
16606
  }
16662
16607
  });
16663
- this.state.values = this.getFilterValues(this.props.filterPosition);
16664
16608
  }
16665
- getFilterValues(position) {
16666
- const sheetId = this.env.model.getters.getActiveSheetId();
16667
- const filter = this.env.model.getters.getFilter({ sheetId, ...position });
16668
- if (!filter) {
16669
- return [];
16609
+ get visibleMenuItems() {
16610
+ return this.props.menuItems.filter((x) => x.isVisible(this.env));
16611
+ }
16612
+ get subMenuPosition() {
16613
+ const position = Object.assign({}, this.subMenu.position);
16614
+ position.y -= this.subMenu.scrollOffset || 0;
16615
+ return position;
16616
+ }
16617
+ get menuHeight() {
16618
+ const menuItems = this.visibleMenuItems;
16619
+ let menuItemsHeight = this.getMenuItemsHeight(menuItems);
16620
+ // We don't display separator at the end of a menu
16621
+ if (menuItems[menuItems.length - 1].separator) {
16622
+ menuItemsHeight -= MENU_SEPARATOR_HEIGHT;
16670
16623
  }
16671
- const cellValues = (filter.filteredZone ? positions(filter.filteredZone) : [])
16672
- .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
16673
- .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
16674
- const filterValues = this.env.model.getters.getFilterValues({ sheetId, ...position });
16675
- const strValues = [...cellValues, ...filterValues];
16676
- const normalizedFilteredValues = filterValues.map(toLowerCase);
16677
- // Set with lowercase values to avoid duplicates
16678
- const normalizedValues = [...new Set(strValues.map(toLowerCase))];
16679
- const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
16680
- return sortedValues.map((normalizedValue) => {
16681
- const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
16682
- -1;
16683
- return {
16684
- checked,
16685
- string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
16686
- };
16687
- });
16624
+ const menuHeight = 2 * MENU_VERTICAL_PADDING + menuItemsHeight;
16625
+ return this.props.maxHeight ? Math.min(menuHeight, this.props.maxHeight) : menuHeight;
16688
16626
  }
16689
- checkValue(value) {
16690
- var _a;
16691
- this.state.selectedValue = value.string;
16692
- value.checked = !value.checked;
16693
- (_a = this.searchBar.el) === null || _a === void 0 ? void 0 : _a.focus();
16627
+ get popoverProps() {
16628
+ const isRoot = this.props.depth === 1;
16629
+ return {
16630
+ anchorRect: {
16631
+ x: this.props.position.x - MENU_WIDTH * (this.props.depth - 1),
16632
+ y: this.props.position.y,
16633
+ width: isRoot ? 0 : MENU_WIDTH,
16634
+ height: isRoot ? 0 : MENU_ITEM_HEIGHT,
16635
+ },
16636
+ positioning: "TopRight",
16637
+ verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
16638
+ onPopoverHidden: () => this.closeSubMenu(),
16639
+ onPopoverMoved: () => this.closeSubMenu(),
16640
+ maxHeight: this.menuHeight,
16641
+ };
16694
16642
  }
16695
- onMouseMove(value) {
16696
- this.state.selectedValue = value.string;
16643
+ getColor(menu) {
16644
+ return menu.textColor ? `color: ${menu.textColor}` : undefined;
16697
16645
  }
16698
- selectAll() {
16699
- this.state.values.forEach((value) => (value.checked = true));
16646
+ async activateMenu(menu) {
16647
+ var _a, _b, _c;
16648
+ const result = await ((_a = menu.action) === null || _a === void 0 ? void 0 : _a.call(menu, this.env));
16649
+ this.close();
16650
+ (_c = (_b = this.props).onMenuClicked) === null || _c === void 0 ? void 0 : _c.call(_b, { detail: result });
16700
16651
  }
16701
- clearAll() {
16702
- this.state.values.forEach((value) => (value.checked = false));
16652
+ close() {
16653
+ this.closeSubMenu();
16654
+ this.props.onClose();
16703
16655
  }
16704
- get filterTable() {
16705
- const sheetId = this.env.model.getters.getActiveSheetId();
16706
- const position = this.props.filterPosition;
16707
- return this.env.model.getters.getFilterTable({ sheetId, ...position });
16656
+ /**
16657
+ * Return the number of pixels between the top of the menu
16658
+ * and the menu item at a given index.
16659
+ */
16660
+ subMenuVerticalPosition(menuIndex) {
16661
+ const menusAbove = this.visibleMenuItems.slice(0, menuIndex);
16662
+ return this.position.y + this.getMenuItemsHeight(menusAbove) + MENU_VERTICAL_PADDING;
16708
16663
  }
16709
- get displayedValues() {
16710
- if (!this.state.textFilter) {
16711
- return this.state.values;
16664
+ onExternalClick(ev) {
16665
+ // Don't close a root menu when clicked to open the submenus.
16666
+ const el = this.menuRef.el;
16667
+ if (el && getOpenedMenus().some((el) => isChildEvent(el, ev))) {
16668
+ return;
16712
16669
  }
16713
- return fuzzyLookup(this.state.textFilter, this.state.values, (val) => val.string);
16670
+ ev.closedMenuId = this.props.menuId;
16671
+ this.close();
16714
16672
  }
16715
- confirm() {
16716
- var _a, _b;
16717
- const position = this.props.filterPosition;
16718
- this.env.model.dispatch("UPDATE_FILTER", {
16719
- ...position,
16720
- sheetId: this.env.model.getters.getActiveSheetId(),
16721
- values: this.state.values.filter((val) => !val.checked).map((val) => val.string),
16722
- });
16723
- (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
16673
+ getMenuItemsHeight(menuItems) {
16674
+ const numberOfSeparators = menuItems.filter((m) => m.separator).length;
16675
+ return MENU_ITEM_HEIGHT * menuItems.length + MENU_SEPARATOR_HEIGHT * numberOfSeparators;
16724
16676
  }
16725
- cancel() {
16726
- var _a, _b;
16727
- (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
16677
+ getName(menu) {
16678
+ return menu.name(this.env);
16728
16679
  }
16729
- onKeyDown(ev) {
16730
- const displayedValues = this.displayedValues;
16731
- if (displayedValues.length === 0)
16732
- return;
16733
- let selectedIndex = undefined;
16734
- if (this.state.selectedValue !== undefined) {
16735
- const index = displayedValues.findIndex((val) => val.string === this.state.selectedValue);
16736
- selectedIndex = index === -1 ? undefined : index;
16737
- }
16738
- switch (ev.key) {
16739
- case "ArrowDown":
16740
- if (selectedIndex === undefined) {
16741
- selectedIndex = 0;
16742
- }
16743
- else {
16744
- selectedIndex = Math.min(selectedIndex + 1, displayedValues.length - 1);
16745
- }
16746
- ev.preventDefault();
16747
- ev.stopPropagation();
16748
- break;
16749
- case "ArrowUp":
16750
- if (selectedIndex === undefined) {
16751
- selectedIndex = displayedValues.length - 1;
16752
- }
16753
- else {
16754
- selectedIndex = Math.max(selectedIndex - 1, 0);
16755
- }
16756
- ev.preventDefault();
16757
- ev.stopPropagation();
16758
- break;
16759
- case "Enter":
16760
- if (selectedIndex !== undefined) {
16761
- this.checkValue(displayedValues[selectedIndex]);
16762
- }
16763
- ev.stopPropagation();
16764
- ev.preventDefault();
16765
- break;
16766
- }
16767
- this.state.selectedValue =
16768
- selectedIndex !== undefined ? displayedValues[selectedIndex].string : undefined;
16769
- if (ev.key === "ArrowUp" || ev.key === "ArrowDown") {
16770
- this.scrollListToSelectedValue(ev.key);
16680
+ isRoot(menu) {
16681
+ return !menu.action;
16682
+ }
16683
+ isEnabled(menu) {
16684
+ if (menu.isEnabled(this.env)) {
16685
+ return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
16771
16686
  }
16687
+ return false;
16772
16688
  }
16773
- clearScrolledToValue() {
16774
- this.state.values.forEach((val) => (val.scrolledTo = undefined));
16689
+ onScroll(ev) {
16690
+ this.subMenu.scrollOffset = ev.target.scrollTop;
16775
16691
  }
16776
- scrollListToSelectedValue(arrow) {
16777
- this.clearScrolledToValue();
16778
- const selectedValue = this.state.values.find((val) => val.string === this.state.selectedValue);
16779
- if (selectedValue) {
16780
- selectedValue.scrolledTo = arrow === "ArrowUp" ? "top" : "bottom";
16692
+ /**
16693
+ * If the given menu is not disabled, open it's submenu at the
16694
+ * correct position according to available surrounding space.
16695
+ */
16696
+ openSubMenu(menu, menuIndex) {
16697
+ const y = this.subMenuVerticalPosition(menuIndex);
16698
+ this.subMenu.position = {
16699
+ x: this.position.x + this.props.depth * MENU_WIDTH,
16700
+ y: y - (this.subMenu.scrollOffset || 0),
16701
+ };
16702
+ this.subMenu.menuItems = menu.children(this.env);
16703
+ this.subMenu.isOpen = true;
16704
+ this.subMenu.parentMenu = menu;
16705
+ }
16706
+ isParentMenu(subMenu, menuItem) {
16707
+ var _a;
16708
+ return ((_a = subMenu.parentMenu) === null || _a === void 0 ? void 0 : _a.id) === menuItem.id;
16709
+ }
16710
+ closeSubMenu() {
16711
+ this.subMenu.isOpen = false;
16712
+ this.subMenu.parentMenu = undefined;
16713
+ }
16714
+ onClickMenu(menu, menuIndex) {
16715
+ if (this.isEnabled(menu)) {
16716
+ if (this.isRoot(menu)) {
16717
+ this.openSubMenu(menu, menuIndex);
16718
+ }
16719
+ else {
16720
+ this.activateMenu(menu);
16721
+ }
16781
16722
  }
16782
16723
  }
16783
- sortFilterZone(sortDirection) {
16784
- var _a, _b;
16785
- const filterPosition = this.props.filterPosition;
16786
- const filterTable = this.filterTable;
16787
- if (!filterPosition || !filterTable || !filterTable.contentZone) {
16788
- return;
16724
+ onMouseOver(menu, position) {
16725
+ if (menu.isEnabled(this.env)) {
16726
+ if (this.isRoot(menu)) {
16727
+ this.openSubMenu(menu, position);
16728
+ }
16729
+ else {
16730
+ this.closeSubMenu();
16731
+ }
16789
16732
  }
16790
- const sheetId = this.env.model.getters.getActiveSheetId();
16791
- this.env.model.dispatch("SORT_CELLS", {
16792
- sheetId,
16793
- col: filterPosition.col,
16794
- row: filterTable.contentZone.top,
16795
- zone: filterTable.contentZone,
16796
- sortDirection,
16797
- sortOptions: { emptyCellAsZero: true, sortHeaders: true },
16798
- });
16799
- (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
16800
16733
  }
16801
16734
  }
16802
- FilterMenu.size = { width: MENU_WIDTH, height: FILTER_MENU_HEIGHT };
16803
- FilterMenu.template = "o-spreadsheet-FilterMenu";
16804
- FilterMenu.style = CSS;
16805
- FilterMenu.components = { FilterMenuValueItem };
16806
- FilterMenu.props = {
16807
- filterPosition: Object,
16808
- onClosed: { type: Function, optional: true },
16735
+ Menu.template = "o-spreadsheet-Menu";
16736
+ Menu.components = { Menu, Popover };
16737
+ Menu.defaultProps = {
16738
+ depth: 1,
16809
16739
  };
16810
- const FilterMenuPopoverBuilder = {
16811
- onOpen: (position, getters) => {
16812
- return {
16813
- isOpen: true,
16814
- props: { filterPosition: position },
16815
- Component: FilterMenu,
16816
- cellCorner: "BottomLeft",
16817
- };
16818
- },
16740
+ Menu.props = {
16741
+ position: Object,
16742
+ menuItems: Array,
16743
+ depth: { type: Number, optional: true },
16744
+ maxHeight: { type: Number, optional: true },
16745
+ onClose: Function,
16746
+ onMenuClicked: { type: Function, optional: true },
16747
+ menuId: { type: String, optional: true },
16819
16748
  };
16820
16749
 
16821
16750
  const LINK_TOOLTIP_HEIGHT = 32;
@@ -17149,83 +17078,6 @@
17149
17078
  const currenciesRegistry = new Registry();
17150
17079
 
17151
17080
  class ImageFigure extends owl.Component {
17152
- constructor() {
17153
- super(...arguments);
17154
- this.menuState = owl.useState({
17155
- isOpen: false,
17156
- position: null,
17157
- });
17158
- this.imageContainerRef = owl.useRef("o-image");
17159
- this.menuButtonRef = owl.useRef("menuButton");
17160
- this.menuButtonRect = useAbsoluteBoundingRect(this.menuButtonRef);
17161
- this.position = useAbsoluteBoundingRect(this.imageContainerRef);
17162
- this.menuItems = createMenu([
17163
- {
17164
- id: "copy",
17165
- name: _lt("Copy"),
17166
- description: "Ctrl+C",
17167
- action: async () => {
17168
- this.env.model.dispatch("SELECT_FIGURE", { id: this.figureId });
17169
- this.env.model.dispatch("COPY");
17170
- await this.env.clipboard.write(this.env.model.getters.getClipboardContent());
17171
- },
17172
- },
17173
- {
17174
- id: "cut",
17175
- name: _lt("Cut"),
17176
- description: "Ctrl+X",
17177
- action: async () => {
17178
- this.env.model.dispatch("SELECT_FIGURE", { id: this.figureId });
17179
- this.env.model.dispatch("CUT");
17180
- await this.env.clipboard.write(this.env.model.getters.getClipboardContent());
17181
- },
17182
- },
17183
- {
17184
- id: "reset_size",
17185
- name: _lt("Reset size"),
17186
- action: () => {
17187
- const size = this.env.model.getters.getImage(this.env.model.getters.getActiveSheetId(), this.figureId).size;
17188
- const { height, width } = getMaxFigureSize(this.env.model.getters, size);
17189
- this.env.model.dispatch("UPDATE_FIGURE", {
17190
- sheetId: this.env.model.getters.getActiveSheetId(),
17191
- id: this.figureId,
17192
- height,
17193
- width,
17194
- });
17195
- },
17196
- },
17197
- {
17198
- id: "delete",
17199
- name: _lt("Delete image"),
17200
- description: "delete",
17201
- action: () => {
17202
- this.env.model.dispatch("DELETE_FIGURE", {
17203
- sheetId: this.env.model.getters.getActiveSheetId(),
17204
- id: this.figureId,
17205
- });
17206
- },
17207
- },
17208
- ]);
17209
- }
17210
- onContextMenu(ev) {
17211
- const position = {
17212
- x: this.position.x + ev.offsetX,
17213
- y: this.position.y + ev.offsetY,
17214
- };
17215
- this.openContextMenu(position);
17216
- }
17217
- showMenu() {
17218
- const { x, y, width } = this.menuButtonRect;
17219
- const menuPosition = {
17220
- x: x >= MENU_WIDTH ? x - MENU_WIDTH : x + width,
17221
- y: y,
17222
- };
17223
- this.openContextMenu(menuPosition);
17224
- }
17225
- openContextMenu(position) {
17226
- this.menuState.isOpen = true;
17227
- this.menuState.position = position;
17228
- }
17229
17081
  // ---------------------------------------------------------------------------
17230
17082
  // Getters
17231
17083
  // ---------------------------------------------------------------------------
@@ -17233,25 +17085,109 @@
17233
17085
  return this.props.figure.id;
17234
17086
  }
17235
17087
  get getImagePath() {
17236
- return this.env.model.getters.getImage(this.env.model.getters.getActiveSheetId(), this.figureId)
17237
- .path;
17088
+ return this.env.model.getters.getImagePath(this.figureId);
17238
17089
  }
17239
17090
  }
17240
17091
  ImageFigure.template = "o-spreadsheet-ImageFigure";
17241
- ImageFigure.components = { Menu };
17092
+ ImageFigure.components = {};
17242
17093
  ImageFigure.props = {
17243
17094
  figure: Object,
17244
17095
  onFigureDeleted: Function,
17245
17096
  };
17246
17097
 
17247
17098
  const figureRegistry = new Registry();
17248
- figureRegistry.add("chart", { Component: ChartFigure, SidePanelComponent: "ChartPanel" });
17099
+ figureRegistry.add("chart", {
17100
+ Component: ChartFigure,
17101
+ SidePanelComponent: "ChartPanel",
17102
+ menuBuilder: getChartMenu,
17103
+ });
17249
17104
  figureRegistry.add("image", {
17250
17105
  Component: ImageFigure,
17251
17106
  keepRatio: true,
17252
17107
  minFigSize: 20,
17253
17108
  borderWidth: 0,
17254
- });
17109
+ menuBuilder: getImageMenuRegistry,
17110
+ });
17111
+ function getChartMenu(figureId, onFigureDeleted, env) {
17112
+ const menuItemSpecs = [
17113
+ {
17114
+ id: "edit",
17115
+ name: _lt("Edit"),
17116
+ sequence: 1,
17117
+ action: () => {
17118
+ env.model.dispatch("SELECT_FIGURE", { id: figureId });
17119
+ env.openSidePanel("ChartPanel");
17120
+ },
17121
+ },
17122
+ getCopyMenuItem(figureId, env),
17123
+ getCutMenuItem(figureId, env),
17124
+ getDeleteMenuItem(figureId, onFigureDeleted, env),
17125
+ ];
17126
+ return createMenu(menuItemSpecs);
17127
+ }
17128
+ function getImageMenuRegistry(figureId, onFigureDeleted, env) {
17129
+ const menuItemSpecs = [
17130
+ getCopyMenuItem(figureId, env),
17131
+ getCutMenuItem(figureId, env),
17132
+ {
17133
+ id: "reset_size",
17134
+ name: _lt("Reset size"),
17135
+ sequence: 4,
17136
+ action: () => {
17137
+ const size = env.model.getters.getImageSize(figureId);
17138
+ const { height, width } = getMaxFigureSize(env.model.getters, size);
17139
+ env.model.dispatch("UPDATE_FIGURE", {
17140
+ sheetId: env.model.getters.getActiveSheetId(),
17141
+ id: figureId,
17142
+ height,
17143
+ width,
17144
+ });
17145
+ },
17146
+ },
17147
+ getDeleteMenuItem(figureId, onFigureDeleted, env),
17148
+ ];
17149
+ return createMenu(menuItemSpecs);
17150
+ }
17151
+ function getCopyMenuItem(figureId, env) {
17152
+ return {
17153
+ id: "copy",
17154
+ name: _lt("Copy"),
17155
+ sequence: 2,
17156
+ description: "Ctrl+C",
17157
+ action: async () => {
17158
+ env.model.dispatch("SELECT_FIGURE", { id: figureId });
17159
+ env.model.dispatch("COPY");
17160
+ await env.clipboard.write(env.model.getters.getClipboardContent());
17161
+ },
17162
+ };
17163
+ }
17164
+ function getCutMenuItem(figureId, env) {
17165
+ return {
17166
+ id: "cut",
17167
+ name: _lt("Cut"),
17168
+ sequence: 3,
17169
+ description: "Ctrl+X",
17170
+ action: async () => {
17171
+ env.model.dispatch("SELECT_FIGURE", { id: figureId });
17172
+ env.model.dispatch("CUT");
17173
+ await env.clipboard.write(env.model.getters.getClipboardContent());
17174
+ },
17175
+ };
17176
+ }
17177
+ function getDeleteMenuItem(figureId, onFigureDeleted, env) {
17178
+ return {
17179
+ id: "delete",
17180
+ name: _lt("Delete"),
17181
+ sequence: 10,
17182
+ action: () => {
17183
+ env.model.dispatch("DELETE_FIGURE", {
17184
+ sheetId: env.model.getters.getActiveSheetId(),
17185
+ id: figureId,
17186
+ });
17187
+ onFigureDeleted();
17188
+ },
17189
+ };
17190
+ }
17255
17191
 
17256
17192
  const inverseCommandRegistry = new Registry()
17257
17193
  .add("ADD_COLUMNS_ROWS", inverseAddColumnsRows)
@@ -18039,9 +17975,9 @@
18039
17975
  const ranges = existingSelectionRange.length
18040
17976
  ? existingSelectionRange
18041
17977
  : this.props.ranges
18042
- ? this.props.ranges.map((xc, i) => ({
17978
+ ? this.props.ranges.map((xc, id) => ({
18043
17979
  xc,
18044
- id: i.toString(),
17980
+ id,
18045
17981
  isFocused: false,
18046
17982
  }))
18047
17983
  : [];
@@ -18107,6 +18043,7 @@
18107
18043
  else if (ev.key === "Enter") {
18108
18044
  const target = ev.target;
18109
18045
  target.blur();
18046
+ this.confirm();
18110
18047
  }
18111
18048
  }
18112
18049
  focus(rangeId) {
@@ -18135,7 +18072,7 @@
18135
18072
  });
18136
18073
  this.triggerChange();
18137
18074
  }
18138
- disable() {
18075
+ confirm() {
18139
18076
  var _a, _b;
18140
18077
  this.env.model.dispatch("UNFOCUS_SELECTION_INPUT");
18141
18078
  const ranges = this.env.model.getters.getSelectionInputValue(this.id);
@@ -18230,7 +18167,6 @@
18230
18167
  LineBarPieConfigPanel.template = "o-spreadsheet-LineBarPieConfigPanel";
18231
18168
  LineBarPieConfigPanel.components = { SelectionInput };
18232
18169
  LineBarPieConfigPanel.props = {
18233
- sheetId: String,
18234
18170
  figureId: String,
18235
18171
  definition: Object,
18236
18172
  updateChart: Function,
@@ -18263,9 +18199,6 @@
18263
18199
  const GRADIENT_HEIGHT = PICKER_WIDTH - 50;
18264
18200
  css /* scss */ `
18265
18201
  .o-color-picker {
18266
- position: absolute;
18267
- top: calc(100% + 5px);
18268
- z-index: ${ComponentsImportance.ColorPicker};
18269
18202
  padding: ${PICKER_PADDING}px 0px;
18270
18203
  box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15);
18271
18204
  background-color: white;
@@ -18380,15 +18313,6 @@
18380
18313
  width: 100%;
18381
18314
  }
18382
18315
  }
18383
- &.right {
18384
- left: 0;
18385
- }
18386
- &.left {
18387
- right: 0;
18388
- }
18389
- &.center {
18390
- left: calc(50% - ${PICKER_WIDTH / 2}px);
18391
- }
18392
18316
  }
18393
18317
  .o-magnifier-glass {
18394
18318
  position: absolute;
@@ -18423,18 +18347,22 @@
18423
18347
  });
18424
18348
  }
18425
18349
  get colorPickerStyle() {
18426
- if (this.props.maxHeight === undefined)
18427
- return "";
18428
- if (this.props.maxHeight <= 0) {
18350
+ if (this.props.maxHeight !== undefined && this.props.maxHeight <= 0) {
18429
18351
  return cssPropertiesToCss({ display: "none" });
18430
18352
  }
18431
- return cssPropertiesToCss({
18432
- "max-height": `${this.props.maxHeight}px`,
18433
- });
18353
+ return "";
18354
+ }
18355
+ get popoverProps() {
18356
+ return {
18357
+ anchorRect: this.props.anchorRect,
18358
+ maxHeight: this.props.maxHeight,
18359
+ positioning: "BottomLeft",
18360
+ verticalOffset: 0,
18361
+ };
18434
18362
  }
18435
18363
  onColorClick(color) {
18436
18364
  if (color) {
18437
- this.props.onColorPicked(color);
18365
+ this.props.onColorPicked(toHex(color));
18438
18366
  }
18439
18367
  }
18440
18368
  getCheckMarkColor() {
@@ -18449,8 +18377,10 @@
18449
18377
  this.state.isCurrentColorInvalid = true;
18450
18378
  return;
18451
18379
  }
18380
+ const color = toHex(this.state.currentColor);
18452
18381
  this.state.isCurrentColorInvalid = false;
18453
- this.props.onColorPicked(this.state.currentColor);
18382
+ this.props.onColorPicked(color);
18383
+ this.state.currentColor = color;
18454
18384
  }
18455
18385
  toggleColorPicker() {
18456
18386
  this.state.showGradient = !this.state.showGradient;
@@ -18485,11 +18415,12 @@
18485
18415
  ColorPicker.defaultProps = {
18486
18416
  currentColor: "", //TODO Change it to false instead of empty string
18487
18417
  };
18418
+ ColorPicker.components = { Popover };
18488
18419
  ColorPicker.props = {
18489
- dropdownDirection: { type: String, optional: true },
18490
18420
  onColorPicked: Function,
18491
18421
  currentColor: { type: String, optional: true },
18492
18422
  maxHeight: { type: Number, optional: true },
18423
+ anchorRect: Object,
18493
18424
  };
18494
18425
 
18495
18426
  css /* scss */ `
@@ -18525,11 +18456,25 @@
18525
18456
  }
18526
18457
  `;
18527
18458
  class ColorPickerWidget extends owl.Component {
18459
+ constructor() {
18460
+ super(...arguments);
18461
+ this.colorPickerButtonRef = owl.useRef("colorPickerButton");
18462
+ }
18528
18463
  get iconStyle() {
18529
18464
  return this.props.currentColor
18530
18465
  ? `border-color: ${this.props.currentColor}`
18531
18466
  : "border-bottom-style: hidden";
18532
18467
  }
18468
+ get colorPickerAnchorRect() {
18469
+ const button = this.colorPickerButtonRef.el;
18470
+ const buttonRect = button.getBoundingClientRect();
18471
+ return {
18472
+ x: buttonRect.x,
18473
+ y: buttonRect.y,
18474
+ width: buttonRect.width,
18475
+ height: buttonRect.height,
18476
+ };
18477
+ }
18533
18478
  }
18534
18479
  ColorPickerWidget.template = "o-spreadsheet-ColorPickerWidget";
18535
18480
  ColorPickerWidget.components = { ColorPicker };
@@ -18539,7 +18484,6 @@
18539
18484
  showColorPicker: Boolean,
18540
18485
  onColorPicked: Function,
18541
18486
  icon: String,
18542
- dropdownDirection: { type: String, optional: true },
18543
18487
  title: { type: String, optional: true },
18544
18488
  disabled: { type: Boolean, optional: true },
18545
18489
  dropdownMaxHeight: { type: Number, optional: true },
@@ -18619,7 +18563,6 @@
18619
18563
  GaugeChartConfigPanel.template = "o-spreadsheet-GaugeChartConfigPanel";
18620
18564
  GaugeChartConfigPanel.components = { SelectionInput };
18621
18565
  GaugeChartConfigPanel.props = {
18622
- sheetId: String,
18623
18566
  figureId: String,
18624
18567
  definition: Object,
18625
18568
  updateChart: Function,
@@ -18766,7 +18709,7 @@
18766
18709
 
18767
18710
  class LineConfigPanel extends LineBarPieConfigPanel {
18768
18711
  get canTreatLabelsAsText() {
18769
- const chart = this.env.model.getters.getChart(this.props.sheetId, this.props.figureId);
18712
+ const chart = this.env.model.getters.getChart(this.props.figureId);
18770
18713
  if (chart && chart instanceof LineChart) {
18771
18714
  return canChartParseLabels(chart.labelRange, this.env.model.getters);
18772
18715
  }
@@ -18843,7 +18786,6 @@
18843
18786
  ScorecardChartConfigPanel.template = "o-spreadsheet-ScorecardChartConfigPanel";
18844
18787
  ScorecardChartConfigPanel.components = { SelectionInput };
18845
18788
  ScorecardChartConfigPanel.props = {
18846
- sheetId: String,
18847
18789
  figureId: String,
18848
18790
  definition: Object,
18849
18791
  updateChart: Function,
@@ -18979,7 +18921,7 @@
18979
18921
  else {
18980
18922
  this.shouldUpdateChart = true;
18981
18923
  }
18982
- if (!this.env.model.getters.isChartDefined(this.sheetId, this.figureId)) {
18924
+ if (!this.env.model.getters.isChartDefined(this.figureId)) {
18983
18925
  this.props.onCloseSidePanel();
18984
18926
  return;
18985
18927
  }
@@ -19000,7 +18942,7 @@
19000
18942
  });
19001
18943
  }
19002
18944
  onTypeChange(type) {
19003
- const context = this.env.model.getters.getContextCreationChart(this.sheetId, this.figureId);
18945
+ const context = this.env.model.getters.getContextCreationChart(this.figureId);
19004
18946
  if (!context) {
19005
18947
  throw new Error("Chart not defined.");
19006
18948
  }
@@ -19012,7 +18954,7 @@
19012
18954
  });
19013
18955
  }
19014
18956
  get chartPanel() {
19015
- const type = this.env.model.getters.getChartType(this.sheetId, this.figureId);
18957
+ const type = this.env.model.getters.getChartType(this.figureId);
19016
18958
  if (!type) {
19017
18959
  throw new Error("Chart not defined.");
19018
18960
  }
@@ -19022,8 +18964,8 @@
19022
18964
  }
19023
18965
  return chartPanel;
19024
18966
  }
19025
- getChartDefinition() {
19026
- return this.env.model.getters.getChartDefinition(this.sheetId, this.figureId);
18967
+ getChartDefinition(figureId = this.figureId) {
18968
+ return this.env.model.getters.getChartDefinition(figureId);
19027
18969
  }
19028
18970
  get chartTypes() {
19029
18971
  return getChartTypes();
@@ -20175,9 +20117,8 @@
20175
20117
 
20176
20118
  .o-figure-menu {
20177
20119
  right: 0px;
20120
+ top: 0px;
20178
20121
  display: none;
20179
- position: absolute;
20180
- padding: 5px;
20181
20122
  }
20182
20123
 
20183
20124
  .o-figure-menu-item {
@@ -20195,7 +20136,10 @@
20195
20136
  class FigureComponent extends owl.Component {
20196
20137
  constructor() {
20197
20138
  super(...arguments);
20139
+ this.menuState = owl.useState({ isOpen: false, position: null, menuItems: [] });
20198
20140
  this.figureRef = owl.useRef("figure");
20141
+ this.menuButtonRef = owl.useRef("menuButton");
20142
+ this.menuButtonRect = useAbsoluteBoundingRect(this.menuButtonRef);
20199
20143
  }
20200
20144
  get isSelected() {
20201
20145
  return this.env.model.getters.getSelectedFigureId() === this.props.figure.id;
@@ -20303,9 +20247,33 @@
20303
20247
  break;
20304
20248
  }
20305
20249
  }
20250
+ onContextMenu(ev) {
20251
+ if (this.env.isDashboard())
20252
+ return;
20253
+ const position = {
20254
+ x: ev.clientX,
20255
+ y: ev.clientY,
20256
+ };
20257
+ this.openContextMenu(position);
20258
+ }
20259
+ showMenu() {
20260
+ const { x, y, width } = this.menuButtonRect;
20261
+ const menuPosition = {
20262
+ x: x >= MENU_WIDTH ? x - MENU_WIDTH : x + width,
20263
+ y: y,
20264
+ };
20265
+ this.openContextMenu(menuPosition);
20266
+ }
20267
+ openContextMenu(position) {
20268
+ this.menuState.isOpen = true;
20269
+ this.menuState.position = position;
20270
+ this.menuState.menuItems = figureRegistry
20271
+ .get(this.props.figure.tag)
20272
+ .menuBuilder(this.props.figure.id, this.props.onFigureDeleted, this.env);
20273
+ }
20306
20274
  }
20307
20275
  FigureComponent.template = "o-spreadsheet-FigureComponent";
20308
- FigureComponent.components = {};
20276
+ FigureComponent.components = { Menu };
20309
20277
  FigureComponent.defaultProps = {
20310
20278
  onFigureDeleted: () => { },
20311
20279
  onMouseDown: () => { },
@@ -22390,6 +22358,7 @@
22390
22358
  theme: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme",
22391
22359
  table: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table",
22392
22360
  hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
22361
+ image: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
22393
22362
  };
22394
22363
  const RELATIONSHIP_NSR = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
22395
22364
  const HEIGHT_FACTOR = 0.75; // 100px => 75 u
@@ -23413,12 +23382,8 @@
23413
23382
  return "n";
23414
23383
  }
23415
23384
  }
23416
- /**
23417
- * For some reason, Excel will only take the devicePixelRatio (i.e. interface scale on Windows desktop)
23418
- * into account for the height.
23419
- */
23420
23385
  function convertHeightToExcel(height) {
23421
- return Math.round(HEIGHT_FACTOR * height * window.devicePixelRatio * 100) / 100;
23386
+ return Math.round(HEIGHT_FACTOR * height * 100) / 100;
23422
23387
  }
23423
23388
  function convertWidthToExcel(width) {
23424
23389
  return Math.round(WIDTH_FACTOR * width * 100) / 100;
@@ -23560,6 +23525,19 @@
23560
23525
  }
23561
23526
  return xlsxId + 1;
23562
23527
  }
23528
+ const imageIds = [];
23529
+ /**
23530
+ * Convert a image o-spreadsheet id to a xlsx id which
23531
+ * are unsigned integers (starting from 1).
23532
+ */
23533
+ function convertImageId(imageId) {
23534
+ const xlsxId = imageIds.findIndex((id) => id === imageId);
23535
+ if (xlsxId === -1) {
23536
+ imageIds.push(imageId);
23537
+ return imageIds.length;
23538
+ }
23539
+ return xlsxId + 1;
23540
+ }
23563
23541
  /**
23564
23542
  * Convert a value expressed in dot to EMU.
23565
23543
  * EMU = English Metrical Unit
@@ -25895,13 +25873,7 @@
25895
25873
  data = migrate(data);
25896
25874
  }
25897
25875
  }
25898
- // sanity check: try to fix missing fields/corrupted state by providing
25899
- // sensible default values
25900
- data = Object.assign(createEmptyWorkbookData(), data, { version: CURRENT_VERSION });
25901
- data.sheets = data.sheets.map((s, i) => Object.assign(createEmptySheet(`Sheet${i + 1}`, `Sheet${i + 1}`), s));
25902
- if (data.sheets.length === 0) {
25903
- data.sheets.push(createEmptySheet(INITIAL_SHEET_ID, "Sheet1"));
25904
- }
25876
+ data = repairData(data);
25905
25877
  return data;
25906
25878
  }
25907
25879
  function migrate(data) {
@@ -26139,6 +26111,48 @@
26139
26111
  },
26140
26112
  },
26141
26113
  ];
26114
+ /**
26115
+ * This function is used to repair faulty data independently of the migration.
26116
+ */
26117
+ function repairData(data) {
26118
+ data = forceUnicityOfFigure(data);
26119
+ data = setDefaults(data);
26120
+ return data;
26121
+ }
26122
+ /**
26123
+ * Force the unicity of figure ids accross sheets
26124
+ */
26125
+ function forceUnicityOfFigure(data) {
26126
+ if (data.uniqueFigureIds) {
26127
+ return data;
26128
+ }
26129
+ const figureIds = new Set();
26130
+ const uuidGenerator = new UuidGenerator();
26131
+ for (const sheet of data.sheets || []) {
26132
+ for (const figure of sheet.figures || []) {
26133
+ if (figureIds.has(figure.id)) {
26134
+ figure.id += uuidGenerator.uuidv4();
26135
+ }
26136
+ figureIds.add(figure.id);
26137
+ }
26138
+ }
26139
+ data.uniqueFigureIds = true;
26140
+ return data;
26141
+ }
26142
+ /**
26143
+ * sanity check: try to fix missing fields/corrupted state by providing
26144
+ * sensible default values
26145
+ */
26146
+ function setDefaults(data) {
26147
+ data = Object.assign(createEmptyWorkbookData(), data, { version: CURRENT_VERSION });
26148
+ data.sheets = data.sheets
26149
+ ? data.sheets.map((s, i) => Object.assign(createEmptySheet(`Sheet${i + 1}`, `Sheet${i + 1}`), s))
26150
+ : [];
26151
+ if (data.sheets.length === 0) {
26152
+ data.sheets.push(createEmptySheet(INITIAL_SHEET_ID, "Sheet1"));
26153
+ }
26154
+ return data;
26155
+ }
26142
26156
  /**
26143
26157
  * The goal of this function is to repair corrupted/wrong initial messages caused by
26144
26158
  * a bug.
@@ -26277,6 +26291,7 @@
26277
26291
  formats: {},
26278
26292
  borders: {},
26279
26293
  revisionId: DEFAULT_REVISION_ID,
26294
+ uniqueFigureIds: true,
26280
26295
  };
26281
26296
  return data;
26282
26297
  }
@@ -26284,6 +26299,7 @@
26284
26299
  return {
26285
26300
  ...createEmptySheet(sheetId, name),
26286
26301
  charts: [],
26302
+ images: [],
26287
26303
  };
26288
26304
  }
26289
26305
  function createEmptyExcelWorkbookData() {
@@ -28056,10 +28072,8 @@
28056
28072
  this.validateChartDefinition = (cmd) => validateChartDefinition(this, cmd.definition);
28057
28073
  }
28058
28074
  adaptRanges(applyChange) {
28059
- for (const sheetId of Object.keys(this.charts)) {
28060
- for (const [chartId, chart] of Object.entries(this.charts[sheetId] || {})) {
28061
- this.history.update("charts", sheetId, chartId, chart === null || chart === void 0 ? void 0 : chart.updateRanges(applyChange));
28062
- }
28075
+ for (const [chartId, chart] of Object.entries(this.charts)) {
28076
+ this.history.update("charts", chartId, chart === null || chart === void 0 ? void 0 : chart.updateRanges(applyChange));
28063
28077
  }
28064
28078
  }
28065
28079
  // ---------------------------------------------------------------------------
@@ -28070,20 +28084,20 @@
28070
28084
  case "CREATE_CHART":
28071
28085
  return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartDuplicate));
28072
28086
  case "UPDATE_CHART":
28073
- return this.validateChartDefinition(cmd);
28087
+ return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartExists));
28074
28088
  default:
28075
28089
  return 0 /* CommandResult.Success */;
28076
28090
  }
28077
28091
  }
28078
28092
  handle(cmd) {
28079
- var _a, _b;
28093
+ var _a;
28080
28094
  switch (cmd.type) {
28081
28095
  case "CREATE_CHART":
28082
28096
  this.addFigure(cmd.id, cmd.sheetId, cmd.position, cmd.size);
28083
- this.addChart(cmd.id, cmd.sheetId, cmd.definition);
28097
+ this.addChart(cmd.id, cmd.definition);
28084
28098
  break;
28085
28099
  case "UPDATE_CHART": {
28086
- this.addChart(cmd.id, cmd.sheetId, cmd.definition);
28100
+ this.addChart(cmd.id, cmd.definition);
28087
28101
  break;
28088
28102
  }
28089
28103
  case "DUPLICATE_SHEET": {
@@ -28092,7 +28106,7 @@
28092
28106
  if (fig.tag === "chart") {
28093
28107
  const figureIdBase = fig.id.split(FIGURE_ID_SPLITTER).pop();
28094
28108
  const duplicatedFigureId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${figureIdBase}`;
28095
- const chart = (_b = (_a = this.charts[cmd.sheetId]) === null || _a === void 0 ? void 0 : _a[fig.id]) === null || _b === void 0 ? void 0 : _b.copyForSheetId(cmd.sheetIdTo);
28109
+ const chart = (_a = this.charts[fig.id]) === null || _a === void 0 ? void 0 : _a.copyForSheetId(cmd.sheetIdTo);
28096
28110
  if (chart) {
28097
28111
  this.dispatch("CREATE_CHART", {
28098
28112
  id: duplicatedFigureId,
@@ -28107,41 +28121,44 @@
28107
28121
  break;
28108
28122
  }
28109
28123
  case "DELETE_FIGURE":
28110
- this.history.update("charts", cmd.sheetId, cmd.id, undefined);
28124
+ this.history.update("charts", cmd.id, undefined);
28111
28125
  break;
28112
28126
  case "DELETE_SHEET":
28113
- this.history.update("charts", cmd.sheetId, undefined);
28127
+ for (let id of this.getChartIds(cmd.sheetId)) {
28128
+ this.history.update("charts", id, undefined);
28129
+ }
28114
28130
  break;
28115
28131
  }
28116
28132
  }
28117
28133
  // ---------------------------------------------------------------------------
28118
28134
  // Getters
28119
28135
  // ---------------------------------------------------------------------------
28120
- getContextCreationChart(sheetId, figureId) {
28121
- var _a, _b;
28122
- return (_b = (_a = this.charts[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId]) === null || _b === void 0 ? void 0 : _b.getContextCreation();
28123
- }
28124
- getChart(sheetId, figureId) {
28136
+ getContextCreationChart(figureId) {
28125
28137
  var _a;
28126
- return (_a = this.charts[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId];
28138
+ return (_a = this.charts[figureId]) === null || _a === void 0 ? void 0 : _a.getContextCreation();
28127
28139
  }
28128
- getChartType(sheetId, figureId) {
28129
- var _a, _b;
28130
- const type = (_b = (_a = this.charts[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId]) === null || _b === void 0 ? void 0 : _b.type;
28140
+ getChart(figureId) {
28141
+ return this.charts[figureId];
28142
+ }
28143
+ getChartType(figureId) {
28144
+ var _a;
28145
+ const type = (_a = this.charts[figureId]) === null || _a === void 0 ? void 0 : _a.type;
28131
28146
  if (!type) {
28132
28147
  throw new Error("Chart not defined.");
28133
28148
  }
28134
28149
  return type;
28135
28150
  }
28136
- isChartDefined(sheetId, figureId) {
28137
- return figureId in (this.charts[sheetId] || {});
28151
+ isChartDefined(figureId) {
28152
+ return figureId in this.charts && this.charts !== undefined;
28138
28153
  }
28139
28154
  getChartIds(sheetId) {
28140
- return Object.keys(this.charts[sheetId] || {});
28155
+ return Object.entries(this.charts)
28156
+ .filter(([, chart]) => (chart === null || chart === void 0 ? void 0 : chart.sheetId) === sheetId)
28157
+ .map(([id]) => id);
28141
28158
  }
28142
- getChartDefinition(sheetId, figureId) {
28143
- var _a, _b;
28144
- const definition = (_b = (_a = this.charts[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId]) === null || _b === void 0 ? void 0 : _b.getDefinition();
28159
+ getChartDefinition(figureId) {
28160
+ var _a;
28161
+ const definition = (_a = this.charts[figureId]) === null || _a === void 0 ? void 0 : _a.getDefinition();
28145
28162
  if (!definition) {
28146
28163
  throw new Error(`There is no chart with the given figureId: ${figureId}`);
28147
28164
  }
@@ -28153,28 +28170,35 @@
28153
28170
  import(data) {
28154
28171
  for (let sheet of data.sheets) {
28155
28172
  if (sheet.figures) {
28156
- const charts = {};
28157
28173
  for (let figure of sheet.figures) {
28158
28174
  // TODO:
28159
28175
  // figure data should be external IMO => chart should be in sheet.chart
28160
28176
  // instead of in figure.data
28161
28177
  if (figure.tag === "chart") {
28162
- charts[figure.id] = this.createChart(figure.id, figure.data, sheet.id);
28178
+ this.charts[figure.id] = this.createChart(figure.id, figure.data, sheet.id);
28163
28179
  }
28164
28180
  }
28165
- this.charts[sheet.id] = charts;
28166
28181
  }
28167
28182
  }
28168
28183
  }
28169
28184
  export(data) {
28185
+ var _a;
28170
28186
  if (data.sheets) {
28171
28187
  for (let sheet of data.sheets) {
28172
28188
  // TODO This code is false, if two plugins want ot insert figures on the sheet, it will crash !
28173
28189
  const sheetFigures = this.getters.getFigures(sheet.id);
28174
- const figures = sheetFigures;
28175
- for (let figure of figures) {
28190
+ const figures = [];
28191
+ for (let sheetFigure of sheetFigures) {
28192
+ const figure = sheetFigure;
28176
28193
  if (figure && figure.tag === "chart") {
28177
- figure.data = this.getChartDefinition(sheet.id, figure.id);
28194
+ const data = (_a = this.charts[figure.id]) === null || _a === void 0 ? void 0 : _a.getDefinition();
28195
+ if (data) {
28196
+ figure.data = data;
28197
+ figures.push(figure);
28198
+ }
28199
+ }
28200
+ else {
28201
+ figures.push(figure);
28178
28202
  }
28179
28203
  }
28180
28204
  sheet.figures = figures;
@@ -28182,13 +28206,13 @@
28182
28206
  }
28183
28207
  }
28184
28208
  exportForExcel(data) {
28185
- var _a, _b;
28209
+ var _a;
28186
28210
  for (let sheet of data.sheets) {
28187
28211
  const sheetFigures = this.getters.getFigures(sheet.id);
28188
28212
  const figures = [];
28189
28213
  for (let figure of sheetFigures) {
28190
28214
  if (figure && figure.tag === "chart") {
28191
- const figureData = (_b = (_a = this.charts[sheet.id]) === null || _a === void 0 ? void 0 : _a[figure.id]) === null || _b === void 0 ? void 0 : _b.getDefinitionForExcel();
28215
+ const figureData = (_a = this.charts[figure.id]) === null || _a === void 0 ? void 0 : _a.getDefinitionForExcel();
28192
28216
  if (figureData) {
28193
28217
  figures.push({
28194
28218
  ...figure,
@@ -28227,15 +28251,21 @@
28227
28251
  * Add a chart in the local state. If a chart already exists, this chart is
28228
28252
  * replaced
28229
28253
  */
28230
- addChart(id, sheetId, definition) {
28231
- this.history.update("charts", sheetId, id, this.createChart(id, definition, sheetId));
28254
+ addChart(id, definition) {
28255
+ const sheetId = this.getters.getFigureSheetId(id);
28256
+ if (sheetId) {
28257
+ this.history.update("charts", id, this.createChart(id, definition, sheetId));
28258
+ }
28232
28259
  }
28233
28260
  checkChartDuplicate(cmd) {
28234
- var _a;
28235
- if ((_a = this.charts[cmd.sheetId]) === null || _a === void 0 ? void 0 : _a[cmd.id]) {
28236
- return 84 /* CommandResult.DuplicatedChartId */;
28237
- }
28238
- return 0 /* CommandResult.Success */;
28261
+ return this.getters.getFigureSheetId(cmd.id)
28262
+ ? 84 /* CommandResult.DuplicatedChartId */
28263
+ : 0 /* CommandResult.Success */;
28264
+ }
28265
+ checkChartExists(cmd) {
28266
+ return this.getters.getFigureSheetId(cmd.id)
28267
+ ? 0 /* CommandResult.Success */
28268
+ : 85 /* CommandResult.ChartDoesNotExist */;
28239
28269
  }
28240
28270
  }
28241
28271
  ChartPlugin.getters = [
@@ -28616,7 +28646,7 @@
28616
28646
  allowDispatch(cmd) {
28617
28647
  switch (cmd.type) {
28618
28648
  case "CREATE_FIGURE":
28619
- return this.checkFigureDuplicate(cmd.sheetId, cmd.figure.id);
28649
+ return this.checkFigureDuplicate(cmd.figure.id);
28620
28650
  case "UPDATE_FIGURE":
28621
28651
  case "DELETE_FIGURE":
28622
28652
  return this.checkFigureExists(cmd.sheetId, cmd.id);
@@ -28682,9 +28712,8 @@
28682
28712
  }
28683
28713
  return 0 /* CommandResult.Success */;
28684
28714
  }
28685
- checkFigureDuplicate(sheetId, figureId) {
28686
- var _a;
28687
- if ((_a = this.figures[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId]) {
28715
+ checkFigureDuplicate(figureId) {
28716
+ if (Object.values(this.figures).find((sheet) => sheet === null || sheet === void 0 ? void 0 : sheet[figureId])) {
28688
28717
  return 82 /* CommandResult.DuplicatedFigureId */;
28689
28718
  }
28690
28719
  return 0 /* CommandResult.Success */;
@@ -28699,6 +28728,9 @@
28699
28728
  var _a;
28700
28729
  return (_a = this.figures[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId];
28701
28730
  }
28731
+ getFigureSheetId(figureId) {
28732
+ return Object.keys(this.figures).find((sheetId) => { var _a; return ((_a = this.figures[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId]) !== undefined; });
28733
+ }
28702
28734
  // ---------------------------------------------------------------------------
28703
28735
  // Import/Export
28704
28736
  // ---------------------------------------------------------------------------
@@ -28723,7 +28755,7 @@
28723
28755
  this.export(data);
28724
28756
  }
28725
28757
  }
28726
- FigurePlugin.getters = ["getFigures", "getFigure"];
28758
+ FigurePlugin.getters = ["getFigures", "getFigure", "getFigureSheetId"];
28727
28759
 
28728
28760
  class FilterTable {
28729
28761
  constructor(zone) {
@@ -28903,7 +28935,7 @@
28903
28935
  filters.push(new Filter(filter.id, filterZone));
28904
28936
  }
28905
28937
  // Add filters for new columns
28906
- if (filters.length < zoneToDimension(zone).width) {
28938
+ if (filters.length < zoneToDimension(zone).numberOfCols) {
28907
28939
  for (let col = zone.left; col <= zone.right; col++) {
28908
28940
  if (!filters.find((filter) => filter.col === col)) {
28909
28941
  filters.push(new Filter(this.uuidGenerator.uuidv4(), { ...zone, left: col, right: col }));
@@ -29450,7 +29482,7 @@
29450
29482
  switch (cmd.type) {
29451
29483
  case "CREATE_IMAGE":
29452
29484
  if (this.getters.getFigure(cmd.sheetId, cmd.figureId)) {
29453
- return 28 /* CommandResult.DuplicatedImageId */;
29485
+ return 28 /* CommandResult.InvalidFigureId */;
29454
29486
  }
29455
29487
  return 0 /* CommandResult.Success */;
29456
29488
  default:
@@ -29470,7 +29502,7 @@
29470
29502
  if (fig.tag === "image") {
29471
29503
  const figureIdBase = fig.id.split(FIGURE_ID_SPLITTER).pop();
29472
29504
  const duplicatedFigureId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${figureIdBase}`;
29473
- const image = this.getImage(cmd.sheetId, fig.id);
29505
+ const image = this.getImage(fig.id);
29474
29506
  if (image) {
29475
29507
  const size = { width: fig.width, height: fig.height };
29476
29508
  this.dispatch("CREATE_IMAGE", {
@@ -29508,13 +29540,19 @@
29508
29540
  // ---------------------------------------------------------------------------
29509
29541
  // Getters
29510
29542
  // ---------------------------------------------------------------------------
29511
- getImage(sheetId, figureId) {
29512
- var _a;
29513
- const image = (_a = this.images[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId];
29514
- if (!image) {
29515
- throw new Error(`There is no image with the given figureId: ${figureId}`);
29543
+ getImage(figureId) {
29544
+ for (const sheet of Object.values(this.images)) {
29545
+ if (sheet && sheet[figureId]) {
29546
+ return sheet[figureId];
29547
+ }
29516
29548
  }
29517
- return image;
29549
+ throw new Error(`There is no image with the given figureId: ${figureId}`);
29550
+ }
29551
+ getImagePath(figureId) {
29552
+ return this.getImage(figureId).path;
29553
+ }
29554
+ getImageSize(figureId) {
29555
+ return this.getImage(figureId).size;
29518
29556
  }
29519
29557
  // ---------------------------------------------------------------------------
29520
29558
  // Private
@@ -29548,6 +29586,24 @@
29548
29586
  }
29549
29587
  }
29550
29588
  }
29589
+ exportForExcel(data) {
29590
+ for (const sheet of data.sheets) {
29591
+ const figures = this.getters.getFigures(sheet.id);
29592
+ const images = [];
29593
+ for (const figure of figures) {
29594
+ if ((figure === null || figure === void 0 ? void 0 : figure.tag) === "image") {
29595
+ const image = this.getImage(figure.id);
29596
+ if (image) {
29597
+ images.push({
29598
+ ...figure,
29599
+ data: deepCopy(image),
29600
+ });
29601
+ }
29602
+ }
29603
+ }
29604
+ sheet.images = images;
29605
+ }
29606
+ }
29551
29607
  getAllImages() {
29552
29608
  const images = [];
29553
29609
  for (const sheetId in this.images) {
@@ -29556,7 +29612,7 @@
29556
29612
  return images;
29557
29613
  }
29558
29614
  }
29559
- ImagePlugin.getters = ["getImage"];
29615
+ ImagePlugin.getters = ["getImage", "getImagePath", "getImageSize"];
29560
29616
 
29561
29617
  class MergePlugin extends CorePlugin {
29562
29618
  constructor() {
@@ -29745,8 +29801,8 @@
29745
29801
  if (merge) {
29746
29802
  return isEqual(zone, merge);
29747
29803
  }
29748
- const { width, height } = zoneToDimension(zone);
29749
- return width === 1 && height === 1;
29804
+ const { numberOfCols, numberOfRows } = zoneToDimension(zone);
29805
+ return numberOfCols === 1 && numberOfRows === 1;
29750
29806
  }
29751
29807
  // ---------------------------------------------------------------------------
29752
29808
  // Merges
@@ -29914,8 +29970,8 @@
29914
29970
  this.removeMerge(sheetId, currentZone);
29915
29971
  break;
29916
29972
  default:
29917
- const { width, height } = zoneToDimension(result.range.zone);
29918
- if (width === 1 && height === 1) {
29973
+ const { numberOfCols, numberOfRows } = zoneToDimension(result.range.zone);
29974
+ if (numberOfCols === 1 && numberOfRows === 1) {
29919
29975
  this.removeMerge(sheetId, currentZone);
29920
29976
  }
29921
29977
  else {
@@ -30674,8 +30730,8 @@
30674
30730
  }
30675
30731
  getSheetSize(sheetId) {
30676
30732
  return {
30677
- height: this.getNumberRows(sheetId),
30678
- width: this.getNumberCols(sheetId),
30733
+ numberOfRows: this.getNumberRows(sheetId),
30734
+ numberOfCols: this.getNumberCols(sheetId),
30679
30735
  };
30680
30736
  }
30681
30737
  getSheetZone(sheetId) {
@@ -31362,9 +31418,7 @@
31362
31418
  return formatColors.filter(isDefined$1);
31363
31419
  }
31364
31420
  getChartColors(sheetId) {
31365
- const charts = this.getters
31366
- .getChartIds(sheetId)
31367
- .map((cid) => this.getters.getChart(sheetId, cid));
31421
+ const charts = this.getters.getChartIds(sheetId).map((cid) => this.getters.getChart(cid));
31368
31422
  let chartsColors = new Set();
31369
31423
  for (let chart of charts) {
31370
31424
  if (chart === undefined) {
@@ -31730,37 +31784,34 @@
31730
31784
  invalidateCFEvaluationCommands.has(cmd.type) ||
31731
31785
  cmd.type === "EVALUATE_CELLS" ||
31732
31786
  cmd.type === "UPDATE_CELL") {
31733
- for (const sheetId in this.charts) {
31734
- for (const chartId in this.charts[sheetId]) {
31735
- this.charts[sheetId][chartId] = undefined;
31736
- }
31787
+ for (const chartId in this.charts) {
31788
+ this.charts[chartId] = undefined;
31737
31789
  }
31738
31790
  }
31739
31791
  switch (cmd.type) {
31740
31792
  case "UPDATE_CHART":
31741
31793
  case "CREATE_CHART":
31742
31794
  case "DELETE_FIGURE":
31743
- if (this.charts[cmd.sheetId]) {
31744
- this.charts[cmd.sheetId][cmd.id] = undefined;
31745
- }
31795
+ this.charts[cmd.id] = undefined;
31746
31796
  break;
31747
31797
  case "DELETE_SHEET":
31748
- delete this.charts[cmd.sheetId];
31798
+ for (let chartId in this.charts) {
31799
+ if (!this.getters.isChartDefined(chartId)) {
31800
+ this.charts[chartId] = undefined;
31801
+ }
31802
+ }
31749
31803
  break;
31750
31804
  }
31751
31805
  }
31752
- getChartRuntime(sheetId, figureId) {
31753
- var _a;
31754
- if (!((_a = this.charts[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId])) {
31755
- const chart = this.getters.getChart(sheetId, figureId);
31806
+ getChartRuntime(figureId) {
31807
+ if (!this.charts[figureId]) {
31808
+ const chart = this.getters.getChart(figureId);
31756
31809
  if (!chart) {
31757
31810
  throw new Error(`No chart for the given id: ${figureId}`);
31758
31811
  }
31759
- if (!this.charts[sheetId])
31760
- this.charts[sheetId] = {};
31761
- this.charts[sheetId][figureId] = this.createRuntimeChart(chart);
31812
+ this.charts[figureId] = this.createRuntimeChart(chart);
31762
31813
  }
31763
- return this.charts[sheetId][figureId];
31814
+ return this.charts[figureId];
31764
31815
  }
31765
31816
  /**
31766
31817
  * Get the background color of a chart based on the color of the first cell of the main range
@@ -32196,6 +32247,9 @@
32196
32247
  case "CREATE_SHEET":
32197
32248
  this.filterValues[cmd.sheetId] = {};
32198
32249
  break;
32250
+ case "HIDE_COLUMNS_ROWS":
32251
+ this.updateHiddenRows();
32252
+ break;
32199
32253
  case "UPDATE_FILTER":
32200
32254
  this.updateFilter(cmd);
32201
32255
  this.updateHiddenRows();
@@ -32304,9 +32358,16 @@
32304
32358
  updateHiddenRows() {
32305
32359
  var _a, _b;
32306
32360
  const sheetId = this.getters.getActiveSheetId();
32307
- const filters = this.getters.getFilters(sheetId);
32361
+ const filters = this.getters
32362
+ .getFilters(sheetId)
32363
+ .sort((filter1, filter2) => filter1.zoneWithHeaders.top - filter2.zoneWithHeaders.top);
32308
32364
  const hiddenRows = new Set();
32309
32365
  for (let filter of filters) {
32366
+ // Disable filters whose header are hidden
32367
+ if (this.getters.isRowHiddenByUser(sheetId, filter.zoneWithHeaders.top))
32368
+ continue;
32369
+ if (hiddenRows.has(filter.zoneWithHeaders.top))
32370
+ continue;
32310
32371
  const filteredValues = (_b = (_a = this.filterValues[sheetId]) === null || _a === void 0 ? void 0 : _a[filter.id]) === null || _b === void 0 ? void 0 : _b.map(toLowerCase);
32311
32372
  if (!filteredValues || !filter.filteredZone)
32312
32373
  continue;
@@ -32329,7 +32390,7 @@
32329
32390
  const tableZone = toZone(tableData.range);
32330
32391
  const filters = [];
32331
32392
  const headerNames = [];
32332
- for (const i of range(0, zoneToDimension(tableZone).width)) {
32393
+ for (const i of range(0, zoneToDimension(tableZone).numberOfCols)) {
32333
32394
  const position = {
32334
32395
  sheetId: sheetData.id,
32335
32396
  col: tableZone.left + i,
@@ -33840,7 +33901,7 @@
33840
33901
  dimensionsToSum(sheetId, zone) {
33841
33902
  const dimensions = new Set();
33842
33903
  if (isOneDimensional(zone)) {
33843
- dimensions.add(zoneToDimension(zone).width === 1 ? "COL" : "ROW");
33904
+ dimensions.add(zoneToDimension(zone).numberOfCols === 1 ? "COL" : "ROW");
33844
33905
  return dimensions;
33845
33906
  }
33846
33907
  if (this.lastColIsEmpty(sheetId, zone)) {
@@ -34428,8 +34489,8 @@
34428
34489
  x.zone.bottom < this.getters.getNumberRows(x.sheetId) &&
34429
34490
  x.zone.right < this.getters.getNumberCols(x.sheetId))
34430
34491
  .map((highlight) => {
34431
- const { height, width } = zoneToDimension(highlight.zone);
34432
- const zone = height * width === 1
34492
+ const { numberOfRows, numberOfCols } = zoneToDimension(highlight.zone);
34493
+ const zone = numberOfRows * numberOfCols === 1
34433
34494
  ? this.getters.expandZone(highlight.sheetId, highlight.zone)
34434
34495
  : highlight.zone;
34435
34496
  return {
@@ -35245,8 +35306,8 @@
35245
35306
  const currentMaxId = Math.max(0, ...this.ranges.map((range) => Number(range.id)));
35246
35307
  this.ranges.splice(index, 0, ...values.map((xc, i) => ({
35247
35308
  xc,
35248
- id: (currentMaxId + i + 1).toString(),
35249
- color: colors$1[(currentMaxId + 1 + i) % colors$1.length],
35309
+ id: currentMaxId + i + 1,
35310
+ color: colors$1[(currentMaxId + i) % colors$1.length],
35250
35311
  })));
35251
35312
  }
35252
35313
  /**
@@ -36336,7 +36397,7 @@
36336
36397
  const merges = this.getters.getMerges(sheetId).filter((merge) => overlap(merge, zone));
36337
36398
  /*Test the presence of merges of different sizes*/
36338
36399
  const mergeDimension = zoneToDimension(merges[0]);
36339
- let [widthFirst, heightFirst] = [mergeDimension.width, mergeDimension.height];
36400
+ let [widthFirst, heightFirst] = [mergeDimension.numberOfCols, mergeDimension.numberOfRows];
36340
36401
  if (!merges.every((merge) => {
36341
36402
  let [widthCurrent, heightCurrent] = [
36342
36403
  merge.right - merge.left + 1,
@@ -37412,7 +37473,7 @@
37412
37473
  constructor(dispatch, getters, sheetId, copiedFigureId) {
37413
37474
  this.dispatch = dispatch;
37414
37475
  this.sheetId = sheetId;
37415
- const chart = getters.getChart(sheetId, copiedFigureId);
37476
+ const chart = getters.getChart(copiedFigureId);
37416
37477
  if (!chart) {
37417
37478
  throw new Error(`No chart for the given id: ${copiedFigureId}`);
37418
37479
  }
@@ -37433,7 +37494,7 @@
37433
37494
  constructor(dispatch, getters, sheetId, copiedFigureId) {
37434
37495
  this.dispatch = dispatch;
37435
37496
  this.sheetId = sheetId;
37436
- const image = getters.getImage(sheetId, copiedFigureId);
37497
+ const image = getters.getImage(copiedFigureId);
37437
37498
  this.copiedImage = deepCopy(image);
37438
37499
  }
37439
37500
  paste(sheetId, figureId, position, size) {
@@ -37469,9 +37530,9 @@
37469
37530
  const values = this.values;
37470
37531
  const pasteZone = this.getPasteZone(target);
37471
37532
  const { left: activeCol, top: activeRow } = pasteZone;
37472
- const { width, height } = zoneToDimension(pasteZone);
37533
+ const { numberOfCols, numberOfRows } = zoneToDimension(pasteZone);
37473
37534
  const sheetId = this.getters.getActiveSheetId();
37474
- this.addMissingDimensions(width, height, activeCol, activeRow);
37535
+ this.addMissingDimensions(numberOfCols, numberOfRows, activeCol, activeRow);
37475
37536
  for (let i = 0; i < values.length; i++) {
37476
37537
  for (let j = 0; j < values[i].length; j++) {
37477
37538
  this.dispatch("UPDATE_CELL", {
@@ -37485,8 +37546,8 @@
37485
37546
  const zone = {
37486
37547
  left: activeCol,
37487
37548
  top: activeRow,
37488
- right: activeCol + width - 1,
37489
- bottom: activeRow + height - 1,
37549
+ right: activeCol + numberOfCols - 1,
37550
+ bottom: activeRow + numberOfRows - 1,
37490
37551
  };
37491
37552
  this.selection.selectZone({ cell: { col: activeCol, row: activeRow }, zone });
37492
37553
  }
@@ -43585,7 +43646,7 @@
43585
43646
  }
43586
43647
  }
43587
43648
 
43588
- function createDrawing(chartRelIds, sheet, figures) {
43649
+ function createDrawing(drawingRelIds, sheet, figures) {
43589
43650
  const namespaces = [
43590
43651
  ["xmlns:xdr", NAMESPACE.drawing],
43591
43652
  ["xmlns:r", RELATIONSHIP_NSR],
@@ -43594,46 +43655,14 @@
43594
43655
  ];
43595
43656
  const figuresNodes = [];
43596
43657
  for (const [figureIndex, figure] of Object.entries(figures)) {
43597
- // position
43598
- const { from, to } = convertFigureData(figure, sheet);
43599
- const chartId = convertChartId(figure.id);
43600
- const cNvPrAttrs = [
43601
- ["id", chartId],
43602
- ["name", `Chart ${chartId}`],
43603
- ["title", "Chart"],
43604
- ];
43605
- figuresNodes.push(escapeXml /*xml*/ `
43606
- <xdr:twoCellAnchor>
43607
- <xdr:from>
43608
- <xdr:col>${from.col}</xdr:col>
43609
- <xdr:colOff>${from.colOff}</xdr:colOff>
43610
- <xdr:row>${from.row}</xdr:row>
43611
- <xdr:rowOff>${from.rowOff}</xdr:rowOff>
43612
- </xdr:from>
43613
- <xdr:to>
43614
- <xdr:col>${to.col}</xdr:col>
43615
- <xdr:colOff>${to.colOff}</xdr:colOff>
43616
- <xdr:row>${to.row}</xdr:row>
43617
- <xdr:rowOff>${to.rowOff}</xdr:rowOff>
43618
- </xdr:to>
43619
- <xdr:graphicFrame>
43620
- <xdr:nvGraphicFramePr>
43621
- <xdr:cNvPr ${formatAttributes(cNvPrAttrs)} />
43622
- <xdr:cNvGraphicFramePr />
43623
- </xdr:nvGraphicFramePr>
43624
- <xdr:xfrm>
43625
- <a:off x="0" y="0"/>
43626
- <a:ext cx="0" cy="0"/>
43627
- </xdr:xfrm>
43628
- <a:graphic>
43629
- <a:graphicData uri="${DRAWING_NS_C}">
43630
- <c:chart r:id="${chartRelIds[figureIndex]}" />
43631
- </a:graphicData>
43632
- </a:graphic>
43633
- </xdr:graphicFrame>
43634
- <xdr:clientData fLocksWithSheet="0"/>
43635
- </xdr:twoCellAnchor>
43636
- `);
43658
+ switch (figure === null || figure === void 0 ? void 0 : figure.tag) {
43659
+ case "chart":
43660
+ figuresNodes.push(createChartDrawing(figure, sheet, drawingRelIds[figureIndex]));
43661
+ break;
43662
+ case "image":
43663
+ figuresNodes.push(createImageDrawing(figure, sheet, drawingRelIds[figureIndex]));
43664
+ break;
43665
+ }
43637
43666
  }
43638
43667
  const xml = escapeXml /*xml*/ `
43639
43668
  <xdr:wsDr ${formatAttributes(namespaces)}>
@@ -43688,6 +43717,93 @@
43688
43717
  index: headers.length - 1,
43689
43718
  offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_SIZE),
43690
43719
  };
43720
+ }
43721
+ function createChartDrawing(figure, sheet, chartRelId) {
43722
+ // position
43723
+ const { from, to } = convertFigureData(figure, sheet);
43724
+ const chartId = convertChartId(figure.id);
43725
+ const cNvPrAttrs = [
43726
+ ["id", chartId],
43727
+ ["name", `Chart ${chartId}`],
43728
+ ["title", "Chart"],
43729
+ ];
43730
+ return escapeXml /*xml*/ `
43731
+ <xdr:twoCellAnchor>
43732
+ <xdr:from>
43733
+ <xdr:col>${from.col}</xdr:col>
43734
+ <xdr:colOff>${from.colOff}</xdr:colOff>
43735
+ <xdr:row>${from.row}</xdr:row>
43736
+ <xdr:rowOff>${from.rowOff}</xdr:rowOff>
43737
+ </xdr:from>
43738
+ <xdr:to>
43739
+ <xdr:col>${to.col}</xdr:col>
43740
+ <xdr:colOff>${to.colOff}</xdr:colOff>
43741
+ <xdr:row>${to.row}</xdr:row>
43742
+ <xdr:rowOff>${to.rowOff}</xdr:rowOff>
43743
+ </xdr:to>
43744
+ <xdr:graphicFrame>
43745
+ <xdr:nvGraphicFramePr>
43746
+ <xdr:cNvPr ${formatAttributes(cNvPrAttrs)} />
43747
+ <xdr:cNvGraphicFramePr />
43748
+ </xdr:nvGraphicFramePr>
43749
+ <xdr:xfrm>
43750
+ <a:off x="0" y="0"/>
43751
+ <a:ext cx="0" cy="0"/>
43752
+ </xdr:xfrm>
43753
+ <a:graphic>
43754
+ <a:graphicData uri="${DRAWING_NS_C}">
43755
+ <c:chart r:id="${chartRelId}" />
43756
+ </a:graphicData>
43757
+ </a:graphic>
43758
+ </xdr:graphicFrame>
43759
+ <xdr:clientData fLocksWithSheet="0"/>
43760
+ </xdr:twoCellAnchor>
43761
+ `;
43762
+ }
43763
+ function createImageDrawing(figure, sheet, imageRelId) {
43764
+ // position
43765
+ const { from, to } = convertFigureData(figure, sheet);
43766
+ const imageId = convertImageId(figure.id);
43767
+ const cNvPrAttrs = [
43768
+ ["id", imageId],
43769
+ ["name", `Image ${imageId}`],
43770
+ ["title", "Image"],
43771
+ ];
43772
+ return escapeXml /*xml*/ `
43773
+ <xdr:twoCellAnchor editAs="oneCell">
43774
+ <xdr:from>
43775
+ <xdr:col>${from.col}</xdr:col>
43776
+ <xdr:colOff>${from.colOff}</xdr:colOff>
43777
+ <xdr:row>${from.row}</xdr:row>
43778
+ <xdr:rowOff>${from.rowOff}</xdr:rowOff>
43779
+ </xdr:from>
43780
+ <xdr:to>
43781
+ <xdr:col>${to.col}</xdr:col>
43782
+ <xdr:colOff>${to.colOff}</xdr:colOff>
43783
+ <xdr:row>${to.row}</xdr:row>
43784
+ <xdr:rowOff>${to.rowOff}</xdr:rowOff>
43785
+ </xdr:to>
43786
+ <xdr:pic>
43787
+ <xdr:nvPicPr>
43788
+ <xdr:cNvPr ${formatAttributes(cNvPrAttrs)}/>
43789
+ <xdr:cNvPicPr preferRelativeResize="0"/>
43790
+ </xdr:nvPicPr>
43791
+ <xdr:blipFill>
43792
+ <a:blip cstate="print" r:embed="${imageRelId}"/>
43793
+ <a:stretch>
43794
+ <a:fillRect/>
43795
+ </a:stretch>
43796
+ </xdr:blipFill>
43797
+ <xdr:spPr>
43798
+ <a:prstGeom prst="rect">
43799
+ <a:avLst/>
43800
+ </a:prstGeom>
43801
+ <a:noFill/>
43802
+ </xdr:spPr>
43803
+ </xdr:pic>
43804
+ <xdr:clientData fLocksWithSheet="0"/>
43805
+ </xdr:twoCellAnchor>
43806
+ `;
43691
43807
  }
43692
43808
 
43693
43809
  function addNumberFormats(numFmts) {
@@ -43883,7 +43999,7 @@
43883
43999
  function addFilterColumns(table) {
43884
44000
  const tableZone = toZone(table.range);
43885
44001
  const columns = [];
43886
- for (const i of range(0, zoneToDimension(tableZone).width)) {
44002
+ for (const i of range(0, zoneToDimension(tableZone).numberOfCols)) {
43887
44003
  const filter = table.filters[i];
43888
44004
  if (!filter || !filter.filteredValues.length) {
43889
44005
  continue;
@@ -43909,7 +44025,7 @@
43909
44025
  var _a;
43910
44026
  const tableZone = toZone(table.range);
43911
44027
  const columns = [];
43912
- for (const i of range(0, zoneToDimension(tableZone).width)) {
44028
+ for (const i of range(0, zoneToDimension(tableZone).numberOfCols)) {
43913
44029
  const colHeaderXc = toXC(tableZone.left + i, tableZone.top);
43914
44030
  const colName = ((_a = sheetData.cells[colHeaderXc]) === null || _a === void 0 ? void 0 : _a.content) || `col${i}`;
43915
44031
  const colAttributes = [
@@ -44157,23 +44273,37 @@
44157
44273
  currentTableIndex += sheet.filterTables.length;
44158
44274
  // Figures and Charts
44159
44275
  let drawingNode = escapeXml ``;
44276
+ const drawingRelIds = [];
44160
44277
  const charts = sheet.charts;
44161
- if (charts.length) {
44162
- const chartRelIds = [];
44163
- for (const chart of charts) {
44164
- const xlsxChartId = convertChartId(chart.id);
44165
- const chartRelId = addRelsToFile(construct.relsFiles, `xl/drawings/_rels/drawing${sheetIndex}.xml.rels`, {
44166
- target: `../charts/chart${xlsxChartId}.xml`,
44167
- type: XLSX_RELATION_TYPE.chart,
44168
- });
44169
- chartRelIds.push(chartRelId);
44170
- files.push(createXMLFile(createChart(chart, sheetIndex, data), `xl/charts/chart${xlsxChartId}.xml`, "chart"));
44171
- }
44278
+ for (const chart of charts) {
44279
+ const xlsxChartId = convertChartId(chart.id);
44280
+ const chartRelId = addRelsToFile(construct.relsFiles, `xl/drawings/_rels/drawing${sheetIndex}.xml.rels`, {
44281
+ target: `../charts/chart${xlsxChartId}.xml`,
44282
+ type: XLSX_RELATION_TYPE.chart,
44283
+ });
44284
+ drawingRelIds.push(chartRelId);
44285
+ files.push(createXMLFile(createChart(chart, sheetIndex, data), `xl/charts/chart${xlsxChartId}.xml`, "chart"));
44286
+ }
44287
+ const images = sheet.images;
44288
+ for (const image of images) {
44289
+ const xlsxImageId = convertImageId(image.id);
44290
+ const imageRelId = addRelsToFile(construct.relsFiles, `xl/drawings/_rels/drawing${sheetIndex}.xml.rels`, {
44291
+ target: `../media/image${xlsxImageId}`,
44292
+ type: XLSX_RELATION_TYPE.image,
44293
+ });
44294
+ drawingRelIds.push(imageRelId);
44295
+ files.push({
44296
+ path: `xl/media/image${xlsxImageId}`,
44297
+ imagePath: image.data.path,
44298
+ });
44299
+ }
44300
+ const drawings = [...charts, ...images];
44301
+ if (drawings.length) {
44172
44302
  const drawingRelId = addRelsToFile(construct.relsFiles, `xl/worksheets/_rels/sheet${sheetIndex}.xml.rels`, {
44173
44303
  target: `../drawings/drawing${sheetIndex}.xml`,
44174
44304
  type: XLSX_RELATION_TYPE.drawing,
44175
44305
  });
44176
- files.push(createXMLFile(createDrawing(chartRelIds, sheet, charts), `xl/drawings/drawing${sheetIndex}.xml`, "drawing"));
44306
+ files.push(createXMLFile(createDrawing(drawingRelIds, sheet, drawings), `xl/drawings/drawing${sheetIndex}.xml`, "drawing"));
44177
44307
  drawingNode = escapeXml /*xml*/ `<drawing r:id="${drawingRelId}" />`;
44178
44308
  }
44179
44309
  const sheetXml = escapeXml /*xml*/ `
@@ -44287,7 +44417,7 @@
44287
44417
  function createContentTypes(files) {
44288
44418
  const overrideNodes = [];
44289
44419
  for (const file of files) {
44290
- if (file.contentType) {
44420
+ if ("contentType" in file && file.contentType) {
44291
44421
  overrideNodes.push(createOverride("/" + file.path, CONTENT_TYPES[file.contentType]));
44292
44422
  }
44293
44423
  }
@@ -44845,9 +44975,9 @@
44845
44975
  Object.defineProperty(exports, '__esModule', { value: true });
44846
44976
 
44847
44977
 
44848
- __info__.version = '16.2.0-alpha.2';
44849
- __info__.date = '2023-03-01T14:43:49.348Z';
44850
- __info__.hash = 'eb626ad';
44978
+ __info__.version = '16.2.0-alpha.4';
44979
+ __info__.date = '2023-03-14T09:24:38.219Z';
44980
+ __info__.hash = '2c6fe88';
44851
44981
 
44852
44982
 
44853
44983
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);