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