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