@odoo/o-spreadsheet 17.0.22 → 17.0.25

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.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 17.0.22
6
- * @date 2024-05-15T09:18:36.212Z
7
- * @hash 1f90749
5
+ * @version 17.0.25
6
+ * @date 2024-06-10T09:33:40.022Z
7
+ * @hash 1f01394
8
8
  */
9
9
 
10
10
  import { Component, useRef, onMounted, useEffect, onWillPatch, useState, onWillUpdateProps, onPatched, useComponent, useExternalListener, onWillUnmount, onWillStart, xml, useChildSubEnv, useSubEnv, markRaw } from '@odoo/owl';
@@ -45,7 +45,7 @@ class LazyTranslatedString extends String {
45
45
  }
46
46
  valueOf() {
47
47
  const str = super.valueOf();
48
- return _loaded() ? sprintf(_translate(str), ...this.values) : str;
48
+ return _loaded() ? sprintf(_translate(str), ...this.values) : sprintf(str, ...this.values);
49
49
  }
50
50
  toString() {
51
51
  return this.valueOf();
@@ -392,7 +392,10 @@ function deepCopy(obj) {
392
392
  * Check if the object is a plain old javascript object.
393
393
  */
394
394
  function isPlainObject(obj) {
395
- return typeof obj === "object" && obj?.constructor === Object;
395
+ return (typeof obj === "object" &&
396
+ obj !== null &&
397
+ // obj.constructor can be undefined when there's no prototype (`Object.create(null, {})`)
398
+ (obj?.constructor === Object || obj?.constructor === undefined));
396
399
  }
397
400
  /**
398
401
  * Sanitize the name of a sheet, by eventually removing quotes
@@ -664,8 +667,9 @@ function deepEquals(o1, o2, ignoreFunctions) {
664
667
  return false;
665
668
  }
666
669
  else {
667
- if (ignoreFunctions && typeOfO1Key === "function")
668
- return true;
670
+ if (ignoreFunctions && typeOfO1Key === "function") {
671
+ continue;
672
+ }
669
673
  if (o1[key] !== o2[key])
670
674
  return false;
671
675
  }
@@ -4303,8 +4307,11 @@ function copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) {
4303
4307
  /**
4304
4308
  * Create a range from a xc. If the xc is empty, this function returns undefined.
4305
4309
  */
4306
- function createRange(getters, sheetId, range) {
4307
- return range ? getters.getRangeFromSheetXC(sheetId, range) : undefined;
4310
+ function createValidRange(getters, sheetId, xc) {
4311
+ if (!xc)
4312
+ return;
4313
+ const range = getters.getRangeFromSheetXC(sheetId, xc);
4314
+ return !(range.invalidSheetName || range.invalidXc) ? range : undefined;
4308
4315
  }
4309
4316
  /**
4310
4317
  * Spread multiple colrows zone to one row/col zone and add a many new input range as needed.
@@ -5232,8 +5239,8 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
5232
5239
  type = "scorecard";
5233
5240
  constructor(definition, sheetId, getters) {
5234
5241
  super(definition, sheetId, getters);
5235
- this.keyValue = createRange(getters, sheetId, definition.keyValue);
5236
- this.baseline = createRange(getters, sheetId, definition.baseline);
5242
+ this.keyValue = createValidRange(getters, sheetId, definition.keyValue);
5243
+ this.baseline = createValidRange(getters, sheetId, definition.baseline);
5237
5244
  this.baselineMode = definition.baselineMode;
5238
5245
  this.baselineDescr = definition.baselineDescr;
5239
5246
  this.background = definition.background;
@@ -6704,6 +6711,7 @@ const FilterMenuPopoverBuilder = {
6704
6711
  };
6705
6712
 
6706
6713
  const macRegex = /Mac/i;
6714
+ const MODIFIER_KEYS$1 = ["Shift", "Control", "Alt", "Meta"];
6707
6715
  /**
6708
6716
  * Return true if the event was triggered from
6709
6717
  * a child element.
@@ -6742,6 +6750,31 @@ function* iterateChildren(el) {
6742
6750
  function getOpenedMenus() {
6743
6751
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
6744
6752
  }
6753
+ const letterRegex = /^[a-zA-Z]$/;
6754
+ /**
6755
+ * Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
6756
+ *
6757
+ * @argument ev - The keyboard event to transform
6758
+ * @argument mode - Use either ev.key of ev.code to get the string shortcut
6759
+ *
6760
+ * @example
6761
+ * event : { ctrlKey: true, key: "a" } => "Ctrl+A"
6762
+ * event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
6763
+ */
6764
+ function keyboardEventToShortcutString(ev, mode = "key") {
6765
+ let keyDownString = "";
6766
+ if (!MODIFIER_KEYS$1.includes(ev.key)) {
6767
+ if (isCtrlKey(ev))
6768
+ keyDownString += "Ctrl+";
6769
+ if (ev.altKey)
6770
+ keyDownString += "Alt+";
6771
+ if (ev.shiftKey)
6772
+ keyDownString += "Shift+";
6773
+ }
6774
+ const key = mode === "key" ? ev.key : ev.code;
6775
+ keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
6776
+ return keyDownString;
6777
+ }
6745
6778
  function isMacOS() {
6746
6779
  return Boolean(macRegex.test(navigator.userAgent));
6747
6780
  }
@@ -6760,8 +6793,7 @@ function isCtrlKey(ev) {
6760
6793
  */
6761
6794
  function useSpreadsheetRect() {
6762
6795
  const position = useState({ x: 0, y: 0, width: 0, height: 0 });
6763
- let spreadsheetElement = document.querySelector(".o-spreadsheet");
6764
- updatePosition();
6796
+ let spreadsheetElement = null;
6765
6797
  function updatePosition() {
6766
6798
  if (!spreadsheetElement) {
6767
6799
  spreadsheetElement = document.querySelector(".o-spreadsheet");
@@ -8534,7 +8566,7 @@ function chartToImage(runtime, figure, type) {
8534
8566
  if ("chartJsConfig" in runtime) {
8535
8567
  runtime.chartJsConfig.plugins = [backgroundColorChartJSPlugin];
8536
8568
  // @ts-ignore
8537
- const chart = new window.Chart(canvas, runtime.chartJsConfig);
8569
+ const chart = new window.Chart(canvas, deepCopy(runtime.chartJsConfig));
8538
8570
  const imgContent = chart.toBase64Image();
8539
8571
  chart.destroy();
8540
8572
  div.remove();
@@ -8578,7 +8610,7 @@ class BarChart extends AbstractChart {
8578
8610
  constructor(definition, sheetId, getters) {
8579
8611
  super(definition, sheetId, getters);
8580
8612
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
8581
- this.labelRange = createRange(getters, sheetId, definition.labelRange);
8613
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
8582
8614
  this.background = definition.background;
8583
8615
  this.verticalAxisPosition = definition.verticalAxisPosition;
8584
8616
  this.legendPosition = definition.legendPosition;
@@ -8821,7 +8853,7 @@ class GaugeChart extends AbstractChart {
8821
8853
  type = "gauge";
8822
8854
  constructor(definition, sheetId, getters) {
8823
8855
  super(definition, sheetId, getters);
8824
- this.dataRange = createRange(this.getters, this.sheetId, definition.dataRange);
8856
+ this.dataRange = createValidRange(this.getters, this.sheetId, definition.dataRange);
8825
8857
  this.sectionRule = definition.sectionRule;
8826
8858
  this.background = definition.background;
8827
8859
  }
@@ -9155,7 +9187,7 @@ class LineChart extends AbstractChart {
9155
9187
  constructor(definition, sheetId, getters) {
9156
9188
  super(definition, sheetId, getters);
9157
9189
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
9158
- this.labelRange = createRange(this.getters, sheetId, definition.labelRange);
9190
+ this.labelRange = createValidRange(this.getters, sheetId, definition.labelRange);
9159
9191
  this.background = definition.background;
9160
9192
  this.verticalAxisPosition = definition.verticalAxisPosition;
9161
9193
  this.legendPosition = definition.legendPosition;
@@ -9475,7 +9507,7 @@ class PieChart extends AbstractChart {
9475
9507
  constructor(definition, sheetId, getters) {
9476
9508
  super(definition, sheetId, getters);
9477
9509
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
9478
- this.labelRange = createRange(getters, sheetId, definition.labelRange);
9510
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
9479
9511
  this.background = definition.background;
9480
9512
  this.legendPosition = definition.legendPosition;
9481
9513
  this.aggregated = definition.aggregated;
@@ -21722,7 +21754,7 @@ class LineBarPieConfigPanel extends Component {
21722
21754
  }
21723
21755
  const getters = this.env.model.getters;
21724
21756
  const sheetId = getters.getActiveSheetId();
21725
- const labelRange = createRange(getters, sheetId, this.labelRange);
21757
+ const labelRange = createValidRange(getters, sheetId, this.labelRange);
21726
21758
  const dataSets = createDataSets(getters, this.dataSeriesRanges, sheetId, this.props.definition.dataSetsHaveTitle);
21727
21759
  if (dataSets.length) {
21728
21760
  return dataSets[0].dataRange.zone.top + 1;
@@ -25764,14 +25796,14 @@ class FigureComponent extends Component {
25764
25796
  }
25765
25797
  onKeyDown(ev) {
25766
25798
  const figure = this.props.figure;
25767
- switch (ev.key) {
25799
+ const keyDownShortcut = keyboardEventToShortcutString(ev);
25800
+ switch (keyDownShortcut) {
25768
25801
  case "Delete":
25769
25802
  this.env.model.dispatch("DELETE_FIGURE", {
25770
25803
  sheetId: this.env.model.getters.getActiveSheetId(),
25771
25804
  id: figure.id,
25772
25805
  });
25773
25806
  this.props.onFigureDeleted();
25774
- ev.stopPropagation();
25775
25807
  ev.preventDefault();
25776
25808
  ev.stopPropagation();
25777
25809
  break;
@@ -25792,7 +25824,22 @@ class FigureComponent extends Component {
25792
25824
  x: figure.x + delta[0],
25793
25825
  y: figure.y + delta[1],
25794
25826
  });
25827
+ ev.preventDefault();
25795
25828
  ev.stopPropagation();
25829
+ break;
25830
+ case "Ctrl+A":
25831
+ // Maybe in the future we will implement a way to select all figures
25832
+ ev.preventDefault();
25833
+ ev.stopPropagation();
25834
+ break;
25835
+ case "Ctrl+Y":
25836
+ case "Ctrl+Z":
25837
+ if (keyDownShortcut === "Ctrl+Y") {
25838
+ this.env.model.dispatch("REQUEST_REDO");
25839
+ }
25840
+ else if (keyDownShortcut === "Ctrl+Z") {
25841
+ this.env.model.dispatch("REQUEST_UNDO");
25842
+ }
25796
25843
  ev.preventDefault();
25797
25844
  ev.stopPropagation();
25798
25845
  break;
@@ -26663,7 +26710,7 @@ class Composer extends Component {
26663
26710
  Enter: this.processEnterKey,
26664
26711
  Escape: this.processEscapeKey,
26665
26712
  F2: () => console.warn("Not implemented"),
26666
- F4: this.processF4Key,
26713
+ F4: (ev) => this.processF4Key(ev),
26667
26714
  Tab: (ev) => this.processTabKey(ev),
26668
26715
  };
26669
26716
  keyCodeMapping = {
@@ -26768,9 +26815,10 @@ class Composer extends Component {
26768
26815
  processEscapeKey() {
26769
26816
  this.env.model.dispatch("CANCEL_EDITION");
26770
26817
  }
26771
- processF4Key() {
26818
+ processF4Key(ev) {
26772
26819
  this.env.model.dispatch("CYCLE_EDITION_REFERENCES");
26773
26820
  this.processContent();
26821
+ ev.stopPropagation();
26774
26822
  }
26775
26823
  processNumpadDecimal(ev) {
26776
26824
  ev.stopPropagation();
@@ -26811,8 +26859,15 @@ class Composer extends Component {
26811
26859
  }
26812
26860
  onPaste(ev) {
26813
26861
  if (this.env.model.getters.getEditionMode() !== "inactive") {
26862
+ // let the browser clipboard work
26814
26863
  ev.stopPropagation();
26815
26864
  }
26865
+ else {
26866
+ // the user meant to paste in the sheet, not open the composer with the pasted content
26867
+ // While we're not editing, we still have the focus and should therefore prevent
26868
+ // the native "paste" to occur.
26869
+ ev.preventDefault();
26870
+ }
26816
26871
  }
26817
26872
  /*
26818
26873
  * Triggered automatically by the content-editable between the keydown and key up
@@ -26821,10 +26876,6 @@ class Composer extends Component {
26821
26876
  if (!this.shouldProcessInputEvents) {
26822
26877
  return;
26823
26878
  }
26824
- if (ev.inputType === "insertFromPaste" &&
26825
- this.env.model.getters.getEditionMode() === "inactive") {
26826
- return;
26827
- }
26828
26879
  ev.stopPropagation();
26829
26880
  let content;
26830
26881
  if (this.env.model.getters.getEditionMode() === "inactive") {
@@ -27272,10 +27323,7 @@ class GridComposer extends Component {
27272
27323
  }
27273
27324
  get containerStyle() {
27274
27325
  if (this.env.model.getters.getEditionMode() === "inactive" || !this.rect) {
27275
- return `
27276
- position: absolute;
27277
- z-index: -1000;
27278
- `;
27326
+ return `z-index: -1000;`;
27279
27327
  }
27280
27328
  const isFormula = this.env.model.getters.getCurrentContent().startsWith("=");
27281
27329
  const cell = this.env.model.getters.getActiveCell();
@@ -37962,7 +38010,12 @@ class MergePlugin extends CorePlugin {
37962
38010
  * if they have at least a common cell
37963
38011
  */
37964
38012
  doesIntersectMerge(sheetId, zone) {
37965
- return positions(zone).some(({ col, row }) => this.getMerge({ sheetId, col, row }) !== undefined);
38013
+ for (const merge of this.getMerges(sheetId)) {
38014
+ if (overlap(zone, merge)) {
38015
+ return true;
38016
+ }
38017
+ }
38018
+ return false;
37966
38019
  }
37967
38020
  /**
37968
38021
  * Returns true if two columns have at least one merge in common
@@ -38563,6 +38616,9 @@ class RangeAdapter {
38563
38616
  if (range.invalidXc) {
38564
38617
  return range.invalidXc;
38565
38618
  }
38619
+ if (!this.getters.tryGetSheet(range.sheetId)) {
38620
+ return INCORRECT_RANGE_STRING;
38621
+ }
38566
38622
  if (range.zone.bottom - range.zone.top < 0 || range.zone.right - range.zone.left < 0) {
38567
38623
  return INCORRECT_RANGE_STRING;
38568
38624
  }
@@ -42014,16 +42070,22 @@ class EvaluationPlugin extends UIPlugin {
42014
42070
  let newContent = undefined;
42015
42071
  let newFormat = undefined;
42016
42072
  let isExported = true;
42073
+ const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
42017
42074
  const formulaCell = this.getCorrespondingFormulaCell(position);
42018
42075
  if (formulaCell) {
42019
42076
  isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
42020
42077
  isFormula = isExported;
42021
42078
  if (!isExported) {
42022
- newContent = value.toString();
42023
- newFormat = evaluatedCell.format;
42079
+ // If the cell contains a non-exported formula and that is evaluates to
42080
+ // nothing* ,we don't export it.
42081
+ // * non-falsy value are relevant and so are 0 and FALSE, which only leaves
42082
+ // the empty string.
42083
+ if (value !== "") {
42084
+ newContent = value.toString();
42085
+ newFormat = evaluatedCell.format;
42086
+ }
42024
42087
  }
42025
42088
  }
42026
- const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
42027
42089
  const exportedCellData = exportedSheetData.cells[xc] || {};
42028
42090
  const format = newFormat
42029
42091
  ? getItemId(newFormat, data.formats)
@@ -48324,7 +48386,7 @@ class ClipboardPlugin extends UIPlugin {
48324
48386
  case "ADD_COLUMNS_ROWS": {
48325
48387
  this.status = "invisible";
48326
48388
  // If we add a col/row inside or before the cut area, we invalidate the clipboard
48327
- if (this.state?.operation !== "CUT") {
48389
+ if (this.state?.operation !== "CUT" || cmd.sheetId !== this.state?.sheetId) {
48328
48390
  return;
48329
48391
  }
48330
48392
  const isClipboardDirty = this.state.isColRowDirtyingClipboard(cmd.position === "before" ? cmd.base : cmd.base + 1, cmd.dimension);
@@ -48336,7 +48398,7 @@ class ClipboardPlugin extends UIPlugin {
48336
48398
  case "REMOVE_COLUMNS_ROWS": {
48337
48399
  this.status = "invisible";
48338
48400
  // If we remove a col/row inside or before the cut area, we invalidate the clipboard
48339
- if (this.state?.operation !== "CUT") {
48401
+ if (this.state?.operation !== "CUT" || cmd.sheetId !== this.state?.sheetId) {
48340
48402
  return;
48341
48403
  }
48342
48404
  for (let el of cmd.elements) {
@@ -51635,9 +51697,11 @@ class BottomBarSheet extends Component {
51635
51697
  if (ev.key === "Enter") {
51636
51698
  ev.preventDefault();
51637
51699
  this.stopEdition();
51700
+ this.env.focusableElement.focus();
51638
51701
  }
51639
51702
  if (ev.key === "Escape") {
51640
51703
  this.cancelEdition();
51704
+ this.env.focusableElement.focus();
51641
51705
  }
51642
51706
  }
51643
51707
  onClickSheetName(ev) {
@@ -53519,6 +53583,10 @@ class Spreadsheet extends Component {
53519
53583
  });
53520
53584
  useExternalListener(window, "resize", () => this.render(true));
53521
53585
  useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
53586
+ // For some reason, the wheel event is not properly registered inside templates
53587
+ // in Chromium-based browsers based on chromium 125
53588
+ // This hack ensures the event declared in the template is properly registered/working
53589
+ useExternalListener(document.body, "wheel", () => { });
53522
53590
  this.bindModelEvents();
53523
53591
  onWillUpdateProps((nextProps) => {
53524
53592
  if (nextProps.model !== this.props.model) {
@@ -56382,7 +56450,11 @@ function addRows(construct, data, sheet) {
56382
56450
  let cellNode = escapeXml ``;
56383
56451
  // Either formula or static value inside the cell
56384
56452
  if (cell.isFormula) {
56385
- ({ attrs: additionalAttrs, node: cellNode } = addFormula(cell));
56453
+ const res = addFormula(cell);
56454
+ if (!res) {
56455
+ continue;
56456
+ }
56457
+ ({ attrs: additionalAttrs, node: cellNode } = res);
56386
56458
  }
56387
56459
  else if (cell.content && isMarkdownLink(cell.content)) {
56388
56460
  const { label } = parseMarkdownLink(cell.content);
@@ -57367,6 +57439,6 @@ const constants = {
57367
57439
  export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
57368
57440
 
57369
57441
 
57370
- __info__.version = "17.0.22";
57371
- __info__.date = "2024-05-15T09:18:36.212Z";
57372
- __info__.hash = "1f90749";
57442
+ __info__.version = "17.0.25";
57443
+ __info__.date = "2024-06-10T09:33:40.022Z";
57444
+ __info__.hash = "1f01394";