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