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