@odoo/o-spreadsheet 17.0.9 → 17.0.12

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.9
6
- * @date 2024-01-29T13:53:22.963Z
7
- * @hash 3c1209a
5
+ * @version 17.0.12
6
+ * @date 2024-02-16T14:24:02.984Z
7
+ * @hash 8fa2bab
8
8
  */
9
9
 
10
10
  'use strict';
@@ -552,8 +552,8 @@ function isObjectEmptyRecursive(argument) {
552
552
  * If the given item does not exist in the dictionary, it creates one with a new id.
553
553
  */
554
554
  function getItemId(item, itemsDic) {
555
- for (let [key, value] of Object.entries(itemsDic)) {
556
- if (deepEquals(value, item)) {
555
+ for (const key in itemsDic) {
556
+ if (deepEquals(itemsDic[key], item)) {
557
557
  return parseInt(key, 10);
558
558
  }
559
559
  }
@@ -652,11 +652,13 @@ function deepEquals(o1, o2) {
652
652
  if (typeof o1 !== "object")
653
653
  return o1 === o2;
654
654
  // Objects can have different keys if the values are undefined
655
- const keys = new Set();
656
- Object.keys(o1).forEach((key) => keys.add(key));
657
- Object.keys(o2).forEach((key) => keys.add(key));
658
- for (let key of keys) {
659
- if (typeof o1[key] !== typeof o1[key])
655
+ for (const key in o2) {
656
+ if (!(key in o1) && o2[key] !== undefined) {
657
+ return false;
658
+ }
659
+ }
660
+ for (const key in o1) {
661
+ if (typeof o1[key] !== typeof o2[key])
660
662
  return false;
661
663
  if (typeof o1[key] === "object") {
662
664
  if (!deepEquals(o1[key], o2[key]))
@@ -726,18 +728,18 @@ function isConsecutive(iterable) {
726
728
  return true;
727
729
  }
728
730
  class JetSet extends Set {
729
- add(...iterable) {
731
+ addMany(iterable) {
730
732
  for (const element of iterable) {
731
733
  super.add(element);
732
734
  }
733
735
  return this;
734
736
  }
735
- delete(...iterable) {
736
- let deleted = false;
737
+ deleteMany(iterable) {
738
+ let wasDeleted = false;
737
739
  for (const element of iterable) {
738
- deleted ||= super.delete(element);
740
+ wasDeleted ||= super.delete(element);
739
741
  }
740
- return deleted;
742
+ return wasDeleted;
741
743
  }
742
744
  }
743
745
  /**
@@ -1996,7 +1998,7 @@ class DispatchResult {
1996
1998
  * Static helper which returns a successful DispatchResult
1997
1999
  */
1998
2000
  static get Success() {
1999
- return new DispatchResult();
2001
+ return SUCCESS;
2000
2002
  }
2001
2003
  get isSuccessful() {
2002
2004
  return this.reasons.length === 0;
@@ -2009,6 +2011,7 @@ class DispatchResult {
2009
2011
  return this.reasons.includes(reason);
2010
2012
  }
2011
2013
  }
2014
+ const SUCCESS = new DispatchResult();
2012
2015
  exports.CommandResult = void 0;
2013
2016
  (function (CommandResult) {
2014
2017
  CommandResult["Success"] = "Success";
@@ -2890,6 +2893,9 @@ function parseFormat(formatString) {
2890
2893
  function formatValue(value, { format, locale }) {
2891
2894
  switch (typeof value) {
2892
2895
  case "string":
2896
+ if (value.includes('\\"')) {
2897
+ return value.replace(/\\"/g, '"');
2898
+ }
2893
2899
  return value;
2894
2900
  case "boolean":
2895
2901
  return value ? "TRUE" : "FALSE";
@@ -4934,14 +4940,8 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
4934
4940
  : undefined));
4935
4941
  }
4936
4942
  }
4937
- else if (zone.left === zone.right && zone.top === zone.bottom) {
4938
- // A single cell. If it's only the title, the dataset is not added.
4939
- if (!dataSetsHaveTitle) {
4940
- dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
4941
- }
4942
- }
4943
4943
  else {
4944
- /* 1 row or 1 column */
4944
+ /* 1 cell, 1 row or 1 column */
4945
4945
  dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
4946
4946
  ? {
4947
4947
  top: zone.top,
@@ -4990,8 +4990,10 @@ function toExcelDataset(getters, ds) {
4990
4990
  }
4991
4991
  const dataRange = ds.dataRange.clone({ zone: dataZone });
4992
4992
  return {
4993
- label: ds.labelCell ? getters.getRangeString(ds.labelCell, "forceSheetReference") : undefined,
4994
- range: getters.getRangeString(dataRange, "forceSheetReference"),
4993
+ label: ds.labelCell
4994
+ ? getters.getRangeString(ds.labelCell, "forceSheetReference", { useFixedReference: true })
4995
+ : undefined,
4996
+ range: getters.getRangeString(dataRange, "forceSheetReference", { useFixedReference: true }),
4995
4997
  };
4996
4998
  }
4997
4999
  function toExcelLabelRange(getters, labelRange, shouldRemoveFirstLabel) {
@@ -5004,7 +5006,7 @@ function toExcelLabelRange(getters, labelRange, shouldRemoveFirstLabel) {
5004
5006
  zone.top = zone.top + 1;
5005
5007
  }
5006
5008
  const range = labelRange.clone({ zone });
5007
- return getters.getRangeString(range, "forceSheetReference");
5009
+ return getters.getRangeString(range, "forceSheetReference", { useFixedReference: true });
5008
5010
  }
5009
5011
  /**
5010
5012
  * Transform a chart definition which supports dataSets (dataSets and LabelRange)
@@ -7385,7 +7387,7 @@ function tokenize(str, locale = DEFAULT_LOCALE) {
7385
7387
  while (!chars.isOver()) {
7386
7388
  let token = tokenizeSpace(chars) ||
7387
7389
  tokenizeArgsSeparator(chars, locale) ||
7388
- tokenizeMisc(chars) ||
7390
+ tokenizeParenthesis(chars) ||
7389
7391
  tokenizeOperator(chars) ||
7390
7392
  tokenizeString(chars) ||
7391
7393
  tokenizeDebugger(chars) ||
@@ -7406,15 +7408,14 @@ function tokenizeDebugger(chars) {
7406
7408
  }
7407
7409
  return null;
7408
7410
  }
7409
- const misc$1 = {
7410
- "(": "LEFT_PAREN",
7411
- ")": "RIGHT_PAREN",
7411
+ const parenthesis = {
7412
+ "(": { type: "LEFT_PAREN", value: "(" },
7413
+ ")": { type: "RIGHT_PAREN", value: ")" },
7412
7414
  };
7413
- function tokenizeMisc(chars) {
7414
- if (chars.current in misc$1) {
7415
+ function tokenizeParenthesis(chars) {
7416
+ if (chars.current === "(" || chars.current === ")") {
7415
7417
  const value = chars.shift();
7416
- const type = misc$1[value];
7417
- return { type, value };
7418
+ return parenthesis[value];
7418
7419
  }
7419
7420
  return null;
7420
7421
  }
@@ -7435,7 +7436,12 @@ function tokenizeOperator(chars) {
7435
7436
  }
7436
7437
  return null;
7437
7438
  }
7439
+ const FIRST_POSSIBLE_NUMBER_CHARS = new Set("0123456789");
7438
7440
  function tokenizeNumber(chars, locale) {
7441
+ if (!FIRST_POSSIBLE_NUMBER_CHARS.has(chars.current) &&
7442
+ chars.current !== locale.decimalSeparator) {
7443
+ return null;
7444
+ }
7439
7445
  const match = chars.remaining().match(getFormulaNumberRegex(locale.decimalSeparator));
7440
7446
  if (match) {
7441
7447
  chars.advanceBy(match[0].length);
@@ -7460,7 +7466,7 @@ function tokenizeString(chars) {
7460
7466
  }
7461
7467
  return null;
7462
7468
  }
7463
- const separatorRegexp = /\w|\.|!|\$/;
7469
+ const separatorRegexp = /^[\w\.!\$]+/;
7464
7470
  /**
7465
7471
  * A "Symbol" is just basically any word-like element that can appear in a
7466
7472
  * formula, which is not a string. So:
@@ -7500,8 +7506,11 @@ function tokenizeSymbol(chars) {
7500
7506
  };
7501
7507
  }
7502
7508
  }
7503
- while (chars.current && separatorRegexp.test(chars.current)) {
7504
- result += chars.shift();
7509
+ const match = chars.remaining().match(separatorRegexp);
7510
+ if (match) {
7511
+ const value = match[0];
7512
+ result += value;
7513
+ chars.advanceBy(value.length);
7505
7514
  }
7506
7515
  if (result.length) {
7507
7516
  const value = result;
@@ -7563,7 +7572,10 @@ class TokenizingChars {
7563
7572
  return this.text.substring(this.currentIndex);
7564
7573
  }
7565
7574
  currentStartsWith(str) {
7566
- for (let j = 0; j < str.length; j++) {
7575
+ if (this.current !== str[0]) {
7576
+ return false;
7577
+ }
7578
+ for (let j = 1; j < str.length; j++) {
7567
7579
  if (this.text[this.currentIndex + j] !== str[j]) {
7568
7580
  return false;
7569
7581
  }
@@ -7590,6 +7602,9 @@ function isValidLocale(locale) {
7590
7602
  if (locale.formulaArgSeparator === locale.decimalSeparator) {
7591
7603
  return false;
7592
7604
  }
7605
+ if (locale.thousandsSeparator === locale.decimalSeparator) {
7606
+ return false;
7607
+ }
7593
7608
  try {
7594
7609
  formatValue(1, { locale, format: "#,##0.00" });
7595
7610
  formatValue(1, { locale, format: locale.dateFormat });
@@ -7680,7 +7695,7 @@ function canonicalizeNumberLiteral(content, locale) {
7680
7695
  if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
7681
7696
  return content;
7682
7697
  }
7683
- return content.replace(locale.decimalSeparator, ".");
7698
+ return content.replace(locale.thousandsSeparator, "").replace(locale.decimalSeparator, ".");
7684
7699
  }
7685
7700
  /**
7686
7701
  * Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
@@ -8594,7 +8609,7 @@ class BarChart extends AbstractChart {
8594
8609
  return undefined;
8595
8610
  const dataSets = this.dataSets
8596
8611
  .map((ds) => toExcelDataset(this.getters, ds))
8597
- .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
8612
+ .filter((ds) => ds.range !== "" && ds.range !== INCORRECT_RANGE_STRING);
8598
8613
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
8599
8614
  return {
8600
8615
  ...this.getDefinition(),
@@ -9172,7 +9187,7 @@ class LineChart extends AbstractChart {
9172
9187
  return undefined;
9173
9188
  const dataSets = this.dataSets
9174
9189
  .map((ds) => toExcelDataset(this.getters, ds))
9175
- .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
9190
+ .filter((ds) => ds.range !== "" && ds.range !== INCORRECT_RANGE_STRING);
9176
9191
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
9177
9192
  return {
9178
9193
  ...this.getDefinition(),
@@ -9478,7 +9493,7 @@ class PieChart extends AbstractChart {
9478
9493
  return undefined;
9479
9494
  const dataSets = this.dataSets
9480
9495
  .map((ds) => toExcelDataset(this.getters, ds))
9481
- .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
9496
+ .filter((ds) => ds.range !== "" && ds.range !== INCORRECT_RANGE_STRING);
9482
9497
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
9483
9498
  return {
9484
9499
  ...this.getDefinition(),
@@ -10752,6 +10767,12 @@ const INSERT_LINK = (env) => {
10752
10767
  let { col, row } = env.model.getters.getActivePosition();
10753
10768
  env.model.dispatch("OPEN_CELL_POPOVER", { col, row, popoverType: "LinkEditor" });
10754
10769
  };
10770
+ const INSERT_LINK_NAME = (env) => {
10771
+ const sheetId = env.model.getters.getActiveSheetId();
10772
+ const { col, row } = env.model.getters.getActivePosition();
10773
+ const cell = env.model.getters.getEvaluatedCell({ sheetId, col, row });
10774
+ return cell && cell.link ? _t("Edit link") : _t("Insert link");
10775
+ };
10755
10776
  //------------------------------------------------------------------------------
10756
10777
  // Filters action
10757
10778
  //------------------------------------------------------------------------------
@@ -19783,7 +19804,7 @@ cellMenuRegistry
19783
19804
  })
19784
19805
  .add("insert_link", {
19785
19806
  ...insertLink,
19786
- name: _t("Insert link"),
19807
+ name: INSERT_LINK_NAME,
19787
19808
  sequence: 150,
19788
19809
  separator: true,
19789
19810
  });
@@ -19921,19 +19942,20 @@ const formatNumberPercent = createFormatActionSpec({
19921
19942
  const formatNumberCurrency = createFormatActionSpec({
19922
19943
  name: _t("Currency"),
19923
19944
  descriptionValue: 1000.12,
19924
- format: (env) => env.model.config.defaultCurrencyFormat,
19945
+ format: (env) => env.model.config.defaultCurrencyFormat ?? DEFAULT_CURRENCY_FORMAT,
19925
19946
  });
19926
19947
  const formatNumberCurrencyRounded = {
19927
19948
  ...createFormatActionSpec({
19928
19949
  name: _t("Currency rounded"),
19929
19950
  descriptionValue: 1000,
19930
- format: (env) => roundFormat(env.model.config.defaultCurrencyFormat),
19951
+ format: (env) => roundFormat(env.model.config.defaultCurrencyFormat ?? DEFAULT_CURRENCY_FORMAT),
19931
19952
  }),
19932
19953
  isVisible: (env) => {
19933
19954
  const currencyFormat = env.model.config.defaultCurrencyFormat;
19934
- return currencyFormat !== roundFormat(currencyFormat);
19955
+ return currencyFormat !== roundFormat(currencyFormat ?? DEFAULT_CURRENCY_FORMAT);
19935
19956
  },
19936
19957
  };
19958
+ const DEFAULT_CURRENCY_FORMAT = "[$$]#,##0.00";
19937
19959
  const EXAMPLE_DATE = parseLiteral("2023/09/26 10:43:00 PM", DEFAULT_LOCALE);
19938
19960
  const formatCustomCurrency = {
19939
19961
  name: _t("Custom currency"),
@@ -26035,6 +26057,17 @@ css /* scss */ `
26035
26057
  `;
26036
26058
  class TextValueProvider extends owl.Component {
26037
26059
  static template = "o-spreadsheet-TextValueProvider";
26060
+ autoCompleteListRef = owl.useRef("autoCompleteList");
26061
+ setup() {
26062
+ owl.useEffect(() => {
26063
+ const selectedIndex = this.props.selectedIndex;
26064
+ if (selectedIndex === undefined) {
26065
+ return;
26066
+ }
26067
+ const selectedElement = this.autoCompleteListRef.el?.children[selectedIndex];
26068
+ selectedElement?.scrollIntoView?.({ block: "nearest" });
26069
+ }, () => [this.props.selectedIndex, this.autoCompleteListRef.el]);
26070
+ }
26038
26071
  }
26039
26072
  TextValueProvider.props = {
26040
26073
  values: Array,
@@ -26486,6 +26519,7 @@ css /* scss */ `
26486
26519
  position: absolute;
26487
26520
  margin: 1px 4px;
26488
26521
  pointer-events: none;
26522
+ overflow: auto;
26489
26523
 
26490
26524
  .o-semi-bold {
26491
26525
  /** FIXME: to remove in favor of Bootstrap
@@ -26501,6 +26535,7 @@ class Composer extends owl.Component {
26501
26535
  static components = { TextValueProvider, FunctionDescriptionProvider };
26502
26536
  static defaultProps = {
26503
26537
  inputStyle: "",
26538
+ isDefaultFocus: false,
26504
26539
  };
26505
26540
  composerRef = owl.useRef("o_composer");
26506
26541
  contentHelper = new ContentEditableHelper(this.composerRef.el);
@@ -26531,7 +26566,10 @@ class Composer extends owl.Component {
26531
26566
  if (this.props.delimitation && this.props.rect) {
26532
26567
  const { x: cellX, y: cellY, height: cellHeight } = this.props.rect;
26533
26568
  const remainingHeight = this.props.delimitation.height - (cellY + cellHeight);
26569
+ assistantStyle["max-height"] = `${remainingHeight}px`;
26534
26570
  if (cellY > remainingHeight) {
26571
+ const availableSpaceAbove = cellY;
26572
+ assistantStyle["max-height"] = `${availableSpaceAbove}px`;
26535
26573
  // render top
26536
26574
  // We compensate 2 px of margin on the assistant style + 1px for design reasons
26537
26575
  assistantStyle.top = `-3px`;
@@ -26557,7 +26595,6 @@ class Composer extends owl.Component {
26557
26595
  F2: () => console.warn("Not implemented"),
26558
26596
  F4: this.processF4Key,
26559
26597
  Tab: (ev) => this.processTabKey(ev),
26560
- " ": (ev) => this.processSpaceKey(ev),
26561
26598
  };
26562
26599
  keyCodeMapping = {
26563
26600
  NumpadDecimal: this.processNumpadDecimal,
@@ -26565,12 +26602,12 @@ class Composer extends owl.Component {
26565
26602
  setup() {
26566
26603
  owl.onMounted(() => {
26567
26604
  const el = this.composerRef.el;
26605
+ if (this.props.isDefaultFocus) {
26606
+ this.env.focusableElement.setFocusableElement(el);
26607
+ }
26568
26608
  this.contentHelper.updateEl(el);
26569
26609
  this.processTokenAtCursor();
26570
26610
  });
26571
- owl.onWillUnmount(() => {
26572
- this.props.onComposerUnmounted?.();
26573
- });
26574
26611
  owl.useEffect(() => {
26575
26612
  this.processContent();
26576
26613
  });
@@ -26579,7 +26616,8 @@ class Composer extends owl.Component {
26579
26616
  // Handlers
26580
26617
  // ---------------------------------------------------------------------------
26581
26618
  processArrowKeys(ev) {
26582
- if (this.env.model.getters.isSelectingForComposer()) {
26619
+ if (this.env.model.getters.isSelectingForComposer() ||
26620
+ this.env.model.getters.getEditionMode() === "inactive") {
26583
26621
  this.functionDescriptionState.showDescription = false;
26584
26622
  // Prevent the default content editable behavior which moves the cursor
26585
26623
  ev.preventDefault();
@@ -26622,26 +26660,20 @@ class Composer extends owl.Component {
26622
26660
  processTabKey(ev) {
26623
26661
  ev.preventDefault();
26624
26662
  ev.stopPropagation();
26625
- const state = this.autoCompleteState;
26626
- if (state.showProvider && state.selectedIndex !== undefined) {
26627
- const autoCompleteValue = this.autoCompleteState.values[state.selectedIndex]?.text;
26628
- if (autoCompleteValue) {
26629
- this.autoComplete(autoCompleteValue);
26630
- return;
26663
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
26664
+ const state = this.autoCompleteState;
26665
+ if (state.showProvider && state.selectedIndex !== undefined) {
26666
+ const autoCompleteValue = this.autoCompleteState.values[state.selectedIndex]?.text;
26667
+ if (autoCompleteValue) {
26668
+ this.autoComplete(autoCompleteValue);
26669
+ return;
26670
+ }
26631
26671
  }
26672
+ interactiveStopEdition(this.env);
26632
26673
  }
26633
26674
  const direction = ev.shiftKey ? "left" : "right";
26634
- interactiveStopEdition(this.env);
26635
26675
  this.env.model.selection.moveAnchorCell(direction, 1);
26636
26676
  }
26637
- processSpaceKey(ev) {
26638
- if (ev.ctrlKey) {
26639
- ev.preventDefault();
26640
- ev.stopPropagation();
26641
- this.showFunctionAutocomplete("");
26642
- this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
26643
- }
26644
- }
26645
26677
  processEnterKey(ev) {
26646
26678
  ev.preventDefault();
26647
26679
  ev.stopPropagation();
@@ -26694,6 +26726,9 @@ class Composer extends owl.Component {
26694
26726
  this.compositionActive = false;
26695
26727
  }
26696
26728
  onKeydown(ev) {
26729
+ if (this.env.model.getters.getEditionMode() === "inactive") {
26730
+ return;
26731
+ }
26697
26732
  let handler = this.keyMapping[ev.key] || this.keyCodeMapping[ev.code];
26698
26733
  if (handler) {
26699
26734
  handler.call(this, ev);
@@ -26702,14 +26737,33 @@ class Composer extends owl.Component {
26702
26737
  ev.stopPropagation();
26703
26738
  }
26704
26739
  }
26740
+ onPaste(ev) {
26741
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
26742
+ ev.stopPropagation();
26743
+ }
26744
+ }
26705
26745
  /*
26706
26746
  * Triggered automatically by the content-editable between the keydown and key up
26707
26747
  * */
26708
26748
  onInput(ev) {
26709
- if (this.props.focus === "inactive" || !this.shouldProcessInputEvents) {
26749
+ if (!this.shouldProcessInputEvents) {
26750
+ return;
26751
+ }
26752
+ if (ev.inputType === "insertFromPaste" &&
26753
+ this.env.model.getters.getEditionMode() === "inactive") {
26710
26754
  return;
26711
26755
  }
26712
- let content = this.contentHelper.getText();
26756
+ ev.stopPropagation();
26757
+ let content;
26758
+ if (this.env.model.getters.getEditionMode() === "inactive") {
26759
+ content = ev.data || "";
26760
+ }
26761
+ else {
26762
+ content = this.contentHelper.getText();
26763
+ }
26764
+ if (this.props.focus === "inactive") {
26765
+ return this.props.onComposerCellFocused?.(content);
26766
+ }
26713
26767
  let selection = this.contentHelper.getCurrentSelection();
26714
26768
  if (ev.inputType === "insertParagraph") {
26715
26769
  const start = Math.min(selection.start, selection.end);
@@ -26791,9 +26845,8 @@ class Composer extends owl.Component {
26791
26845
  }
26792
26846
  const newSelection = this.contentHelper.getCurrentSelection();
26793
26847
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
26794
- if (this.props.focus === "inactive") {
26795
- this.props.onComposerContentFocused(newSelection);
26796
- }
26848
+ this.props.onComposerContentFocused();
26849
+ if (this.props.focus === "inactive") ;
26797
26850
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", newSelection);
26798
26851
  this.processTokenAtCursor();
26799
26852
  }
@@ -26960,7 +27013,7 @@ class Composer extends owl.Component {
26960
27013
  const highlights = this.env.model.getters.getHighlights();
26961
27014
  const refSheet = sheetName
26962
27015
  ? this.env.model.getters.getSheetIdByName(sheetName)
26963
- : this.env.model.getters.getCurrentEditedCell().sheetId;
27016
+ : this.env.model.getters.getCurrentEditedCell()?.sheetId;
26964
27017
  const highlight = highlights.find((highlight) => {
26965
27018
  if (highlight.sheetId !== refSheet)
26966
27019
  return false;
@@ -27053,12 +27106,13 @@ class Composer extends owl.Component {
27053
27106
  }
27054
27107
  }
27055
27108
  Composer.props = {
27056
- focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27057
- onComposerContentFocused: Function,
27058
27109
  inputStyle: { type: String, optional: true },
27059
27110
  rect: { type: Object, optional: true },
27060
27111
  delimitation: { type: Object, optional: true },
27061
- onComposerUnmounted: { type: Function, optional: true },
27112
+ focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27113
+ onComposerCellFocused: { type: Function, optional: true },
27114
+ onComposerContentFocused: Function,
27115
+ isDefaultFocus: { type: Boolean, optional: true },
27062
27116
  };
27063
27117
 
27064
27118
  const COMPOSER_BORDER_WIDTH = 3 * 0.4 * window.devicePixelRatio || 1;
@@ -27092,51 +27146,25 @@ css /* scss */ `
27092
27146
  class GridComposer extends owl.Component {
27093
27147
  static template = "o-spreadsheet-GridComposer";
27094
27148
  static components = { Composer };
27095
- gridComposerRef;
27096
- zone;
27097
- rect;
27149
+ rect = this.defaultRect;
27150
+ isEditing = false;
27098
27151
  isCellReferenceVisible;
27099
- composerState;
27152
+ get defaultRect() {
27153
+ return { x: 0, y: 0, width: 0, height: 0 };
27154
+ }
27100
27155
  setup() {
27101
- this.gridComposerRef = owl.useRef("gridComposer");
27102
- this.composerState = owl.useState({
27103
- rect: undefined,
27104
- delimitation: undefined,
27105
- });
27106
- const { sheetId, col, row } = this.env.model.getters.getActivePosition();
27107
- this.zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row }));
27108
- this.rect = this.env.model.getters.getVisibleRect(this.zone);
27109
- this.isCellReferenceVisible = false;
27110
- owl.onMounted(() => {
27111
- const el = this.gridComposerRef.el;
27112
- this.composerState.rect = {
27113
- x: this.rect.x,
27114
- y: this.rect.y,
27115
- width: el.clientWidth,
27116
- height: el.clientHeight,
27117
- };
27118
- this.composerState.delimitation = {
27119
- width: this.props.gridDims.width,
27120
- height: this.props.gridDims.height,
27121
- };
27122
- });
27123
27156
  owl.onWillUpdateProps(() => {
27124
- if (this.isCellReferenceVisible) {
27125
- return;
27126
- }
27127
- const sheetId = this.env.model.getters.getActiveSheetId();
27128
- const zone = this.env.model.getters.getSelectedZone();
27129
- const rect = this.env.model.getters.getVisibleRect(zone);
27130
- if (!deepEquals(rect, this.rect) ||
27131
- sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
27132
- this.isCellReferenceVisible = true;
27133
- }
27157
+ this.updateComponentPosition();
27158
+ this.updateCellReferenceVisibility();
27134
27159
  });
27135
27160
  }
27136
27161
  get shouldDisplayCellReference() {
27137
27162
  return this.isCellReferenceVisible;
27138
27163
  }
27139
27164
  get cellReference() {
27165
+ if (!this.env.model.getters.getCurrentEditedCell()) {
27166
+ return "";
27167
+ }
27140
27168
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
27141
27169
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
27142
27170
  return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
@@ -27148,7 +27176,27 @@ class GridComposer extends owl.Component {
27148
27176
  top: `${top - GRID_CELL_REFERENCE_TOP_OFFSET}px`,
27149
27177
  });
27150
27178
  }
27179
+ get composerProps() {
27180
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
27181
+ return {
27182
+ rect: { ...this.rect },
27183
+ delimitation: {
27184
+ width,
27185
+ height,
27186
+ },
27187
+ focus: this.props.focus,
27188
+ isDefaultFocus: true,
27189
+ onComposerContentFocused: this.props.onComposerContentFocused,
27190
+ onComposerCellFocused: this.props.onComposerCellFocused,
27191
+ };
27192
+ }
27151
27193
  get containerStyle() {
27194
+ if (this.env.model.getters.getEditionMode() === "inactive" || !this.rect) {
27195
+ return `
27196
+ position: absolute;
27197
+ z-index: -1000;
27198
+ `;
27199
+ }
27152
27200
  const isFormula = this.env.model.getters.getCurrentContent().startsWith("=");
27153
27201
  const cell = this.env.model.getters.getActiveCell();
27154
27202
  const position = this.env.model.getters.getActivePosition();
@@ -27168,6 +27216,8 @@ class GridComposer extends owl.Component {
27168
27216
  if (!isFormula) {
27169
27217
  textAlign = style.align || cell.defaultAlign;
27170
27218
  }
27219
+ const maxHeight = this.props.gridDims.height - this.rect.y;
27220
+ const maxWidth = this.props.gridDims.width - this.rect.x;
27171
27221
  /**
27172
27222
  * min-size is on the container, not the composer element, because we want to have the same size as the cell by default,
27173
27223
  * including all the paddings/margins of the composer
@@ -27179,6 +27229,8 @@ class GridComposer extends owl.Component {
27179
27229
  top: `${top}px`,
27180
27230
  "min-width": `${width + 1}px`,
27181
27231
  "min-height": `${height + 1}px`,
27232
+ "max-width": `${maxWidth}px`,
27233
+ "max-height": `${maxHeight}px`,
27182
27234
  background,
27183
27235
  color,
27184
27236
  "font-size": `${fontSizeInPixels(fontSize)}px`,
@@ -27188,20 +27240,38 @@ class GridComposer extends owl.Component {
27188
27240
  "text-align": textAlign,
27189
27241
  });
27190
27242
  }
27191
- get composerStyle() {
27192
- const maxHeight = this.props.gridDims.height - this.rect.y;
27193
- const maxWidth = this.props.gridDims.width - this.rect.x;
27194
- return cssPropertiesToCss({
27195
- "max-width": `${maxWidth}px`,
27196
- "max-height": `${maxHeight}px`,
27197
- });
27243
+ updateComponentPosition() {
27244
+ const isEditing = this.env.model.getters.getEditionMode() !== "inactive";
27245
+ if (this.isEditing !== isEditing) {
27246
+ this.isEditing = isEditing;
27247
+ if (!isEditing) {
27248
+ this.rect = this.defaultRect;
27249
+ this.env.focusableElement.focus();
27250
+ return;
27251
+ }
27252
+ const position = this.env.model.getters.getActivePosition();
27253
+ const zone = this.env.model.getters.expandZone(position.sheetId, positionToZone(position));
27254
+ this.rect = this.env.model.getters.getVisibleRect(zone);
27255
+ }
27256
+ }
27257
+ updateCellReferenceVisibility() {
27258
+ if (this.isCellReferenceVisible || this.env.model.getters.getEditionMode() === "inactive") {
27259
+ return;
27260
+ }
27261
+ const sheetId = this.env.model.getters.getActiveSheetId();
27262
+ const zone = this.env.model.getters.getSelectedZone();
27263
+ const rect = this.env.model.getters.getVisibleRect(zone);
27264
+ if (!deepEquals(rect, this.rect) ||
27265
+ sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
27266
+ this.isCellReferenceVisible = true;
27267
+ }
27198
27268
  }
27199
27269
  }
27200
27270
  GridComposer.props = {
27201
27271
  focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27202
- onComposerUnmounted: Function,
27203
27272
  onComposerContentFocused: Function,
27204
27273
  gridDims: Object,
27274
+ onComposerCellFocused: Function,
27205
27275
  };
27206
27276
 
27207
27277
  const CSS$1 = css /* scss */ `
@@ -28292,7 +28362,6 @@ class GridOverlay extends owl.Component {
28292
28362
  this.props.onCellDoubleClicked(col, row);
28293
28363
  }
28294
28364
  onContextMenu(ev) {
28295
- ev.preventDefault();
28296
28365
  const [col, row] = this.getCartesianCoordinates(ev);
28297
28366
  this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY });
28298
28367
  }
@@ -29353,6 +29422,7 @@ const registries$1 = {
29353
29422
  GROUP_HEADERS: groupHeadersMenuRegistry,
29354
29423
  UNGROUP_HEADERS: unGroupHeadersMenuRegistry,
29355
29424
  };
29425
+ const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
29356
29426
  // -----------------------------------------------------------------------------
29357
29427
  // JS
29358
29428
  // -----------------------------------------------------------------------------
@@ -29376,7 +29446,6 @@ class Grid extends owl.Component {
29376
29446
  HEADER_WIDTH = HEADER_WIDTH;
29377
29447
  menuState;
29378
29448
  gridRef;
29379
- hiddenInput;
29380
29449
  onMouseWheel;
29381
29450
  canvasPosition;
29382
29451
  hoveredCell;
@@ -29387,17 +29456,16 @@ class Grid extends owl.Component {
29387
29456
  menuItems: [],
29388
29457
  });
29389
29458
  this.gridRef = owl.useRef("grid");
29390
- this.hiddenInput = owl.useRef("hiddenInput");
29391
29459
  this.canvasPosition = useAbsoluteBoundingRect(this.gridRef);
29392
29460
  this.hoveredCell = owl.useState({ col: undefined, row: undefined });
29393
29461
  owl.useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });
29394
29462
  owl.useExternalListener(document.body, "cut", this.copy.bind(this, true));
29395
29463
  owl.useExternalListener(document.body, "copy", this.copy.bind(this, false));
29396
29464
  owl.useExternalListener(document.body, "paste", this.paste);
29397
- owl.onMounted(() => this.focus());
29398
- this.props.exposeFocus(() => this.focus());
29465
+ owl.onMounted(() => this.focusDefaultElement());
29466
+ this.props.exposeFocus(() => this.focusDefaultElement());
29399
29467
  useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());
29400
- owl.useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]);
29468
+ owl.useEffect(() => this.focusDefaultElement(), () => [this.env.model.getters.getActiveSheetId()]);
29401
29469
  this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {
29402
29470
  this.moveCanvas(deltaX, deltaY);
29403
29471
  this.hoveredCell.col = undefined;
@@ -29420,7 +29488,7 @@ class Grid extends owl.Component {
29420
29488
  if (this.env.model.getters.hasOpenedPopover()) {
29421
29489
  this.closeOpenedPopover();
29422
29490
  }
29423
- this.focus();
29491
+ this.focusDefaultElement();
29424
29492
  }
29425
29493
  // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key
29426
29494
  // down itself
@@ -29599,10 +29667,10 @@ class Grid extends owl.Component {
29599
29667
  "ALT+SHIFT+ARROWUP": () => this.processHeaderGroupingKey("up"),
29600
29668
  "ALT+SHIFT+ARROWDOWN": () => this.processHeaderGroupingKey("down"),
29601
29669
  };
29602
- focus() {
29670
+ focusDefaultElement() {
29603
29671
  if (!this.env.model.getters.getSelectedFigureId() &&
29604
29672
  this.env.model.getters.getEditionMode() === "inactive") {
29605
- this.hiddenInput.el?.focus();
29673
+ this.env.focusableElement.focus();
29606
29674
  }
29607
29675
  }
29608
29676
  get gridEl() {
@@ -29725,16 +29793,15 @@ class Grid extends owl.Component {
29725
29793
  }
29726
29794
  }
29727
29795
  onKeydown(ev) {
29728
- const eventKey = ev.key.toUpperCase();
29729
29796
  let keyDownString = "";
29730
- if (isCtrlKey(ev) && eventKey !== "CTRL")
29731
- keyDownString += "CTRL+";
29732
- if (ev.metaKey)
29733
- keyDownString += "CTRL+";
29734
- if (ev.altKey && eventKey !== "ALT")
29735
- keyDownString += "ALT+";
29736
- if (ev.shiftKey && eventKey !== "SHIFT")
29737
- keyDownString += "SHIFT+";
29797
+ if (!MODIFIER_KEYS.includes(ev.key)) {
29798
+ if (isCtrlKey(ev))
29799
+ keyDownString += "CTRL+";
29800
+ if (ev.altKey)
29801
+ keyDownString += "ALT+";
29802
+ if (ev.shiftKey)
29803
+ keyDownString += "SHIFT+";
29804
+ }
29738
29805
  keyDownString += ev.key.toUpperCase();
29739
29806
  let handler = this.keyDownMapping[keyDownString];
29740
29807
  if (handler) {
@@ -29748,19 +29815,6 @@ class Grid extends owl.Component {
29748
29815
  return;
29749
29816
  }
29750
29817
  }
29751
- onInput(ev) {
29752
- // the user meant to paste in the sheet, not open the composer with the pasted content
29753
- if (!ev.isComposing && ev.inputType === "insertFromPaste") {
29754
- return;
29755
- }
29756
- if (ev.data) {
29757
- // if the user types a character on the grid, it means he wants to start composing the selected cell with that
29758
- // character
29759
- ev.preventDefault();
29760
- ev.stopPropagation();
29761
- this.props.onGridComposerCellFocused(ev.data);
29762
- }
29763
- }
29764
29818
  // ---------------------------------------------------------------------------
29765
29819
  // Context Menu
29766
29820
  // ---------------------------------------------------------------------------
@@ -29875,7 +29929,7 @@ class Grid extends owl.Component {
29875
29929
  }
29876
29930
  closeMenu() {
29877
29931
  this.menuState.isOpen = false;
29878
- this.focus();
29932
+ this.focusDefaultElement();
29879
29933
  }
29880
29934
  processHeaderGroupingKey(direction) {
29881
29935
  if (this.env.model.getters.getSelectedZones().length !== 1) {
@@ -33770,6 +33824,31 @@ const MIGRATIONS = [
33770
33824
  return data;
33771
33825
  },
33772
33826
  },
33827
+ {
33828
+ description: "Fix datafilter duplication",
33829
+ from: 12,
33830
+ to: 12.5,
33831
+ applyMigration(data) {
33832
+ for (let sheet of data.sheets || []) {
33833
+ let knownDataFilterZones = [];
33834
+ for (let filterTable of sheet.filterTables || []) {
33835
+ const zone = toZone(filterTable.range);
33836
+ // See commit message for the details
33837
+ const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
33838
+ if (intersectZoneIndex !== -1) {
33839
+ knownDataFilterZones[intersectZoneIndex] = zone;
33840
+ }
33841
+ else {
33842
+ knownDataFilterZones.push(zone);
33843
+ }
33844
+ }
33845
+ sheet.filterTables = knownDataFilterZones.map((zone) => ({
33846
+ range: zoneToXc(zone),
33847
+ }));
33848
+ }
33849
+ return data;
33850
+ },
33851
+ },
33773
33852
  {
33774
33853
  description: "Change Border description structure",
33775
33854
  from: 12,
@@ -34609,22 +34688,7 @@ class BordersPlugin extends CorePlugin {
34609
34688
  }
34610
34689
  }
34611
34690
  export(data) {
34612
- // Borders
34613
- let borderId = 0;
34614
34691
  const borders = {};
34615
- /**
34616
- * Get the id of the given border. If the border does not exist, it creates
34617
- * one.
34618
- */
34619
- function getBorderId(border) {
34620
- for (let [key, value] of Object.entries(borders)) {
34621
- if (deepEquals(value, border)) {
34622
- return parseInt(key, 10);
34623
- }
34624
- }
34625
- borders[++borderId] = border;
34626
- return borderId;
34627
- }
34628
34692
  for (let sheet of data.sheets) {
34629
34693
  for (let col = 0; col < sheet.colNumber; col++) {
34630
34694
  for (let row = 0; row < sheet.rowNumber; row++) {
@@ -34632,7 +34696,7 @@ class BordersPlugin extends CorePlugin {
34632
34696
  if (border) {
34633
34697
  const xc = toXC(col, row);
34634
34698
  const cell = sheet.cells[xc];
34635
- const borderId = getBorderId(border);
34699
+ const borderId = getItemId(border, borders);
34636
34700
  if (cell) {
34637
34701
  cell.border = borderId;
34638
34702
  }
@@ -35437,9 +35501,9 @@ class CellPlugin extends CorePlugin {
35437
35501
  allowDispatch(cmd) {
35438
35502
  switch (cmd.type) {
35439
35503
  case "UPDATE_CELL":
35440
- return this.checkCellOutOfSheet(cmd);
35504
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
35441
35505
  case "CLEAR_CELL":
35442
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
35506
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
35443
35507
  default:
35444
35508
  return "Success" /* CommandResult.Success */;
35445
35509
  }
@@ -35589,8 +35653,8 @@ class CellPlugin extends CorePlugin {
35589
35653
  }
35590
35654
  removeDefaultStyleValues(style) {
35591
35655
  const cleanedStyle = { ...style };
35592
- for (const [property, defaultValue] of Object.entries(DEFAULT_STYLE)) {
35593
- if (cleanedStyle[property] === defaultValue) {
35656
+ for (const property in DEFAULT_STYLE) {
35657
+ if (cleanedStyle[property] === DEFAULT_STYLE[property]) {
35594
35658
  delete cleanedStyle[property];
35595
35659
  }
35596
35660
  }
@@ -35615,13 +35679,13 @@ class CellPlugin extends CorePlugin {
35615
35679
  /*
35616
35680
  * Reconstructs the original formula string based on a normalized form and its dependencies
35617
35681
  */
35618
- getFormulaCellContent(sheetId, compiledFormula, dependencies) {
35682
+ getFormulaCellContent(sheetId, compiledFormula, dependencies, useFixedReference = false) {
35619
35683
  const ranges = dependencies || compiledFormula.dependencies;
35620
35684
  let rangeIndex = 0;
35621
35685
  return concat(compiledFormula.tokens.map((token) => {
35622
35686
  if (token.type === "REFERENCE") {
35623
35687
  const range = ranges[rangeIndex++];
35624
- return this.getters.getRangeString(range, sheetId);
35688
+ return this.getters.getRangeString(range, sheetId, { useFixedReference });
35625
35689
  }
35626
35690
  return token.value;
35627
35691
  }));
@@ -35747,10 +35811,7 @@ class CellPlugin extends CorePlugin {
35747
35811
  else {
35748
35812
  style = before ? before.style : undefined;
35749
35813
  }
35750
- const locale = this.getters.getLocale();
35751
- let format = ("format" in after ? after.format : before && before.format) ||
35752
- detectDateFormat(afterContent, locale) ||
35753
- detectNumberFormat(afterContent);
35814
+ const format = "format" in after ? after.format : before && before.format;
35754
35815
  /* Read the following IF as:
35755
35816
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
35756
35817
  * - the command says the new content is empty and has no border/format/style
@@ -35791,12 +35852,11 @@ class CellPlugin extends CorePlugin {
35791
35852
  }
35792
35853
  createLiteralCell(id, content, format, style) {
35793
35854
  const locale = this.getters.getLocale();
35794
- content = parseLiteral(content, locale).toString();
35795
35855
  return {
35796
35856
  id,
35797
- content,
35857
+ content: parseLiteral(content, locale).toString(),
35798
35858
  style,
35799
- format,
35859
+ format: format || detectDateFormat(content, locale) || detectNumberFormat(content),
35800
35860
  isFormula: false,
35801
35861
  };
35802
35862
  }
@@ -35822,8 +35882,11 @@ class CellPlugin extends CorePlugin {
35822
35882
  * being a computed property to rebuild the dependencies XC.
35823
35883
  */
35824
35884
  createFormulaCellWithDependencies(id, compiledFormula, format, style, sheetId) {
35825
- const dependencies = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));
35826
- return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString.bind(this));
35885
+ const dependencies = [];
35886
+ for (const xc of compiledFormula.dependencies) {
35887
+ dependencies.push(this.getters.getRangeFromSheetXC(sheetId, xc));
35888
+ }
35889
+ return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString);
35827
35890
  }
35828
35891
  createErrorFormula(id, content, format, style, error) {
35829
35892
  return {
@@ -35858,6 +35921,18 @@ class CellPlugin extends CorePlugin {
35858
35921
  }
35859
35922
  return "Success" /* CommandResult.Success */;
35860
35923
  }
35924
+ checkUselessUpdateCell(cmd) {
35925
+ const cell = this.getters.getCell(cmd);
35926
+ const hasContent = "content" in cmd || "formula" in cmd;
35927
+ const hasStyle = "style" in cmd;
35928
+ const hasFormat = "format" in cmd;
35929
+ if ((!hasContent || cell?.content === cmd.content) &&
35930
+ (!hasStyle || deepEquals(cell?.style, cmd.style)) &&
35931
+ (!hasFormat || cell?.format === cmd.format)) {
35932
+ return "NoChanges" /* CommandResult.NoChanges */;
35933
+ }
35934
+ return "Success" /* CommandResult.Success */;
35935
+ }
35861
35936
  }
35862
35937
  class FormulaCellWithDependencies {
35863
35938
  id;
@@ -35877,7 +35952,7 @@ class FormulaCellWithDependencies {
35877
35952
  const tokens = compiledFormula.tokens.map((token) => {
35878
35953
  if (token.type === "REFERENCE") {
35879
35954
  const index = rangeIndex++;
35880
- return new RangeReferenceToken(() => this.getRangeString(dependencies[index], this.sheetId));
35955
+ return new RangeReferenceToken(dependencies, index, this.sheetId, this.getRangeString);
35881
35956
  }
35882
35957
  return token;
35883
35958
  });
@@ -35890,15 +35965,34 @@ class FormulaCellWithDependencies {
35890
35965
  get content() {
35891
35966
  return concat(this.compiledFormula.tokens.map((token) => token.value));
35892
35967
  }
35968
+ get contentWithFixedReferences() {
35969
+ let rangeIndex = 0;
35970
+ return concat(this.compiledFormula.tokens.map((token) => {
35971
+ if (token.type === "REFERENCE") {
35972
+ const index = rangeIndex++;
35973
+ return this.getRangeString(this.compiledFormula.dependencies[index], this.sheetId, {
35974
+ useFixedReference: true,
35975
+ });
35976
+ }
35977
+ return token.value;
35978
+ }));
35979
+ }
35893
35980
  }
35894
35981
  class RangeReferenceToken {
35895
- getValue;
35982
+ ranges;
35983
+ rangeIndex;
35984
+ sheetId;
35985
+ getRangeString;
35896
35986
  type = "REFERENCE";
35897
- constructor(getValue) {
35898
- this.getValue = getValue;
35987
+ constructor(ranges, rangeIndex, sheetId, getRangeString) {
35988
+ this.ranges = ranges;
35989
+ this.rangeIndex = rangeIndex;
35990
+ this.sheetId = sheetId;
35991
+ this.getRangeString = getRangeString;
35899
35992
  }
35900
35993
  get value() {
35901
- return this.getValue();
35994
+ const range = this.ranges[this.rangeIndex];
35995
+ return this.getRangeString(range, this.sheetId);
35902
35996
  }
35903
35997
  }
35904
35998
 
@@ -36194,7 +36288,13 @@ class ConditionalFormatPlugin extends CorePlugin {
36194
36288
  }
36195
36289
  }
36196
36290
  exportForExcel(data) {
36197
- this.export(data);
36291
+ if (data.sheets) {
36292
+ for (let sheet of data.sheets) {
36293
+ if (this.cfRules[sheet.id]) {
36294
+ sheet.conditionalFormats = this.cfRules[sheet.id].map((rule) => this.mapToConditionalFormat(sheet.id, rule, { useFixedReference: true }));
36295
+ }
36296
+ }
36297
+ }
36198
36298
  }
36199
36299
  // ---------------------------------------------------------------------------
36200
36300
  // Getters
@@ -36259,11 +36359,11 @@ class ConditionalFormatPlugin extends CorePlugin {
36259
36359
  // ---------------------------------------------------------------------------
36260
36360
  // Private
36261
36361
  // ---------------------------------------------------------------------------
36262
- mapToConditionalFormat(sheetId, cf) {
36362
+ mapToConditionalFormat(sheetId, cf, { useFixedReference } = { useFixedReference: false }) {
36263
36363
  return {
36264
36364
  ...cf,
36265
36365
  ranges: cf.ranges.map((range) => {
36266
- return this.getters.getRangeString(range, sheetId);
36366
+ return this.getters.getRangeString(range, sheetId, { useFixedReference });
36267
36367
  }),
36268
36368
  };
36269
36369
  }
@@ -38331,8 +38431,10 @@ class RangeAdapter {
38331
38431
  *
38332
38432
  * @param range the range (received from getRangeFromXC or getRangeFromZone)
38333
38433
  * @param forSheetId the id of the sheet where the range string is supposed to be used.
38434
+ * @param options
38435
+ * @param options.useFixedReference if true, the range will be returned with fixed row and column
38334
38436
  */
38335
- getRangeString(range, forSheetId) {
38437
+ getRangeString(range, forSheetId, options = { useFixedReference: false }) {
38336
38438
  if (!range) {
38337
38439
  return INCORRECT_RANGE_STRING;
38338
38440
  }
@@ -38359,7 +38461,7 @@ class RangeAdapter {
38359
38461
  if (prefixSheet && !sheetName) {
38360
38462
  return INCORRECT_RANGE_STRING;
38361
38463
  }
38362
- let rangeString = this.getRangePartString(rangeImpl, 0);
38464
+ let rangeString = this.getRangePartString(rangeImpl, 0, options);
38363
38465
  if (rangeImpl.parts && rangeImpl.parts.length === 2) {
38364
38466
  // this if converts A2:A2 into A2 except if any part of the original range had fixed row or column (with $)
38365
38467
  if (rangeImpl.zone.top !== rangeImpl.zone.bottom ||
@@ -38369,7 +38471,7 @@ class RangeAdapter {
38369
38471
  rangeImpl.parts[1].rowFixed ||
38370
38472
  rangeImpl.parts[1].colFixed) {
38371
38473
  rangeString += ":";
38372
- rangeString += this.getRangePartString(rangeImpl, 1);
38474
+ rangeString += this.getRangePartString(rangeImpl, 1, options);
38373
38475
  }
38374
38476
  }
38375
38477
  return `${prefixSheet ? sheetName + "!" : ""}${rangeString}`;
@@ -38410,13 +38512,13 @@ class RangeAdapter {
38410
38512
  /**
38411
38513
  * Get a Xc string that represent a part of a range
38412
38514
  */
38413
- getRangePartString(range, part) {
38515
+ getRangePartString(range, part, options = { useFixedReference: false }) {
38414
38516
  const colFixed = range.parts && range.parts[part]?.colFixed ? "$" : "";
38415
38517
  const col = part === 0 ? numberToLetters(range.zone.left) : numberToLetters(range.zone.right);
38416
38518
  const rowFixed = range.parts && range.parts[part]?.rowFixed ? "$" : "";
38417
38519
  const row = part === 0 ? String(range.zone.top + 1) : String(range.zone.bottom + 1);
38418
38520
  let str = "";
38419
- if (range.isFullCol) {
38521
+ if (range.isFullCol && !options.useFixedReference) {
38420
38522
  if (part === 0 && range.unboundedZone.hasHeader) {
38421
38523
  str = colFixed + col + rowFixed + row;
38422
38524
  }
@@ -38424,7 +38526,7 @@ class RangeAdapter {
38424
38526
  str = colFixed + col;
38425
38527
  }
38426
38528
  }
38427
- else if (range.isFullRow) {
38529
+ else if (range.isFullRow && !options.useFixedReference) {
38428
38530
  if (part === 0 && range.unboundedZone.hasHeader) {
38429
38531
  str = colFixed + col + rowFixed + row;
38430
38532
  }
@@ -40626,7 +40728,15 @@ class SpreadsheetRTree {
40626
40728
  if (!this.rTrees[sheetId]) {
40627
40729
  return;
40628
40730
  }
40629
- this.rTrees[sheetId].remove(item, deepEquals);
40731
+ this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
40732
+ }
40733
+ rtreeItemComparer(left, right) {
40734
+ return (left.data == right.data &&
40735
+ left.boundingBox.sheetId === right.boundingBox.sheetId &&
40736
+ left.boundingBox?.zone.left === right.boundingBox.zone.left &&
40737
+ left.boundingBox?.zone.top === right.boundingBox.zone.top &&
40738
+ left.boundingBox?.zone.right === right.boundingBox.zone.right &&
40739
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
40630
40740
  }
40631
40741
  }
40632
40742
  /**
@@ -40704,7 +40814,7 @@ class FormulaDependencyGraph {
40704
40814
  const queue = Array.from(ranges).reverse();
40705
40815
  while (queue.length > 0) {
40706
40816
  const range = queue.pop();
40707
- visited.add(...this.encoder.encodeBoundingBox(range));
40817
+ visited.addMany(this.encoder.encodeBoundingBox(range));
40708
40818
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
40709
40819
  for (const positionId of impactedPositionIds) {
40710
40820
  if (!visited.has(positionId)) {
@@ -40712,7 +40822,7 @@ class FormulaDependencyGraph {
40712
40822
  }
40713
40823
  }
40714
40824
  }
40715
- visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
40825
+ visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
40716
40826
  return visited;
40717
40827
  }
40718
40828
  }
@@ -40850,9 +40960,9 @@ class Evaluator {
40850
40960
  const cells = positions.map((p) => this.encoder.encode(p));
40851
40961
  const cellsToCompute = new JetSet(cells);
40852
40962
  const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
40853
- cellsToCompute.add(...this.getCellsDependingOn(cells));
40854
- cellsToCompute.add(...arrayFormulasPositionIds);
40855
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
40963
+ cellsToCompute.addMany(this.getCellsDependingOn(cells));
40964
+ cellsToCompute.addMany(arrayFormulasPositionIds);
40965
+ cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositionIds));
40856
40966
  this.evaluate(cellsToCompute);
40857
40967
  }
40858
40968
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -40866,7 +40976,7 @@ class Evaluator {
40866
40976
  }
40867
40977
  if (!content) {
40868
40978
  // The previous content could have blocked some array formulas
40869
- impactedPositionIds.add(...this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
40979
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
40870
40980
  }
40871
40981
  }
40872
40982
  return impactedPositionIds;
@@ -40915,7 +41025,7 @@ class Evaluator {
40915
41025
  }
40916
41026
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
40917
41027
  const cells = new JetSet(arrayFormulas);
40918
- cells.add(...this.getCellsDependingOn(arrayFormulas));
41028
+ cells.addMany(this.getCellsDependingOn(arrayFormulas));
40919
41029
  return cells;
40920
41030
  }
40921
41031
  nextPositionsToUpdate = new JetSet();
@@ -41064,7 +41174,7 @@ class Evaluator {
41064
41174
  this.setEvaluatedCell(positionId, evaluatedCell);
41065
41175
  // check if formula dependencies present in the spread zone
41066
41176
  // if so, they need to be recomputed
41067
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([positionId]));
41177
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([positionId]));
41068
41178
  };
41069
41179
  }
41070
41180
  invalidateSpreading(positionId) {
@@ -41079,8 +41189,8 @@ class Evaluator {
41079
41189
  continue;
41080
41190
  }
41081
41191
  this.evaluatedCells.delete(child);
41082
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([child]));
41083
- this.nextPositionsToUpdate.add(...this.getArrayFormulasBlockedByOrSpreadingOn(child));
41192
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
41193
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
41084
41194
  }
41085
41195
  this.spreadingRelations.removeNode(positionId);
41086
41196
  }
@@ -41429,7 +41539,13 @@ class EvaluationPlugin extends UIPlugin {
41429
41539
  const format = newFormat
41430
41540
  ? getItemId(newFormat, data.formats)
41431
41541
  : exportedCellData.format;
41432
- const content = !isExported ? newContent : exportedCellData.content;
41542
+ let content;
41543
+ if (formulaCell instanceof FormulaCellWithDependencies) {
41544
+ content = this.getters.getFormulaCellContent(exportedSheetData.id, formulaCell.compiledFormula, formulaCell.compiledFormula.dependencies, true);
41545
+ }
41546
+ else {
41547
+ content = !isExported ? newContent : exportedCellData.content;
41548
+ }
41433
41549
  exportedSheetData.cells[xc] = { ...exportedCellData, value, isFormula, content, format };
41434
41550
  }
41435
41551
  }
@@ -50745,6 +50861,16 @@ class ImageProvider {
50745
50861
  }
50746
50862
  }
50747
50863
 
50864
+ class FocusableElement {
50865
+ focusableElement = undefined;
50866
+ setFocusableElement(element) {
50867
+ this.focusableElement = element;
50868
+ }
50869
+ focus() {
50870
+ this.focusableElement?.focus();
50871
+ }
50872
+ }
50873
+
50748
50874
  const RIPPLE_KEY_FRAMES = [
50749
50875
  { transform: "scale(0)" },
50750
50876
  { transform: "scale(0.8)", offset: 0.33 },
@@ -52901,6 +53027,7 @@ class Spreadsheet extends owl.Component {
52901
53027
  toggleSidePanel: this.toggleSidePanel.bind(this),
52902
53028
  clipboard: this.env.clipboard || instantiateClipboard(),
52903
53029
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
53030
+ focusableElement: new FocusableElement(),
52904
53031
  });
52905
53032
  owl.useExternalListener(window, "resize", () => this.render(true));
52906
53033
  owl.useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
@@ -53007,16 +53134,17 @@ class Spreadsheet extends owl.Component {
53007
53134
  }
53008
53135
  this.composer.topBarFocus = "contentFocus";
53009
53136
  this.composer.gridFocusMode = "inactive";
53010
- this.setComposerContent({ selection } || {});
53137
+ this.setComposerContent({ selection });
53011
53138
  }
53012
- onGridComposerContentFocused() {
53139
+ onGridComposerContentFocused(selection) {
53013
53140
  if (this.model.getters.isReadonly()) {
53014
53141
  return;
53015
53142
  }
53016
53143
  this.composer.topBarFocus = "inactive";
53017
53144
  this.composer.gridFocusMode = "contentFocus";
53018
- this.setComposerContent({});
53145
+ this.setComposerContent({ selection });
53019
53146
  }
53147
+ // TODO: either both are defined or none of them. change those args to an object
53020
53148
  onGridComposerCellFocused(content, selection) {
53021
53149
  if (this.model.getters.isReadonly()) {
53022
53150
  return;
@@ -54442,11 +54570,10 @@ class SelectionStreamProcessorImpl {
54442
54570
  * next cluster if the given cell is outside a cluster or at the border of a cluster in the given direction.
54443
54571
  */
54444
54572
  getEndOfCluster(startPosition, dim, dir) {
54445
- const sheet = this.getters.getActiveSheet();
54446
54573
  let currentPosition = startPosition;
54447
54574
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
54448
54575
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
54449
- let mode = !this.isCellEmpty(currentPosition, sheet.id) && !this.isCellEmpty(nextCellPosition, sheet.id)
54576
+ let mode = !this.isEvaluatedCellEmpty(currentPosition) && !this.isEvaluatedCellEmpty(nextCellPosition)
54450
54577
  ? "endOfCluster"
54451
54578
  : "nextCluster";
54452
54579
  while (true) {
@@ -54456,7 +54583,7 @@ class SelectionStreamProcessorImpl {
54456
54583
  currentPosition.row === nextCellPosition.row) {
54457
54584
  break;
54458
54585
  }
54459
- const isNextCellEmpty = this.isCellEmpty(nextCellPosition, sheet.id);
54586
+ const isNextCellEmpty = this.isEvaluatedCellEmpty(nextCellPosition);
54460
54587
  if (mode === "endOfCluster" && isNextCellEmpty) {
54461
54588
  break;
54462
54589
  }
@@ -54470,10 +54597,20 @@ class SelectionStreamProcessorImpl {
54470
54597
  return dim === "cols" ? currentPosition.col : currentPosition.row;
54471
54598
  }
54472
54599
  /**
54473
- * Check if a cell is empty or undefined in the model. If the cell is part of a merge,
54474
- * check if the merge containing the cell is empty.
54600
+ * Checks if a cell is empty (i.e. does not have a content). If the cell is part of a merge,
54601
+ * the check applies to the main cell of the merge.
54475
54602
  */
54476
- isCellEmpty({ col, row }, sheetId = this.getters.getActiveSheetId()) {
54603
+ isCellEmpty({ col, row }) {
54604
+ const sheetId = this.getters.getActiveSheetId();
54605
+ const position = this.getters.getMainCellPosition({ sheetId, col, row });
54606
+ return !(this.getters.getCorrespondingFormulaCell(position) || this.getters.getCell(position)?.content);
54607
+ }
54608
+ /**
54609
+ * Checks if a cell evaluated value is empty. If the cell is part of a merge,
54610
+ * the check applies to the main cell of the merge.
54611
+ */
54612
+ isEvaluatedCellEmpty({ col, row }) {
54613
+ const sheetId = this.getters.getActiveSheetId();
54477
54614
  const position = this.getters.getMainCellPosition({ sheetId, col, row });
54478
54615
  const cell = this.getters.getEvaluatedCell(position);
54479
54616
  return cell.type === CellValueType.empty;
@@ -54951,12 +55088,7 @@ function addFormula(cell) {
54951
55088
  attrs.push(["t", "str"]);
54952
55089
  cycle = escapeXml /*xml*/ `<v>${cell.value}</v>`;
54953
55090
  }
54954
- node = escapeXml /*xml*/ `
54955
- <f>
54956
- ${XlsxFormula}
54957
- </f>
54958
- ${cycle}
54959
- `;
55091
+ node = escapeXml /*xml*/ `<f>${XlsxFormula}</f>${cycle}`;
54960
55092
  return { attrs, node };
54961
55093
  }
54962
55094
  function addContent(content, sharedStrings, forceString = false) {
@@ -55775,11 +55907,10 @@ function addRows(construct, data, sheet) {
55775
55907
  ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader));
55776
55908
  }
55777
55909
  attributes.push(...additionalAttrs);
55778
- cellNodes.push(escapeXml /*xml*/ `
55779
- <c ${formatAttributes(attributes)}>
55780
- ${cellNode}
55781
- </c>
55782
- `);
55910
+ // prettier-ignore
55911
+ cellNodes.push(escapeXml /*xml*/ `<c ${formatAttributes(attributes)}>
55912
+ ${cellNode}
55913
+ </c>`);
55783
55914
  }
55784
55915
  }
55785
55916
  if (cellNodes.length || row.size !== DEFAULT_CELL_HEIGHT || row.isHidden) {
@@ -56375,7 +56506,6 @@ class Model extends EventBus {
56375
56506
  ...config,
56376
56507
  mode: config.mode || "normal",
56377
56508
  custom: config.custom || {},
56378
- defaultCurrencyFormat: config.defaultCurrencyFormat || "[$$]#,##0.00",
56379
56509
  external: this.setupExternalConfig(config.external || {}),
56380
56510
  transportService,
56381
56511
  client,
@@ -56413,6 +56543,7 @@ class Model extends EventBus {
56413
56543
  custom: this.config.custom,
56414
56544
  uiActions: this.config,
56415
56545
  session: this.session,
56546
+ defaultCurrencyFormat: this.config.defaultCurrencyFormat,
56416
56547
  };
56417
56548
  }
56418
56549
  // ---------------------------------------------------------------------------
@@ -56785,6 +56916,6 @@ exports.setTranslationMethod = setTranslationMethod;
56785
56916
  exports.tokenize = tokenize;
56786
56917
 
56787
56918
 
56788
- __info__.version = "17.0.9";
56789
- __info__.date = "2024-01-29T13:53:22.963Z";
56790
- __info__.hash = "3c1209a";
56919
+ __info__.version = "17.0.12";
56920
+ __info__.date = "2024-02-16T14:24:02.984Z";
56921
+ __info__.hash = "8fa2bab";