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