@odoo/o-spreadsheet 16.4.16 → 16.4.18

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.
@@ -1167,16 +1167,81 @@
1167
1167
  }
1168
1168
 
1169
1169
  // -----------------------------------------------------------------------------
1170
+ /**
1171
+ * A DateTime object that can be used to manipulate spreadsheet dates.
1172
+ * Conceptually, a spreadsheet date is simply a number with a date format,
1173
+ * and it is timezone-agnostic.
1174
+ * This DateTime object consistently uses UTC time to represent a naive date and time.
1175
+ */
1176
+ class DateTime {
1177
+ jsDate;
1178
+ constructor(year, month, day, hours = 0, minutes = 0, seconds = 0) {
1179
+ this.jsDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds, 0));
1180
+ }
1181
+ static fromTimestamp(timestamp) {
1182
+ const date = new Date(timestamp);
1183
+ return new DateTime(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
1184
+ }
1185
+ static now() {
1186
+ const now = new Date();
1187
+ return new DateTime(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds());
1188
+ }
1189
+ toString() {
1190
+ return this.jsDate.toString();
1191
+ }
1192
+ toLocaleDateString() {
1193
+ return this.jsDate.toLocaleDateString();
1194
+ }
1195
+ getTime() {
1196
+ return this.jsDate.getTime();
1197
+ }
1198
+ getFullYear() {
1199
+ return this.jsDate.getUTCFullYear();
1200
+ }
1201
+ getMonth() {
1202
+ return this.jsDate.getUTCMonth();
1203
+ }
1204
+ getDate() {
1205
+ return this.jsDate.getUTCDate();
1206
+ }
1207
+ getDay() {
1208
+ return this.jsDate.getUTCDay();
1209
+ }
1210
+ getHours() {
1211
+ return this.jsDate.getUTCHours();
1212
+ }
1213
+ getMinutes() {
1214
+ return this.jsDate.getUTCMinutes();
1215
+ }
1216
+ getSeconds() {
1217
+ return this.jsDate.getUTCSeconds();
1218
+ }
1219
+ setFullYear(year) {
1220
+ this.jsDate.setFullYear(year);
1221
+ }
1222
+ setDate(date) {
1223
+ this.jsDate.setUTCDate(date);
1224
+ }
1225
+ setHours(hours) {
1226
+ this.jsDate.setUTCHours(hours);
1227
+ }
1228
+ setMinutes(minutes) {
1229
+ this.jsDate.setUTCMinutes(minutes);
1230
+ }
1231
+ setSeconds(seconds) {
1232
+ this.jsDate.setUTCSeconds(seconds);
1233
+ }
1234
+ }
1170
1235
  // -----------------------------------------------------------------------------
1171
1236
  // Parsing
1172
1237
  // -----------------------------------------------------------------------------
1173
- const INITIAL_1900_DAY = new Date(1899, 11, 30);
1238
+ const INITIAL_1900_DAY = new DateTime(1899, 11, 30);
1174
1239
  const MS_PER_DAY = 24 * 60 * 60 * 1000;
1175
1240
  const CURRENT_MILLENIAL = 2000; // note: don't forget to update this in 2999
1176
- const CURRENT_YEAR = new Date().getFullYear();
1177
- const CURRENT_MONTH = new Date().getMonth();
1178
- const INITIAL_JS_DAY = new Date(0);
1179
- const DATE_JS_1900_OFFSET = INITIAL_JS_DAY - INITIAL_1900_DAY;
1241
+ const CURRENT_YEAR = DateTime.now().getFullYear();
1242
+ const CURRENT_MONTH = DateTime.now().getMonth();
1243
+ const INITIAL_JS_DAY = DateTime.fromTimestamp(0);
1244
+ const DATE_JS_1900_OFFSET = INITIAL_JS_DAY.getTime() - INITIAL_1900_DAY.getTime();
1180
1245
  const mdyDateRegexp = /^\d{1,2}(\/|-|\s)\d{1,2}((\/|-|\s)\d{1,4})?$/;
1181
1246
  const ymdDateRegexp = /^\d{3,4}(\/|-|\s)\d{1,2}(\/|-|\s)\d{1,2}$/;
1182
1247
  const dateSeparatorsRegex = /\/|-|\s/;
@@ -1223,7 +1288,7 @@
1223
1288
  return {
1224
1289
  value: date.value + time.value,
1225
1290
  format: date.format + " " + (time.format === "hhhh:mm:ss" ? "hh:mm:ss" : time.format),
1226
- jsDate: new Date(date.jsDate.getFullYear() + time.jsDate.getFullYear() - 1899, date.jsDate.getMonth() + time.jsDate.getMonth() - 11, date.jsDate.getDate() + time.jsDate.getDate() - 30, date.jsDate.getHours() + time.jsDate.getHours(), date.jsDate.getMinutes() + time.jsDate.getMinutes(), date.jsDate.getSeconds() + time.jsDate.getSeconds()),
1291
+ jsDate: new DateTime(date.jsDate.getFullYear() + time.jsDate.getFullYear() - 1899, date.jsDate.getMonth() + time.jsDate.getMonth() - 11, date.jsDate.getDate() + time.jsDate.getDate() - 30, date.jsDate.getHours() + time.jsDate.getHours(), date.jsDate.getMinutes() + time.jsDate.getMinutes(), date.jsDate.getSeconds() + time.jsDate.getSeconds()),
1227
1292
  };
1228
1293
  }
1229
1294
  return date || time;
@@ -1295,12 +1360,12 @@
1295
1360
  // month + 1: months are 0-indexed in JS
1296
1361
  const leadingZero = (monthStr?.length === 2 && month + 1 < 10) || (dayStr?.length === 2 && day < 10);
1297
1362
  const fullYear = yearStr?.length !== 2;
1298
- const jsDate = new Date(year, month, day);
1363
+ const jsDate = new DateTime(year, month, day);
1299
1364
  if (jsDate.getMonth() !== month || jsDate.getDate() !== day) {
1300
1365
  // invalid date
1301
1366
  return null;
1302
1367
  }
1303
- const delta = jsDate - INITIAL_1900_DAY;
1368
+ const delta = jsDate.getTime() - INITIAL_1900_DAY.getTime();
1304
1369
  const format = getFormatFromDateParts(parts, separator, leadingZero, fullYear);
1305
1370
  return {
1306
1371
  value: Math.round(delta / MS_PER_DAY),
@@ -1391,7 +1456,7 @@
1391
1456
  if (hours >= 24) {
1392
1457
  format = "hhhh:mm:ss";
1393
1458
  }
1394
- const jsDate = new Date(1899, 11, 30, hours, minutes, seconds);
1459
+ const jsDate = new DateTime(1899, 11, 30, hours, minutes, seconds);
1395
1460
  return {
1396
1461
  value: hours / 24 + minutes / 1440 + seconds / 86400,
1397
1462
  format: format,
@@ -1405,7 +1470,7 @@
1405
1470
  // -----------------------------------------------------------------------------
1406
1471
  function numberToJsDate(value) {
1407
1472
  const truncValue = Math.trunc(value);
1408
- let date = new Date(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
1473
+ let date = DateTime.fromTimestamp(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
1409
1474
  let time = value - truncValue;
1410
1475
  time = time < 0 ? 1 + time : time;
1411
1476
  const hours = Math.round(time * 24);
@@ -1422,7 +1487,7 @@
1422
1487
  }
1423
1488
  /** Return the number of days in the current month of the given date */
1424
1489
  function getDaysInMonth(date) {
1425
- return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
1490
+ return new DateTime(date.getFullYear(), date.getMonth() + 1, 0).getDate();
1426
1491
  }
1427
1492
  function isLastDayOfMonth(date) {
1428
1493
  return getDaysInMonth(date) === date.getDate();
@@ -1440,7 +1505,7 @@
1440
1505
  const yStart = date.getFullYear();
1441
1506
  const mStart = date.getMonth();
1442
1507
  const dStart = date.getDate();
1443
- const jsDate = new Date(yStart, mStart + months);
1508
+ const jsDate = new DateTime(yStart, mStart + months, 1);
1444
1509
  if (keepEndOfMonth && dStart === getDaysInMonth(date)) {
1445
1510
  jsDate.setDate(getDaysInMonth(jsDate));
1446
1511
  }
@@ -2044,7 +2109,7 @@
2044
2109
  .map((p) => {
2045
2110
  switch (p) {
2046
2111
  case "hhhh":
2047
- const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY) / (60 * 60 * 1000));
2112
+ const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY.getTime()) / (60 * 60 * 1000));
2048
2113
  return helapsedHours.toString();
2049
2114
  case "hh":
2050
2115
  return hours.toString().padStart(2, "0");
@@ -5598,6 +5663,7 @@
5598
5663
  },
5599
5664
  };
5600
5665
 
5666
+ const macRegex = /Mac/i;
5601
5667
  /**
5602
5668
  * Return true if the event was triggered from
5603
5669
  * a child element.
@@ -5637,7 +5703,15 @@
5637
5703
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
5638
5704
  }
5639
5705
  function isMacOS() {
5640
- return navigator.userAgent.toUpperCase().indexOf("MAC") >= 0;
5706
+ return Boolean(macRegex.test(navigator.userAgent));
5707
+ }
5708
+ /**
5709
+ * @param {KeyboardEvent | MouseEvent} ev
5710
+ * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
5711
+ * On Mac, this is the "meta" or "command" key.
5712
+ */
5713
+ function isCtrlKey(ev) {
5714
+ return isMacOS() ? ev.metaKey : ev.ctrlKey;
5641
5715
  }
5642
5716
 
5643
5717
  /**
@@ -5977,6 +6051,10 @@
5977
6051
  justify-content: space-between;
5978
6052
  }
5979
6053
 
6054
+ .o-menu-item-name {
6055
+ min-width: 40%;
6056
+ }
6057
+
5980
6058
  .o-menu-item-icon {
5981
6059
  display: inline-block;
5982
6060
  margin: 0px 8px 0px 0px;
@@ -6311,6 +6389,9 @@
6311
6389
  function makeArg(str, description) {
6312
6390
  let parts = str.match(ARG_REGEXP);
6313
6391
  let name = parts[1].trim();
6392
+ if (!name) {
6393
+ throw new Error(`Function argument definition is missing a name: '${str}'.`);
6394
+ }
6314
6395
  let types = [];
6315
6396
  let isOptional = false;
6316
6397
  let isRepeating = false;
@@ -9561,7 +9642,7 @@
9561
9642
  if (_year < 1900) {
9562
9643
  _year += 1900;
9563
9644
  }
9564
- const jsDate = new Date(_year, _month - 1, _day);
9645
+ const jsDate = new DateTime(_year, _month - 1, _day);
9565
9646
  const result = jsDateToRoundNumber(jsDate);
9566
9647
  assert(() => result >= 0, _lt(`The function [[FUNCTION_NAME]] result must be greater than or equal 01/01/1900.`));
9567
9648
  return result;
@@ -9604,7 +9685,7 @@
9604
9685
  // See: https://support.microsoft.com/en-us/office/datedif-function-25dba1a4-2812-480b-84dd-8b32a451b35c
9605
9686
  let days = jsEndDate.getDate() - jsStartDate.getDate();
9606
9687
  if (days < 0) {
9607
- const monthBeforeEndMonth = new Date(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
9688
+ const monthBeforeEndMonth = new DateTime(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
9608
9689
  const daysInMonthBeforeEndMonth = getDaysInMonth(monthBeforeEndMonth);
9609
9690
  days = daysInMonthBeforeEndMonth - Math.abs(days);
9610
9691
  }
@@ -9613,7 +9694,7 @@
9613
9694
  if (areTwoDatesWithinOneYear(_startDate, _endDate)) {
9614
9695
  return getTimeDifferenceInWholeDays(jsStartDate, jsEndDate);
9615
9696
  }
9616
- const endDateWithinOneYear = new Date(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
9697
+ const endDateWithinOneYear = new DateTime(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
9617
9698
  let days = getTimeDifferenceInWholeDays(jsStartDate, endDateWithinOneYear);
9618
9699
  if (days < 0) {
9619
9700
  endDateWithinOneYear.setFullYear(jsStartDate.getFullYear() + 1);
@@ -9730,7 +9811,7 @@
9730
9811
  const _months = Math.trunc(toNumber(months, this.locale));
9731
9812
  const yStart = _startDate.getFullYear();
9732
9813
  const mStart = _startDate.getMonth();
9733
- const jsDate = new Date(yStart, mStart + _months + 1, 0);
9814
+ const jsDate = new DateTime(yStart, mStart + _months + 1, 0);
9734
9815
  return jsDateToRoundNumber(jsDate);
9735
9816
  },
9736
9817
  isExported: true,
@@ -9767,17 +9848,17 @@
9767
9848
  // The first week of the year is the week that contains the first
9768
9849
  // Thursday of the year.
9769
9850
  let firstThursday = 1;
9770
- while (new Date(y, 0, firstThursday).getDay() !== 4) {
9851
+ while (new DateTime(y, 0, firstThursday).getDay() !== 4) {
9771
9852
  firstThursday += 1;
9772
9853
  }
9773
- const firstDayOfFirstWeek = new Date(y, 0, firstThursday - 3);
9854
+ const firstDayOfFirstWeek = new DateTime(y, 0, firstThursday - 3);
9774
9855
  // The last week of the year is the week that contains the last Thursday of
9775
9856
  // the year.
9776
9857
  let lastThursday = 31;
9777
- while (new Date(y, 11, lastThursday).getDay() !== 4) {
9858
+ while (new DateTime(y, 11, lastThursday).getDay() !== 4) {
9778
9859
  lastThursday -= 1;
9779
9860
  }
9780
- const lastDayOfLastWeek = new Date(y, 11, lastThursday + 3);
9861
+ const lastDayOfLastWeek = new DateTime(y, 11, lastThursday + 3);
9781
9862
  // B - If our date > lastDayOfLastWeek then it's in the weeks of the year after
9782
9863
  // If our date < firstDayOfFirstWeek then it's in the weeks of the year before
9783
9864
  let offsetYear;
@@ -9803,17 +9884,17 @@
9803
9884
  case 1:
9804
9885
  // firstDay is the 1st day of the 1st week of the year after
9805
9886
  // firstDay = lastDayOfLastWeek + 1 Day
9806
- firstDay = new Date(y, 11, lastThursday + 3 + 1);
9887
+ firstDay = new DateTime(y, 11, lastThursday + 3 + 1);
9807
9888
  break;
9808
9889
  case -1:
9809
9890
  // firstDay is the 1st day of the 1st week of the previous year.
9810
9891
  // The first week of the previous year is the week that contains the
9811
9892
  // first Thursday of the previous year.
9812
9893
  let firstThursdayPreviousYear = 1;
9813
- while (new Date(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
9894
+ while (new DateTime(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
9814
9895
  firstThursdayPreviousYear += 1;
9815
9896
  }
9816
- firstDay = new Date(y - 1, 0, firstThursdayPreviousYear - 3);
9897
+ firstDay = new DateTime(y - 1, 0, firstThursdayPreviousYear - 3);
9817
9898
  break;
9818
9899
  }
9819
9900
  const diff = (_date.getTime() - firstDay.getTime()) / MS_PER_DAY;
@@ -9948,8 +10029,8 @@
9948
10029
  });
9949
10030
  }
9950
10031
  const invertDate = _startDate.getTime() > _endDate.getTime();
9951
- const stopDate = new Date((invertDate ? _startDate : _endDate).getTime());
9952
- let stepDate = new Date((invertDate ? _endDate : _startDate).getTime());
10032
+ const stopDate = DateTime.fromTimestamp((invertDate ? _startDate : _endDate).getTime());
10033
+ let stepDate = DateTime.fromTimestamp((invertDate ? _endDate : _startDate).getTime());
9953
10034
  const timeStopDate = stopDate.getTime();
9954
10035
  let timeStepDate = stepDate.getTime();
9955
10036
  let netWorkingDay = 0;
@@ -9975,8 +10056,7 @@
9975
10056
  return this.locale.dateFormat + " " + this.locale.timeFormat;
9976
10057
  },
9977
10058
  compute: function () {
9978
- let today = new Date();
9979
- today.setMilliseconds(0);
10059
+ let today = DateTime.now();
9980
10060
  const delta = today.getTime() - INITIAL_1900_DAY.getTime();
9981
10061
  const time = today.getHours() / 24 + today.getMinutes() / 1440 + today.getSeconds() / 86400;
9982
10062
  return Math.floor(delta / MS_PER_DAY) + time;
@@ -10050,8 +10130,8 @@
10050
10130
  return this.locale.dateFormat;
10051
10131
  },
10052
10132
  compute: function () {
10053
- const today = new Date();
10054
- const jsDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());
10133
+ const today = DateTime.now();
10134
+ const jsDate = new DateTime(today.getFullYear(), today.getMonth(), today.getDate());
10055
10135
  return jsDateToRoundNumber(jsDate);
10056
10136
  },
10057
10137
  isExported: true,
@@ -10106,10 +10186,10 @@
10106
10186
  }
10107
10187
  const y = _date.getFullYear();
10108
10188
  let dayStart = 1;
10109
- let startDayOfFirstWeek = new Date(y, 0, dayStart);
10189
+ let startDayOfFirstWeek = new DateTime(y, 0, dayStart);
10110
10190
  while (startDayOfFirstWeek.getDay() !== startDayOfWeek) {
10111
10191
  dayStart += 1;
10112
- startDayOfFirstWeek = new Date(y, 0, dayStart);
10192
+ startDayOfFirstWeek = new DateTime(y, 0, dayStart);
10113
10193
  }
10114
10194
  const dif = (_date.getTime() - startDayOfFirstWeek.getTime()) / MS_PER_DAY;
10115
10195
  if (dif < 0) {
@@ -10167,7 +10247,7 @@
10167
10247
  timesHoliday.add(holiday.getTime());
10168
10248
  });
10169
10249
  }
10170
- let stepDate = new Date(_startDate.getTime());
10250
+ let stepDate = DateTime.fromTimestamp(_startDate.getTime());
10171
10251
  let timeStepDate = stepDate.getTime();
10172
10252
  const unitDay = Math.sign(_numDays);
10173
10253
  let stepDay = Math.abs(_numDays);
@@ -10231,7 +10311,7 @@
10231
10311
  const _startDate = toJsDate(date, this.locale);
10232
10312
  const yStart = _startDate.getFullYear();
10233
10313
  const mStart = _startDate.getMonth();
10234
- const jsDate = new Date(yStart, mStart, 1);
10314
+ const jsDate = new DateTime(yStart, mStart, 1);
10235
10315
  return jsDateToRoundNumber(jsDate);
10236
10316
  },
10237
10317
  };
@@ -10273,7 +10353,7 @@
10273
10353
  compute: function (date) {
10274
10354
  const quarter = QUARTER.compute.bind(this)(date);
10275
10355
  const year = YEAR.compute.bind(this)(date);
10276
- const jsDate = new Date(year, (quarter - 1) * 3, 1);
10356
+ const jsDate = new DateTime(year, (quarter - 1) * 3, 1);
10277
10357
  return jsDateToRoundNumber(jsDate);
10278
10358
  },
10279
10359
  };
@@ -10290,7 +10370,7 @@
10290
10370
  compute: function (date) {
10291
10371
  const quarter = QUARTER.compute.bind(this)(date);
10292
10372
  const year = YEAR.compute.bind(this)(date);
10293
- const jsDate = new Date(year, quarter * 3, 0);
10373
+ const jsDate = new DateTime(year, quarter * 3, 0);
10294
10374
  return jsDateToRoundNumber(jsDate);
10295
10375
  },
10296
10376
  };
@@ -10306,7 +10386,7 @@
10306
10386
  },
10307
10387
  compute: function (date) {
10308
10388
  const year = YEAR.compute.bind(this)(date);
10309
- const jsDate = new Date(year, 0, 1);
10389
+ const jsDate = new DateTime(year, 0, 1);
10310
10390
  return jsDateToRoundNumber(jsDate);
10311
10391
  },
10312
10392
  };
@@ -10322,7 +10402,7 @@
10322
10402
  },
10323
10403
  compute: function (date) {
10324
10404
  const year = YEAR.compute.bind(this)(date);
10325
- const jsDate = new Date(year + 1, 0, 0);
10405
+ const jsDate = new DateTime(year + 1, 0, 0);
10326
10406
  return jsDateToRoundNumber(jsDate);
10327
10407
  },
10328
10408
  };
@@ -10370,8 +10450,8 @@
10370
10450
  const DELTA = {
10371
10451
  description: _lt("Compare two numeric values, returning 1 if they're equal."),
10372
10452
  args: [
10373
- arg(" (number)", _lt("The first number to compare.")),
10374
- arg(` (number, default=${DEFAULT_DELTA_ARG})`, _lt("The second number to compare.")),
10453
+ arg("number1 (number)", _lt("The first number to compare.")),
10454
+ arg(`number2 (number, default=${DEFAULT_DELTA_ARG})`, _lt("The second number to compare.")),
10375
10455
  ],
10376
10456
  returns: ["NUMBER"],
10377
10457
  compute: function (number1, number2 = DEFAULT_DELTA_ARG) {
@@ -10540,7 +10620,7 @@
10540
10620
  function assertSettlementLessThanOneYearBeforeMaturity(settlement, maturity, locale) {
10541
10621
  const startDate = toJsDate(settlement, locale);
10542
10622
  const endDate = toJsDate(maturity, locale);
10543
- const startDatePlusOneYear = new Date(startDate);
10623
+ const startDatePlusOneYear = toJsDate(settlement, locale);
10544
10624
  startDatePlusOneYear.setFullYear(startDate.getFullYear() + 1);
10545
10625
  assert(() => endDate.getTime() <= startDatePlusOneYear.getTime(), _lt("The settlement date (%s) must at most one year after the maturity date (%s).", settlement.toString(), maturity.toString()));
10546
10626
  }
@@ -10675,7 +10755,7 @@
10675
10755
  arg("salvage (number)", _lt("The value of the asset at the end of depreciation.")),
10676
10756
  arg("period (number)", _lt("The single period within life for which to calculate depreciation.")),
10677
10757
  arg("rate (number)", _lt("The deprecation rate.")),
10678
- arg(" (number, optional)", _lt("An indicator of what day count method to use.")),
10758
+ arg("day_count_convention (number, optional)", _lt("An indicator of what day count method to use.")),
10679
10759
  ],
10680
10760
  returns: ["NUMBER"],
10681
10761
  compute: function (cost, purchaseDate, firstPeriodEnd, salvage, period, rate, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {
@@ -13694,8 +13774,8 @@
13694
13774
  description: _lt("A segment of a string."),
13695
13775
  args: [
13696
13776
  arg("text (string)", _lt("The string to extract a segment from.")),
13697
- arg(" (number)", _lt("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
13698
- arg(" (number)", _lt("The length of the segment to extract.")),
13777
+ arg("starting_at (number)", _lt("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
13778
+ arg("extract_length (number)", _lt("The length of the segment to extract.")),
13699
13779
  ],
13700
13780
  returns: ["STRING"],
13701
13781
  compute: function (text, starting_at, extract_length) {
@@ -20491,10 +20571,10 @@
20491
20571
  function updateSelectionWithArrowKeys(ev, selection) {
20492
20572
  const direction = arrowMap[ev.key];
20493
20573
  if (ev.shiftKey) {
20494
- selection.resizeAnchorZone(direction, ev.ctrlKey ? "end" : 1);
20574
+ selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
20495
20575
  }
20496
20576
  else {
20497
- selection.moveAnchorCell(direction, ev.ctrlKey ? "end" : 1);
20577
+ selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
20498
20578
  }
20499
20579
  }
20500
20580
 
@@ -25539,7 +25619,10 @@
25539
25619
  return;
25540
25620
  }
25541
25621
  const [col, row] = this.getCartesianCoordinates(ev);
25542
- this.props.onCellClicked(col, row, { shiftKey: ev.shiftKey, ctrlKey: ev.ctrlKey });
25622
+ this.props.onCellClicked(col, row, {
25623
+ expandZone: ev.shiftKey,
25624
+ addZone: isCtrlKey(ev),
25625
+ });
25543
25626
  }
25544
25627
  onDoubleClick(ev) {
25545
25628
  const [col, row] = this.getCartesianCoordinates(ev);
@@ -25767,7 +25850,7 @@
25767
25850
  this._increaseSelection(index);
25768
25851
  }
25769
25852
  else {
25770
- this._selectElement(index, ev.ctrlKey);
25853
+ this._selectElement(index, isCtrlKey(ev));
25771
25854
  }
25772
25855
  this.lastSelectedElementIndex = index;
25773
25856
  const mouseMoveSelect = (col, row) => {
@@ -25780,7 +25863,7 @@
25780
25863
  const mouseUpSelect = () => {
25781
25864
  this.state.isSelecting = false;
25782
25865
  this.lastSelectedElementIndex = null;
25783
- this.env.model.dispatch(ev.ctrlKey ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
25866
+ this.env.model.dispatch(isCtrlKey(ev) ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
25784
25867
  this._computeGrabDisplay(ev);
25785
25868
  };
25786
25869
  dragAndDropBeyondTheViewport(this.env, mouseMoveSelect, mouseUpSelect);
@@ -25932,8 +26015,8 @@
25932
26015
  this.env.raiseError(MergeErrorMessage);
25933
26016
  }
25934
26017
  }
25935
- _selectElement(index, ctrlKey) {
25936
- this.env.model.selection.selectColumn(index, ctrlKey ? "newAnchor" : "overrideSelection");
26018
+ _selectElement(index, addDistinctHeader) {
26019
+ this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
25937
26020
  }
25938
26021
  _increaseSelection(index) {
25939
26022
  this.env.model.selection.selectColumn(index, "updateAnchor");
@@ -26106,8 +26189,8 @@
26106
26189
  this.env.raiseError(MergeErrorMessage);
26107
26190
  }
26108
26191
  }
26109
- _selectElement(index, ctrlKey) {
26110
- this.env.model.selection.selectRow(index, ctrlKey ? "newAnchor" : "overrideSelection");
26192
+ _selectElement(index, addDistinctHeader) {
26193
+ this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
26111
26194
  }
26112
26195
  _increaseSelection(index) {
26113
26196
  this.env.model.selection.selectRow(index, "updateAnchor");
@@ -26913,8 +26996,8 @@
26913
26996
  // ---------------------------------------------------------------------------
26914
26997
  // Zone selection with mouse
26915
26998
  // ---------------------------------------------------------------------------
26916
- onCellClicked(col, row, { ctrlKey, shiftKey }) {
26917
- if (ctrlKey) {
26999
+ onCellClicked(col, row, { addZone, expandZone }) {
27000
+ if (addZone) {
26918
27001
  this.env.model.dispatch("PREPARE_SELECTION_INPUT_EXPANSION");
26919
27002
  }
26920
27003
  if (this.env.model.getters.hasOpenedPopover()) {
@@ -26923,10 +27006,10 @@
26923
27006
  if (this.env.model.getters.getEditionMode() === "editing") {
26924
27007
  this.env.model.dispatch("STOP_EDITION");
26925
27008
  }
26926
- if (shiftKey) {
27009
+ if (expandZone) {
26927
27010
  this.env.model.selection.setAnchorCorner(col, row);
26928
27011
  }
26929
- else if (ctrlKey) {
27012
+ else if (addZone) {
26930
27013
  this.env.model.selection.addCellToSelection(col, row);
26931
27014
  }
26932
27015
  else {
@@ -26988,9 +27071,7 @@
26988
27071
  return;
26989
27072
  }
26990
27073
  let keyDownString = "";
26991
- if (ev.ctrlKey)
26992
- keyDownString += "CTRL+";
26993
- if (ev.metaKey)
27074
+ if (isCtrlKey(ev))
26994
27075
  keyDownString += "CTRL+";
26995
27076
  if (ev.altKey)
26996
27077
  keyDownString += "ALT+";
@@ -35559,56 +35640,758 @@
35559
35640
  }
35560
35641
  }
35561
35642
 
35643
+ function quickselect(arr, k, left, right, compare) {
35644
+ quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
35645
+ }
35646
+
35647
+ function quickselectStep(arr, k, left, right, compare) {
35648
+
35649
+ while (right > left) {
35650
+ if (right - left > 600) {
35651
+ var n = right - left + 1;
35652
+ var m = k - left + 1;
35653
+ var z = Math.log(n);
35654
+ var s = 0.5 * Math.exp(2 * z / 3);
35655
+ var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
35656
+ var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
35657
+ var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
35658
+ quickselectStep(arr, k, newLeft, newRight, compare);
35659
+ }
35660
+
35661
+ var t = arr[k];
35662
+ var i = left;
35663
+ var j = right;
35664
+
35665
+ swap(arr, left, k);
35666
+ if (compare(arr[right], t) > 0) swap(arr, left, right);
35667
+
35668
+ while (i < j) {
35669
+ swap(arr, i, j);
35670
+ i++;
35671
+ j--;
35672
+ while (compare(arr[i], t) < 0) i++;
35673
+ while (compare(arr[j], t) > 0) j--;
35674
+ }
35675
+
35676
+ if (compare(arr[left], t) === 0) swap(arr, left, j);
35677
+ else {
35678
+ j++;
35679
+ swap(arr, j, right);
35680
+ }
35681
+
35682
+ if (j <= k) left = j + 1;
35683
+ if (k <= j) right = j - 1;
35684
+ }
35685
+ }
35686
+
35687
+ function swap(arr, i, j) {
35688
+ var tmp = arr[i];
35689
+ arr[i] = arr[j];
35690
+ arr[j] = tmp;
35691
+ }
35692
+
35693
+ function defaultCompare(a, b) {
35694
+ return a < b ? -1 : a > b ? 1 : 0;
35695
+ }
35696
+
35697
+ class RBush {
35698
+ constructor(maxEntries = 9) {
35699
+ // max entries in a node is 9 by default; min node fill is 40% for best performance
35700
+ this._maxEntries = Math.max(4, maxEntries);
35701
+ this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
35702
+ this.clear();
35703
+ }
35704
+
35705
+ all() {
35706
+ return this._all(this.data, []);
35707
+ }
35708
+
35709
+ search(bbox) {
35710
+ let node = this.data;
35711
+ const result = [];
35712
+
35713
+ if (!intersects(bbox, node)) return result;
35714
+
35715
+ const toBBox = this.toBBox;
35716
+ const nodesToSearch = [];
35717
+
35718
+ while (node) {
35719
+ for (let i = 0; i < node.children.length; i++) {
35720
+ const child = node.children[i];
35721
+ const childBBox = node.leaf ? toBBox(child) : child;
35722
+
35723
+ if (intersects(bbox, childBBox)) {
35724
+ if (node.leaf) result.push(child);
35725
+ else if (contains(bbox, childBBox)) this._all(child, result);
35726
+ else nodesToSearch.push(child);
35727
+ }
35728
+ }
35729
+ node = nodesToSearch.pop();
35730
+ }
35731
+
35732
+ return result;
35733
+ }
35734
+
35735
+ collides(bbox) {
35736
+ let node = this.data;
35737
+
35738
+ if (!intersects(bbox, node)) return false;
35739
+
35740
+ const nodesToSearch = [];
35741
+ while (node) {
35742
+ for (let i = 0; i < node.children.length; i++) {
35743
+ const child = node.children[i];
35744
+ const childBBox = node.leaf ? this.toBBox(child) : child;
35745
+
35746
+ if (intersects(bbox, childBBox)) {
35747
+ if (node.leaf || contains(bbox, childBBox)) return true;
35748
+ nodesToSearch.push(child);
35749
+ }
35750
+ }
35751
+ node = nodesToSearch.pop();
35752
+ }
35753
+
35754
+ return false;
35755
+ }
35756
+
35757
+ load(data) {
35758
+ if (!(data && data.length)) return this;
35759
+
35760
+ if (data.length < this._minEntries) {
35761
+ for (let i = 0; i < data.length; i++) {
35762
+ this.insert(data[i]);
35763
+ }
35764
+ return this;
35765
+ }
35766
+
35767
+ // recursively build the tree with the given data from scratch using OMT algorithm
35768
+ let node = this._build(data.slice(), 0, data.length - 1, 0);
35769
+
35770
+ if (!this.data.children.length) {
35771
+ // save as is if tree is empty
35772
+ this.data = node;
35773
+
35774
+ } else if (this.data.height === node.height) {
35775
+ // split root if trees have the same height
35776
+ this._splitRoot(this.data, node);
35777
+
35778
+ } else {
35779
+ if (this.data.height < node.height) {
35780
+ // swap trees if inserted one is bigger
35781
+ const tmpNode = this.data;
35782
+ this.data = node;
35783
+ node = tmpNode;
35784
+ }
35785
+
35786
+ // insert the small tree into the large tree at appropriate level
35787
+ this._insert(node, this.data.height - node.height - 1, true);
35788
+ }
35789
+
35790
+ return this;
35791
+ }
35792
+
35793
+ insert(item) {
35794
+ if (item) this._insert(item, this.data.height - 1);
35795
+ return this;
35796
+ }
35797
+
35798
+ clear() {
35799
+ this.data = createNode([]);
35800
+ return this;
35801
+ }
35802
+
35803
+ remove(item, equalsFn) {
35804
+ if (!item) return this;
35805
+
35806
+ let node = this.data;
35807
+ const bbox = this.toBBox(item);
35808
+ const path = [];
35809
+ const indexes = [];
35810
+ let i, parent, goingUp;
35811
+
35812
+ // depth-first iterative tree traversal
35813
+ while (node || path.length) {
35814
+
35815
+ if (!node) { // go up
35816
+ node = path.pop();
35817
+ parent = path[path.length - 1];
35818
+ i = indexes.pop();
35819
+ goingUp = true;
35820
+ }
35821
+
35822
+ if (node.leaf) { // check current node
35823
+ const index = findItem(item, node.children, equalsFn);
35824
+
35825
+ if (index !== -1) {
35826
+ // item found, remove the item and condense tree upwards
35827
+ node.children.splice(index, 1);
35828
+ path.push(node);
35829
+ this._condense(path);
35830
+ return this;
35831
+ }
35832
+ }
35833
+
35834
+ if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
35835
+ path.push(node);
35836
+ indexes.push(i);
35837
+ i = 0;
35838
+ parent = node;
35839
+ node = node.children[0];
35840
+
35841
+ } else if (parent) { // go right
35842
+ i++;
35843
+ node = parent.children[i];
35844
+ goingUp = false;
35845
+
35846
+ } else node = null; // nothing found
35847
+ }
35848
+
35849
+ return this;
35850
+ }
35851
+
35852
+ toBBox(item) { return item; }
35853
+
35854
+ compareMinX(a, b) { return a.minX - b.minX; }
35855
+ compareMinY(a, b) { return a.minY - b.minY; }
35856
+
35857
+ toJSON() { return this.data; }
35858
+
35859
+ fromJSON(data) {
35860
+ this.data = data;
35861
+ return this;
35862
+ }
35863
+
35864
+ _all(node, result) {
35865
+ const nodesToSearch = [];
35866
+ while (node) {
35867
+ if (node.leaf) result.push(...node.children);
35868
+ else nodesToSearch.push(...node.children);
35869
+
35870
+ node = nodesToSearch.pop();
35871
+ }
35872
+ return result;
35873
+ }
35874
+
35875
+ _build(items, left, right, height) {
35876
+
35877
+ const N = right - left + 1;
35878
+ let M = this._maxEntries;
35879
+ let node;
35880
+
35881
+ if (N <= M) {
35882
+ // reached leaf level; return leaf
35883
+ node = createNode(items.slice(left, right + 1));
35884
+ calcBBox(node, this.toBBox);
35885
+ return node;
35886
+ }
35887
+
35888
+ if (!height) {
35889
+ // target height of the bulk-loaded tree
35890
+ height = Math.ceil(Math.log(N) / Math.log(M));
35891
+
35892
+ // target number of root entries to maximize storage utilization
35893
+ M = Math.ceil(N / Math.pow(M, height - 1));
35894
+ }
35895
+
35896
+ node = createNode([]);
35897
+ node.leaf = false;
35898
+ node.height = height;
35899
+
35900
+ // split the items into M mostly square tiles
35901
+
35902
+ const N2 = Math.ceil(N / M);
35903
+ const N1 = N2 * Math.ceil(Math.sqrt(M));
35904
+
35905
+ multiSelect(items, left, right, N1, this.compareMinX);
35906
+
35907
+ for (let i = left; i <= right; i += N1) {
35908
+
35909
+ const right2 = Math.min(i + N1 - 1, right);
35910
+
35911
+ multiSelect(items, i, right2, N2, this.compareMinY);
35912
+
35913
+ for (let j = i; j <= right2; j += N2) {
35914
+
35915
+ const right3 = Math.min(j + N2 - 1, right2);
35916
+
35917
+ // pack each entry recursively
35918
+ node.children.push(this._build(items, j, right3, height - 1));
35919
+ }
35920
+ }
35921
+
35922
+ calcBBox(node, this.toBBox);
35923
+
35924
+ return node;
35925
+ }
35926
+
35927
+ _chooseSubtree(bbox, node, level, path) {
35928
+ while (true) {
35929
+ path.push(node);
35930
+
35931
+ if (node.leaf || path.length - 1 === level) break;
35932
+
35933
+ let minArea = Infinity;
35934
+ let minEnlargement = Infinity;
35935
+ let targetNode;
35936
+
35937
+ for (let i = 0; i < node.children.length; i++) {
35938
+ const child = node.children[i];
35939
+ const area = bboxArea(child);
35940
+ const enlargement = enlargedArea(bbox, child) - area;
35941
+
35942
+ // choose entry with the least area enlargement
35943
+ if (enlargement < minEnlargement) {
35944
+ minEnlargement = enlargement;
35945
+ minArea = area < minArea ? area : minArea;
35946
+ targetNode = child;
35947
+
35948
+ } else if (enlargement === minEnlargement) {
35949
+ // otherwise choose one with the smallest area
35950
+ if (area < minArea) {
35951
+ minArea = area;
35952
+ targetNode = child;
35953
+ }
35954
+ }
35955
+ }
35956
+
35957
+ node = targetNode || node.children[0];
35958
+ }
35959
+
35960
+ return node;
35961
+ }
35962
+
35963
+ _insert(item, level, isNode) {
35964
+ const bbox = isNode ? item : this.toBBox(item);
35965
+ const insertPath = [];
35966
+
35967
+ // find the best node for accommodating the item, saving all nodes along the path too
35968
+ const node = this._chooseSubtree(bbox, this.data, level, insertPath);
35969
+
35970
+ // put the item into the node
35971
+ node.children.push(item);
35972
+ extend(node, bbox);
35973
+
35974
+ // split on node overflow; propagate upwards if necessary
35975
+ while (level >= 0) {
35976
+ if (insertPath[level].children.length > this._maxEntries) {
35977
+ this._split(insertPath, level);
35978
+ level--;
35979
+ } else break;
35980
+ }
35981
+
35982
+ // adjust bboxes along the insertion path
35983
+ this._adjustParentBBoxes(bbox, insertPath, level);
35984
+ }
35985
+
35986
+ // split overflowed node into two
35987
+ _split(insertPath, level) {
35988
+ const node = insertPath[level];
35989
+ const M = node.children.length;
35990
+ const m = this._minEntries;
35991
+
35992
+ this._chooseSplitAxis(node, m, M);
35993
+
35994
+ const splitIndex = this._chooseSplitIndex(node, m, M);
35995
+
35996
+ const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
35997
+ newNode.height = node.height;
35998
+ newNode.leaf = node.leaf;
35999
+
36000
+ calcBBox(node, this.toBBox);
36001
+ calcBBox(newNode, this.toBBox);
36002
+
36003
+ if (level) insertPath[level - 1].children.push(newNode);
36004
+ else this._splitRoot(node, newNode);
36005
+ }
36006
+
36007
+ _splitRoot(node, newNode) {
36008
+ // split root node
36009
+ this.data = createNode([node, newNode]);
36010
+ this.data.height = node.height + 1;
36011
+ this.data.leaf = false;
36012
+ calcBBox(this.data, this.toBBox);
36013
+ }
36014
+
36015
+ _chooseSplitIndex(node, m, M) {
36016
+ let index;
36017
+ let minOverlap = Infinity;
36018
+ let minArea = Infinity;
36019
+
36020
+ for (let i = m; i <= M - m; i++) {
36021
+ const bbox1 = distBBox(node, 0, i, this.toBBox);
36022
+ const bbox2 = distBBox(node, i, M, this.toBBox);
36023
+
36024
+ const overlap = intersectionArea(bbox1, bbox2);
36025
+ const area = bboxArea(bbox1) + bboxArea(bbox2);
36026
+
36027
+ // choose distribution with minimum overlap
36028
+ if (overlap < minOverlap) {
36029
+ minOverlap = overlap;
36030
+ index = i;
36031
+
36032
+ minArea = area < minArea ? area : minArea;
36033
+
36034
+ } else if (overlap === minOverlap) {
36035
+ // otherwise choose distribution with minimum area
36036
+ if (area < minArea) {
36037
+ minArea = area;
36038
+ index = i;
36039
+ }
36040
+ }
36041
+ }
36042
+
36043
+ return index || M - m;
36044
+ }
36045
+
36046
+ // sorts node children by the best axis for split
36047
+ _chooseSplitAxis(node, m, M) {
36048
+ const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
36049
+ const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
36050
+ const xMargin = this._allDistMargin(node, m, M, compareMinX);
36051
+ const yMargin = this._allDistMargin(node, m, M, compareMinY);
36052
+
36053
+ // if total distributions margin value is minimal for x, sort by minX,
36054
+ // otherwise it's already sorted by minY
36055
+ if (xMargin < yMargin) node.children.sort(compareMinX);
36056
+ }
36057
+
36058
+ // total margin of all possible split distributions where each node is at least m full
36059
+ _allDistMargin(node, m, M, compare) {
36060
+ node.children.sort(compare);
36061
+
36062
+ const toBBox = this.toBBox;
36063
+ const leftBBox = distBBox(node, 0, m, toBBox);
36064
+ const rightBBox = distBBox(node, M - m, M, toBBox);
36065
+ let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
36066
+
36067
+ for (let i = m; i < M - m; i++) {
36068
+ const child = node.children[i];
36069
+ extend(leftBBox, node.leaf ? toBBox(child) : child);
36070
+ margin += bboxMargin(leftBBox);
36071
+ }
36072
+
36073
+ for (let i = M - m - 1; i >= m; i--) {
36074
+ const child = node.children[i];
36075
+ extend(rightBBox, node.leaf ? toBBox(child) : child);
36076
+ margin += bboxMargin(rightBBox);
36077
+ }
36078
+
36079
+ return margin;
36080
+ }
36081
+
36082
+ _adjustParentBBoxes(bbox, path, level) {
36083
+ // adjust bboxes along the given tree path
36084
+ for (let i = level; i >= 0; i--) {
36085
+ extend(path[i], bbox);
36086
+ }
36087
+ }
36088
+
36089
+ _condense(path) {
36090
+ // go through the path, removing empty nodes and updating bboxes
36091
+ for (let i = path.length - 1, siblings; i >= 0; i--) {
36092
+ if (path[i].children.length === 0) {
36093
+ if (i > 0) {
36094
+ siblings = path[i - 1].children;
36095
+ siblings.splice(siblings.indexOf(path[i]), 1);
36096
+
36097
+ } else this.clear();
36098
+
36099
+ } else calcBBox(path[i], this.toBBox);
36100
+ }
36101
+ }
36102
+ }
36103
+
36104
+ function findItem(item, items, equalsFn) {
36105
+ if (!equalsFn) return items.indexOf(item);
36106
+
36107
+ for (let i = 0; i < items.length; i++) {
36108
+ if (equalsFn(item, items[i])) return i;
36109
+ }
36110
+ return -1;
36111
+ }
36112
+
36113
+ // calculate node's bbox from bboxes of its children
36114
+ function calcBBox(node, toBBox) {
36115
+ distBBox(node, 0, node.children.length, toBBox, node);
36116
+ }
36117
+
36118
+ // min bounding rectangle of node children from k to p-1
36119
+ function distBBox(node, k, p, toBBox, destNode) {
36120
+ if (!destNode) destNode = createNode(null);
36121
+ destNode.minX = Infinity;
36122
+ destNode.minY = Infinity;
36123
+ destNode.maxX = -Infinity;
36124
+ destNode.maxY = -Infinity;
36125
+
36126
+ for (let i = k; i < p; i++) {
36127
+ const child = node.children[i];
36128
+ extend(destNode, node.leaf ? toBBox(child) : child);
36129
+ }
36130
+
36131
+ return destNode;
36132
+ }
36133
+
36134
+ function extend(a, b) {
36135
+ a.minX = Math.min(a.minX, b.minX);
36136
+ a.minY = Math.min(a.minY, b.minY);
36137
+ a.maxX = Math.max(a.maxX, b.maxX);
36138
+ a.maxY = Math.max(a.maxY, b.maxY);
36139
+ return a;
36140
+ }
36141
+
36142
+ function compareNodeMinX(a, b) { return a.minX - b.minX; }
36143
+ function compareNodeMinY(a, b) { return a.minY - b.minY; }
36144
+
36145
+ function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
36146
+ function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
36147
+
36148
+ function enlargedArea(a, b) {
36149
+ return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
36150
+ (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
36151
+ }
36152
+
36153
+ function intersectionArea(a, b) {
36154
+ const minX = Math.max(a.minX, b.minX);
36155
+ const minY = Math.max(a.minY, b.minY);
36156
+ const maxX = Math.min(a.maxX, b.maxX);
36157
+ const maxY = Math.min(a.maxY, b.maxY);
36158
+
36159
+ return Math.max(0, maxX - minX) *
36160
+ Math.max(0, maxY - minY);
36161
+ }
36162
+
36163
+ function contains(a, b) {
36164
+ return a.minX <= b.minX &&
36165
+ a.minY <= b.minY &&
36166
+ b.maxX <= a.maxX &&
36167
+ b.maxY <= a.maxY;
36168
+ }
36169
+
36170
+ function intersects(a, b) {
36171
+ return b.minX <= a.maxX &&
36172
+ b.minY <= a.maxY &&
36173
+ b.maxX >= a.minX &&
36174
+ b.maxY >= a.minY;
36175
+ }
36176
+
36177
+ function createNode(children) {
36178
+ return {
36179
+ children,
36180
+ height: 1,
36181
+ leaf: true,
36182
+ minX: Infinity,
36183
+ minY: Infinity,
36184
+ maxX: -Infinity,
36185
+ maxY: -Infinity
36186
+ };
36187
+ }
36188
+
36189
+ // sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
36190
+ // combines selection algorithm with binary divide & conquer approach
36191
+
36192
+ function multiSelect(arr, left, right, n, compare) {
36193
+ const stack = [left, right];
36194
+
36195
+ while (stack.length) {
36196
+ right = stack.pop();
36197
+ left = stack.pop();
36198
+
36199
+ if (right - left <= n) continue;
36200
+
36201
+ const mid = left + Math.ceil((right - left) / n / 2) * n;
36202
+ quickselect(arr, mid, left, right, compare);
36203
+
36204
+ stack.push(left, mid, mid, right);
36205
+ }
36206
+ }
36207
+
36208
+ /**
36209
+ * R-Tree Data Structure
36210
+ *
36211
+ * R-Tree is a spatial data structure used for efficient indexing and querying
36212
+ * of multi-dimensional objects, particularly in geometric and spatial applications.
36213
+ *
36214
+ * It organizes objects into a tree hierarchy, grouping nearby objects together
36215
+ * in bounding boxes. Each node in the tree represents a bounding box that
36216
+ * contains its child nodes or leaf objects. This hierarchical structure allows
36217
+ * for faster spatial queries.
36218
+ *
36219
+ * @see https://en.wikipedia.org/wiki/R-tree
36220
+ *
36221
+ * Consider a 2D Space with four zones: A, B, C, D
36222
+ * +--------------------------+
36223
+ * | |
36224
+ * | +---+ +-------+ |
36225
+ * | | A | | B | |
36226
+ * | +---+ +-------+ |
36227
+ * | |
36228
+ * | |
36229
+ * | +---+ |
36230
+ * | | C | |
36231
+ * | +---+ |
36232
+ * | +-----------+ |
36233
+ * | | D | |
36234
+ * | +-----------+ |
36235
+ * | |
36236
+ * +--------------------------+
36237
+ *
36238
+ * It groups together zones that are spatially close into a minimum bounding box.
36239
+ * For example, A and B are grouped together in rectangle R1, and C and D are grouped
36240
+ * in R2.
36241
+ *
36242
+ * R0
36243
+ * +--------------------------+
36244
+ * | R1 |
36245
+ * | +-----------------+ |
36246
+ * | | A | | B | |
36247
+ * | +-----------------+ |
36248
+ * | |
36249
+ * | R2 |
36250
+ * | +---+---+---+ |
36251
+ * | | | C | | |
36252
+ * | | +---+ | |
36253
+ * | +-----------+ |
36254
+ * | | D | |
36255
+ * | +-----------+ |
36256
+ * | |
36257
+ * +--------------------------+
36258
+ *
36259
+ * The tree would look like this:
36260
+ * R0
36261
+ * / \
36262
+ * / \
36263
+ * R1 R2
36264
+ * | |
36265
+ * A,B C,D
36266
+
36267
+ * Choosing how to group the zones is crucial for the performance of the tree.
36268
+ * Key considerations include avoiding excessive empty space coverage and minimizing overlap
36269
+ * to reduce the number of subtrees processed during searches.
36270
+ *
36271
+ * Various heuristics exist for determining the optimal grouping strategy, such as "least enlargement"
36272
+ * which prioritizes grouping nodes resulting in the smallest increase in bounding box size. In cases where
36273
+ * the choice cannot be made based on this criterion due to the same enlargement for different groupings,
36274
+ * we then evaluate "least area," aiming to minimize the overall area of bounding boxes.
36275
+ *
36276
+ * This implementation is tailored for spreadsheet use, indexing objects associated
36277
+ * with a zone and a sheet.
36278
+ *
36279
+ * It uses the RBush library under the hood. One 2D RBush R-tree per sheet.
36280
+ * @see https://github.com/mourner/rbush
36281
+ */
36282
+ class SpreadsheetRTree {
36283
+ /**
36284
+ * One 2D R-tree per sheet
36285
+ */
36286
+ rTrees = {};
36287
+ /**
36288
+ * Bulk-inserts the given items into the tree. Bulk insertion is usually ~2-3 times
36289
+ * faster than inserting items one by one. After bulk loading (bulk insertion into
36290
+ * an empty tree), subsequent query performance is also ~20-30% better.
36291
+ */
36292
+ constructor(items = []) {
36293
+ const rangesPerSheet = {};
36294
+ for (const item of items) {
36295
+ const sheetId = item.boundingBox.sheetId;
36296
+ if (!rangesPerSheet[sheetId]) {
36297
+ rangesPerSheet[sheetId] = [];
36298
+ }
36299
+ rangesPerSheet[sheetId].push(item);
36300
+ }
36301
+ for (const sheetId in rangesPerSheet) {
36302
+ this.rTrees[sheetId] = new ZoneRBush();
36303
+ this.rTrees[sheetId].load(rangesPerSheet[sheetId]); // bulk-insert
36304
+ }
36305
+ }
36306
+ insert(item) {
36307
+ const sheetId = item.boundingBox.sheetId;
36308
+ if (!this.rTrees[sheetId]) {
36309
+ this.rTrees[sheetId] = new ZoneRBush();
36310
+ }
36311
+ this.rTrees[sheetId].insert(item);
36312
+ }
36313
+ search({ zone, sheetId }) {
36314
+ if (!this.rTrees[sheetId]) {
36315
+ return [];
36316
+ }
36317
+ return this.rTrees[sheetId].search({
36318
+ minX: zone.left,
36319
+ minY: zone.top,
36320
+ maxX: zone.right,
36321
+ maxY: zone.bottom,
36322
+ });
36323
+ }
36324
+ remove(item) {
36325
+ const sheetId = item.boundingBox.sheetId;
36326
+ if (!this.rTrees[sheetId]) {
36327
+ return;
36328
+ }
36329
+ this.rTrees[sheetId].remove(item, deepEquals);
36330
+ }
36331
+ }
36332
+ /**
36333
+ * RBush extension to use zones as bounding boxes
36334
+ */
36335
+ class ZoneRBush extends RBush {
36336
+ toBBox({ boundingBox }) {
36337
+ const zone = boundingBox.zone;
36338
+ return {
36339
+ minX: zone.left,
36340
+ minY: zone.top,
36341
+ maxX: zone.right,
36342
+ maxY: zone.bottom,
36343
+ };
36344
+ }
36345
+ compareMinX(a, b) {
36346
+ return a.boundingBox.zone.left - b.boundingBox.zone.left;
36347
+ }
36348
+ compareMinY(a, b) {
36349
+ return a.boundingBox.zone.top - b.boundingBox.zone.top;
36350
+ }
36351
+ }
36352
+
35562
36353
  /**
35563
- * This class is an implementation of a dependency Graph.
36354
+ * Implementation of a dependency Graph.
35564
36355
  * The graph is used to evaluate the cells in the correct
35565
36356
  * order, and should be updated each time a cell's content is modified
35566
36357
  *
36358
+ * It uses an R-Tree data structure to efficiently find dependent cells.
35567
36359
  */
35568
36360
  class FormulaDependencyGraph {
35569
- /**
35570
- * Internal structure:
35571
- * - key: a cell position (encoded as an integer)
35572
- * - value: a set of cell positions that depends on the key
35573
- *
35574
- * Given
35575
- * - A1:"= B1 + SQRT(B2)"
35576
- * - C1:"= B1";
35577
- * - C2:"= C1"
35578
- *
35579
- * we will have something like:
35580
- * - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
35581
- * - B2 ---> (A1) meaning A1 depends on B2
35582
- * - C1 ---> (C2) meaning C2 depends on C1
35583
- */
35584
- inverseDependencies = new Map();
36361
+ encoder;
35585
36362
  dependencies = new Map();
36363
+ rTree;
36364
+ constructor(encoder, data = []) {
36365
+ this.encoder = encoder;
36366
+ this.rTree = new SpreadsheetRTree(data);
36367
+ }
35586
36368
  removeAllDependencies(formulaPositionId) {
35587
- const dependencies = this.dependencies.get(formulaPositionId);
35588
- if (!dependencies) {
36369
+ const ranges = this.dependencies.get(formulaPositionId);
36370
+ if (!ranges) {
35589
36371
  return;
35590
36372
  }
35591
- for (const dependency of dependencies) {
35592
- this.inverseDependencies.get(dependency)?.delete(formulaPositionId);
36373
+ for (const range of ranges) {
36374
+ this.rTree.remove(range);
35593
36375
  }
35594
36376
  this.dependencies.delete(formulaPositionId);
35595
36377
  }
35596
36378
  addDependencies(formulaPositionId, dependencies) {
35597
- for (const dependency of dependencies) {
35598
- const inverseDependencies = this.inverseDependencies.get(dependency);
35599
- if (inverseDependencies) {
35600
- inverseDependencies.add(formulaPositionId);
35601
- }
35602
- else {
35603
- this.inverseDependencies.set(dependency, new Set([formulaPositionId]));
35604
- }
36379
+ const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
36380
+ data: formulaPositionId,
36381
+ boundingBox: {
36382
+ zone,
36383
+ sheetId,
36384
+ },
36385
+ }));
36386
+ for (const item of rTreeItems) {
36387
+ this.rTree.insert(item);
35605
36388
  }
35606
36389
  const existingDependencies = this.dependencies.get(formulaPositionId);
35607
36390
  if (existingDependencies) {
35608
- existingDependencies.push(...dependencies);
36391
+ existingDependencies.push(...rTreeItems);
35609
36392
  }
35610
36393
  else {
35611
- this.dependencies.set(formulaPositionId, dependencies);
36394
+ this.dependencies.set(formulaPositionId, rTreeItems);
35612
36395
  }
35613
36396
  }
35614
36397
  /**
@@ -35616,20 +36399,20 @@
35616
36399
  * in the correct order they should be evaluated.
35617
36400
  * This is called a topological ordering (excluding cycles)
35618
36401
  */
35619
- getCellsDependingOn(positionIds) {
36402
+ getCellsDependingOn(ranges) {
35620
36403
  const visited = new JetSet();
35621
- const queue = Array.from(positionIds).reverse();
36404
+ const queue = Array.from(ranges).reverse();
35622
36405
  while (queue.length > 0) {
35623
- const node = queue.pop();
35624
- visited.add(node);
35625
- const adjacentNodes = this.inverseDependencies.get(node) || new Set();
35626
- for (const adjacentNode of adjacentNodes) {
35627
- if (!visited.has(adjacentNode)) {
35628
- queue.push(adjacentNode);
36406
+ const range = queue.pop();
36407
+ visited.add(...this.encoder.encodeBoundingBox(range));
36408
+ const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
36409
+ for (const positionId of impactedPositionIds) {
36410
+ if (!visited.has(positionId)) {
36411
+ queue.push(this.encoder.decodeToBoundingBox(positionId));
35629
36412
  }
35630
36413
  }
35631
36414
  }
35632
- visited.delete(...positionIds);
36415
+ visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
35633
36416
  return visited;
35634
36417
  }
35635
36418
  }
@@ -35717,9 +36500,9 @@
35717
36500
  context;
35718
36501
  getters;
35719
36502
  compilationParams;
35720
- positionEncoder = new PositionBitsEncoder();
36503
+ encoder = new PositionBitsEncoder();
35721
36504
  evaluatedCells = new Map();
35722
- formulaDependencies = lazy(new FormulaDependencyGraph());
36505
+ formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
35723
36506
  blockedArrayFormulas = new Set();
35724
36507
  spreadingRelations = new SpreadingRelation();
35725
36508
  constructor(context, getters) {
@@ -35728,16 +36511,16 @@
35728
36511
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
35729
36512
  }
35730
36513
  getEvaluatedCell(position) {
35731
- return (this.evaluatedCells.get(this.encodePosition(position)) ||
36514
+ return (this.evaluatedCells.get(this.encoder.encode(position)) ||
35732
36515
  createEvaluatedCell("", { locale: this.getters.getLocale() }));
35733
36516
  }
35734
36517
  getArrayFormulaSpreadingOn(position) {
35735
- const positionId = this.encodePosition(position);
36518
+ const positionId = this.encoder.encode(position);
35736
36519
  const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
35737
- return formulaPosition !== undefined ? this.decodePosition(formulaPosition) : undefined;
36520
+ return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
35738
36521
  }
35739
36522
  getEvaluatedPositions() {
35740
- return [...this.evaluatedCells.keys()].map(this.decodePosition.bind(this));
36523
+ return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
35741
36524
  }
35742
36525
  getArrayFormulaSpreadingOnId(positionId) {
35743
36526
  if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
@@ -35747,7 +36530,7 @@
35747
36530
  return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
35748
36531
  }
35749
36532
  updateDependencies(position) {
35750
- const positionId = this.encodePosition(position);
36533
+ const positionId = this.encoder.encode(position);
35751
36534
  this.formulaDependencies().removeAllDependencies(positionId);
35752
36535
  const dependencies = this.getDirectDependencies(positionId);
35753
36536
  this.formulaDependencies().addDependencies(positionId, dependencies);
@@ -35757,12 +36540,12 @@
35757
36540
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
35758
36541
  }
35759
36542
  evaluateCells(positions) {
35760
- const cells = positions.map(this.encodePosition.bind(this));
36543
+ const cells = positions.map((p) => this.encoder.encode(p));
35761
36544
  const cellsToCompute = new JetSet(cells);
35762
- const arrayFormulas = this.getArrayFormulasImpactedByChangesOf(cells);
36545
+ const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
35763
36546
  cellsToCompute.add(...this.getCellsDependingOn(cells));
35764
- cellsToCompute.add(...arrayFormulas);
35765
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulas));
36547
+ cellsToCompute.add(...arrayFormulasPositionIds);
36548
+ cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
35766
36549
  this.evaluate(cellsToCompute);
35767
36550
  }
35768
36551
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -35785,12 +36568,14 @@
35785
36568
  this.blockedArrayFormulas = new Set();
35786
36569
  this.spreadingRelations = new SpreadingRelation();
35787
36570
  this.formulaDependencies = lazy(() => {
35788
- const dependencyGraph = new FormulaDependencyGraph();
35789
- for (const positionId of this.getAllCells()) {
35790
- const dependencies = this.getDirectDependencies(positionId);
35791
- dependencyGraph.addDependencies(positionId, dependencies);
35792
- }
35793
- return dependencyGraph;
36571
+ const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
36572
+ data: positionId,
36573
+ boundingBox: {
36574
+ zone: range.zone,
36575
+ sheetId: range.sheetId,
36576
+ },
36577
+ })));
36578
+ return new FormulaDependencyGraph(this.encoder, dependencies);
35794
36579
  });
35795
36580
  }
35796
36581
  evaluateAllCells() {
@@ -35811,7 +36596,7 @@
35811
36596
  for (const sheetId of this.getters.getSheetIds()) {
35812
36597
  const cellIds = this.getters.getCells(sheetId);
35813
36598
  for (const cellId in cellIds) {
35814
- positionIds.add(this.encodePosition(this.getters.getCellPosition(cellId)));
36599
+ positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
35815
36600
  }
35816
36601
  }
35817
36602
  return positionIds;
@@ -35878,7 +36663,7 @@
35878
36663
  }
35879
36664
  }
35880
36665
  computeAndSave(position) {
35881
- const positionId = this.encodePosition(position);
36666
+ const positionId = this.encoder.encode(position);
35882
36667
  const evaluatedCell = this.computeCell(positionId);
35883
36668
  if (!this.evaluatedCells.has(positionId)) {
35884
36669
  this.setEvaluatedCell(positionId, evaluatedCell);
@@ -35939,15 +36724,15 @@
35939
36724
  throw new Error(_lt("Result couldn't be automatically expanded. Please insert more columns and rows."));
35940
36725
  }
35941
36726
  updateSpreadRelation({ sheetId, col, row, }) {
35942
- const arrayFormulaPositionId = this.encodePosition({ sheetId, col, row });
36727
+ const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
35943
36728
  return (i, j) => {
35944
36729
  const position = { sheetId, col: i + col, row: j + row };
35945
- const resultPositionId = this.encodePosition(position);
36730
+ const resultPositionId = this.encoder.encode(position);
35946
36731
  this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
35947
36732
  };
35948
36733
  }
35949
36734
  checkCollision({ sheetId, col, row }) {
35950
- const formulaPositionId = this.encodePosition({ sheetId, col, row });
36735
+ const formulaPositionId = this.encoder.encode({ sheetId, col, row });
35951
36736
  return (i, j) => {
35952
36737
  const position = { sheetId: sheetId, col: i + col, row: j + row };
35953
36738
  const rawCell = this.getters.getCell(position);
@@ -35969,7 +36754,7 @@
35969
36754
  format: format || formatFromPosition(i, j),
35970
36755
  locale: this.getters.getLocale(),
35971
36756
  });
35972
- const positionId = this.encodePosition(position);
36757
+ const positionId = this.encoder.encode(position);
35973
36758
  this.setEvaluatedCell(positionId, evaluatedCell);
35974
36759
  // check if formula dependencies present in the spread zone
35975
36760
  // if so, they need to be recomputed
@@ -36001,29 +36786,17 @@
36001
36786
  if (!cell?.isFormula) {
36002
36787
  return [];
36003
36788
  }
36004
- const dependencies = [];
36005
- for (const range of cell.dependencies) {
36006
- if (range.invalidSheetName || range.invalidXc) {
36007
- continue;
36008
- }
36009
- const sheetId = range.sheetId;
36010
- forEachPositionsInZone(range.zone, (col, row) => {
36011
- dependencies.push(this.encodePosition({ sheetId, col, row }));
36012
- });
36013
- }
36014
- return dependencies;
36789
+ return cell.dependencies;
36015
36790
  }
36016
36791
  getCellsDependingOn(positionIds) {
36017
- return this.formulaDependencies().getCellsDependingOn(positionIds);
36792
+ const ranges = [];
36793
+ for (const positionId of positionIds) {
36794
+ ranges.push(this.encoder.decodeToBoundingBox(positionId));
36795
+ }
36796
+ return this.formulaDependencies().getCellsDependingOn(ranges);
36018
36797
  }
36019
36798
  getCell(positionId) {
36020
- return this.getters.getCell(this.decodePosition(positionId));
36021
- }
36022
- encodePosition(position) {
36023
- return this.positionEncoder.encode(position);
36024
- }
36025
- decodePosition(positionId) {
36026
- return this.positionEncoder.decode(positionId);
36799
+ return this.getters.getCell(this.encoder.decode(positionId));
36027
36800
  }
36028
36801
  }
36029
36802
  function forEachSpreadPositionInMatrix(matrix, callback) {
@@ -36098,6 +36871,13 @@
36098
36871
  encode({ sheetId, col, row }) {
36099
36872
  return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
36100
36873
  }
36874
+ encodeBoundingBox({ sheetId, zone }) {
36875
+ const positions = [];
36876
+ forEachPositionsInZone(zone, (col, row) => {
36877
+ positions.push(this.encode({ sheetId, col, row }));
36878
+ });
36879
+ return positions;
36880
+ }
36101
36881
  decode(id) {
36102
36882
  // keep only the last 21 bits by AND-ing the bit sequence with 21 ones
36103
36883
  const row = Number(id & 2097151n);
@@ -36105,6 +36885,10 @@
36105
36885
  const sheetId = this.decodeSheet(id >> 42n);
36106
36886
  return { sheetId, col, row };
36107
36887
  }
36888
+ decodeToBoundingBox(id) {
36889
+ const { sheetId, col, row } = this.decode(id);
36890
+ return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
36891
+ }
36108
36892
  encodeSheet(sheetId) {
36109
36893
  const sheetKey = this.sheetMapping[sheetId];
36110
36894
  if (sheetKey === undefined) {
@@ -36219,6 +37003,7 @@
36219
37003
  class EvaluationPlugin extends UIPlugin {
36220
37004
  static getters = [
36221
37005
  "evaluateFormula",
37006
+ "getCorrespondingFormulaCell",
36222
37007
  "getRangeFormattedValues",
36223
37008
  "getRangeValues",
36224
37009
  "getRangeFormats",
@@ -36272,7 +37057,12 @@
36272
37057
  // Getters
36273
37058
  // ---------------------------------------------------------------------------
36274
37059
  evaluateFormula(sheetId, formulaString) {
36275
- return this.evaluator.evaluateFormula(sheetId, formulaString);
37060
+ try {
37061
+ return this.evaluator.evaluateFormula(sheetId, formulaString);
37062
+ }
37063
+ catch (error) {
37064
+ return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
37065
+ }
36276
37066
  }
36277
37067
  /**
36278
37068
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -36662,34 +37452,29 @@
36662
37452
  getComputedStyles(sheetId) {
36663
37453
  const computedStyle = {};
36664
37454
  for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
36665
- try {
36666
- switch (cf.rule.type) {
36667
- case "ColorScaleRule":
36668
- for (let range of cf.ranges) {
36669
- this.applyColorScale(sheetId, range, cf.rule, computedStyle);
36670
- }
36671
- break;
36672
- case "CellIsRule":
36673
- for (let ref of cf.ranges) {
36674
- const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
36675
- for (let row = zone.top; row <= zone.bottom; row++) {
36676
- for (let col = zone.left; col <= zone.right; col++) {
36677
- const pr = this.rulePredicate[cf.rule.type];
36678
- let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
36679
- if (pr && pr(cell, cf.rule)) {
36680
- if (!computedStyle[col])
36681
- computedStyle[col] = [];
36682
- // we must combine all the properties of all the CF rules applied to the given cell
36683
- computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
36684
- }
37455
+ switch (cf.rule.type) {
37456
+ case "ColorScaleRule":
37457
+ for (let range of cf.ranges) {
37458
+ this.applyColorScale(sheetId, range, cf.rule, computedStyle);
37459
+ }
37460
+ break;
37461
+ case "CellIsRule":
37462
+ for (let ref of cf.ranges) {
37463
+ const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
37464
+ for (let row = zone.top; row <= zone.bottom; row++) {
37465
+ for (let col = zone.left; col <= zone.right; col++) {
37466
+ const pr = this.rulePredicate[cf.rule.type];
37467
+ let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
37468
+ if (pr && pr(cell, cf.rule)) {
37469
+ if (!computedStyle[col])
37470
+ computedStyle[col] = [];
37471
+ // we must combine all the properties of all the CF rules applied to the given cell
37472
+ computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
36685
37473
  }
36686
37474
  }
36687
37475
  }
36688
- break;
36689
- }
36690
- }
36691
- catch (_) {
36692
- // we don't care about the errors within the evaluation of a rule
37476
+ }
37477
+ break;
36693
37478
  }
36694
37479
  }
36695
37480
  return computedStyle;
@@ -36725,7 +37510,7 @@
36725
37510
  return percentile(rangeValues, Number(threshold.value) / 100, true);
36726
37511
  case "formula":
36727
37512
  const value = threshold.value && this.getters.evaluateFormula(sheetId, threshold.value);
36728
- return !(value instanceof Promise) ? value : null;
37513
+ return typeof value === "number" ? value : null;
36729
37514
  default:
36730
37515
  return null;
36731
37516
  }
@@ -37292,7 +38077,7 @@
37292
38077
  let row = zone.bottom;
37293
38078
  if (col > 0) {
37294
38079
  let leftPosition = { sheetId, col: col - 1, row };
37295
- while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
38080
+ while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
37296
38081
  this.getters.getCell(leftPosition)?.content) {
37297
38082
  row += 1;
37298
38083
  leftPosition = { sheetId, col: col - 1, row };
@@ -37302,7 +38087,7 @@
37302
38087
  col = zone.right;
37303
38088
  if (col <= this.getters.getNumberCols(sheetId)) {
37304
38089
  let rightPosition = { sheetId, col: col + 1, row };
37305
- while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
38090
+ while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
37306
38091
  this.getters.getCell(rightPosition)?.content) {
37307
38092
  row += 1;
37308
38093
  rightPosition = { sheetId, col: col + 1, row };
@@ -47050,7 +47835,7 @@
47050
47835
  }
47051
47836
  onKeydown(ev) {
47052
47837
  let keyDownString = "";
47053
- if (ev.ctrlKey || ev.metaKey) {
47838
+ if (isCtrlKey(ev)) {
47054
47839
  keyDownString += "CTRL+";
47055
47840
  }
47056
47841
  keyDownString += ev.key.toUpperCase();
@@ -50811,9 +51596,9 @@
50811
51596
  Object.defineProperty(exports, '__esModule', { value: true });
50812
51597
 
50813
51598
 
50814
- __info__.version = '16.4.16';
50815
- __info__.date = '2023-12-05T09:37:14.210Z';
50816
- __info__.hash = '1abbb92';
51599
+ __info__.version = '16.4.18';
51600
+ __info__.date = '2024-01-12T13:43:10.143Z';
51601
+ __info__.hash = 'f438e08';
50817
51602
 
50818
51603
 
50819
51604
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);