@odoo/o-spreadsheet 17.0.5 → 17.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 17.0.5
6
- * @date 2023-12-05T09:51:44.657Z
7
- * @hash 2480a5d
5
+ * @version 17.0.7
6
+ * @date 2024-01-12T13:42:04.654Z
7
+ * @hash cadb817
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -330,13 +330,6 @@
330
330
  //------------------------------------------------------------------------------
331
331
  // Miscellaneous
332
332
  //------------------------------------------------------------------------------
333
- /**
334
- * Stringify an object, like JSON.stringify, except that the first level of keys
335
- * is ordered.
336
- */
337
- function stringify(obj) {
338
- return JSON.stringify(obj, Object.keys(obj).sort());
339
- }
340
333
  /**
341
334
  * Remove quotes from a quoted string
342
335
  * ```js
@@ -559,7 +552,7 @@
559
552
  */
560
553
  function getItemId(item, itemsDic) {
561
554
  for (let [key, value] of Object.entries(itemsDic)) {
562
- if (stringify(value) === stringify(item)) {
555
+ if (deepEquals(value, item)) {
563
556
  return parseInt(key, 10);
564
557
  }
565
558
  }
@@ -1209,16 +1202,84 @@
1209
1202
  // -----------------------------------------------------------------------------
1210
1203
  // Date Type
1211
1204
  // -----------------------------------------------------------------------------
1205
+ /**
1206
+ * A DateTime object that can be used to manipulate spreadsheet dates.
1207
+ * Conceptually, a spreadsheet date is simply a number with a date format,
1208
+ * and it is timezone-agnostic.
1209
+ * This DateTime object consistently uses UTC time to represent a naive date and time.
1210
+ */
1211
+ class DateTime {
1212
+ jsDate;
1213
+ constructor(year, month, day, hours = 0, minutes = 0, seconds = 0) {
1214
+ this.jsDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds, 0));
1215
+ }
1216
+ static fromTimestamp(timestamp) {
1217
+ const date = new Date(timestamp);
1218
+ return new DateTime(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
1219
+ }
1220
+ static now() {
1221
+ const now = new Date();
1222
+ return new DateTime(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds());
1223
+ }
1224
+ toString() {
1225
+ return this.jsDate.toString();
1226
+ }
1227
+ toLocaleDateString() {
1228
+ return this.jsDate.toLocaleDateString();
1229
+ }
1230
+ getTime() {
1231
+ return this.jsDate.getTime();
1232
+ }
1233
+ getFullYear() {
1234
+ return this.jsDate.getUTCFullYear();
1235
+ }
1236
+ getMonth() {
1237
+ return this.jsDate.getUTCMonth();
1238
+ }
1239
+ getDate() {
1240
+ return this.jsDate.getUTCDate();
1241
+ }
1242
+ getDay() {
1243
+ return this.jsDate.getUTCDay();
1244
+ }
1245
+ getHours() {
1246
+ return this.jsDate.getUTCHours();
1247
+ }
1248
+ getMinutes() {
1249
+ return this.jsDate.getUTCMinutes();
1250
+ }
1251
+ getSeconds() {
1252
+ return this.jsDate.getUTCSeconds();
1253
+ }
1254
+ setFullYear(year) {
1255
+ return this.jsDate.setUTCFullYear(year);
1256
+ }
1257
+ setMonth(month) {
1258
+ return this.jsDate.setUTCMonth(month);
1259
+ }
1260
+ setDate(date) {
1261
+ return this.jsDate.setUTCDate(date);
1262
+ }
1263
+ setHours(hours) {
1264
+ return this.jsDate.setUTCHours(hours);
1265
+ }
1266
+ setMinutes(minutes) {
1267
+ return this.jsDate.setUTCMinutes(minutes);
1268
+ }
1269
+ setSeconds(seconds) {
1270
+ return this.jsDate.setUTCSeconds(seconds);
1271
+ }
1272
+ }
1212
1273
  // -----------------------------------------------------------------------------
1213
1274
  // Parsing
1214
1275
  // -----------------------------------------------------------------------------
1215
- const INITIAL_1900_DAY = new Date(1899, 11, 30);
1276
+ const INITIAL_1900_DAY = new DateTime(1899, 11, 30);
1216
1277
  const MS_PER_DAY = 24 * 60 * 60 * 1000;
1217
1278
  const CURRENT_MILLENIAL = 2000; // note: don't forget to update this in 2999
1218
- const CURRENT_YEAR = new Date().getFullYear();
1219
- const CURRENT_MONTH = new Date().getMonth();
1220
- const INITIAL_JS_DAY = new Date(0);
1221
- const DATE_JS_1900_OFFSET = INITIAL_JS_DAY - INITIAL_1900_DAY;
1279
+ const CURRENT_YEAR = DateTime.now().getFullYear();
1280
+ const CURRENT_MONTH = DateTime.now().getMonth();
1281
+ const INITIAL_JS_DAY = DateTime.fromTimestamp(0);
1282
+ const DATE_JS_1900_OFFSET = INITIAL_JS_DAY.getTime() - INITIAL_1900_DAY.getTime();
1222
1283
  const mdyDateRegexp = /^\d{1,2}(\/|-|\s)\d{1,2}((\/|-|\s)\d{1,4})?$/;
1223
1284
  const ymdDateRegexp = /^\d{3,4}(\/|-|\s)\d{1,2}(\/|-|\s)\d{1,2}$/;
1224
1285
  const dateSeparatorsRegex = /\/|-|\s/;
@@ -1279,7 +1340,7 @@
1279
1340
  return {
1280
1341
  value: date.value + time.value,
1281
1342
  format: date.format + " " + (time.format === "hhhh:mm:ss" ? "hh:mm:ss" : time.format),
1282
- 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()),
1343
+ 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()),
1283
1344
  };
1284
1345
  }
1285
1346
  return date || time;
@@ -1355,12 +1416,12 @@
1355
1416
  // month + 1: months are 0-indexed in JS
1356
1417
  const leadingZero = (monthStr?.length === 2 && month + 1 < 10) || (dayStr?.length === 2 && day < 10);
1357
1418
  const fullYear = yearStr?.length !== 2;
1358
- const jsDate = new Date(year, month, day);
1419
+ const jsDate = new DateTime(year, month, day);
1359
1420
  if (jsDate.getMonth() !== month || jsDate.getDate() !== day) {
1360
1421
  // invalid date
1361
1422
  return null;
1362
1423
  }
1363
- const delta = jsDate - INITIAL_1900_DAY;
1424
+ const delta = jsDate.getTime() - INITIAL_1900_DAY.getTime();
1364
1425
  const format = getFormatFromDateParts(parts, separator, leadingZero, fullYear);
1365
1426
  return {
1366
1427
  value: Math.round(delta / MS_PER_DAY),
@@ -1451,7 +1512,7 @@
1451
1512
  if (hours >= 24) {
1452
1513
  format = "hhhh:mm:ss";
1453
1514
  }
1454
- const jsDate = new Date(1899, 11, 30, hours, minutes, seconds);
1515
+ const jsDate = new DateTime(1899, 11, 30, hours, minutes, seconds);
1455
1516
  return {
1456
1517
  value: hours / 24 + minutes / 1440 + seconds / 86400,
1457
1518
  format: format,
@@ -1465,7 +1526,7 @@
1465
1526
  // -----------------------------------------------------------------------------
1466
1527
  function numberToJsDate(value) {
1467
1528
  const truncValue = Math.trunc(value);
1468
- let date = new Date(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
1529
+ let date = DateTime.fromTimestamp(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
1469
1530
  let time = value - truncValue;
1470
1531
  time = time < 0 ? 1 + time : time;
1471
1532
  const hours = Math.round(time * 24);
@@ -1485,7 +1546,7 @@
1485
1546
  }
1486
1547
  /** Return the number of days in the current month of the given date */
1487
1548
  function getDaysInMonth(date) {
1488
- return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
1549
+ return new DateTime(date.getFullYear(), date.getMonth() + 1, 0).getDate();
1489
1550
  }
1490
1551
  function isLastDayOfMonth(date) {
1491
1552
  return getDaysInMonth(date) === date.getDate();
@@ -1503,7 +1564,7 @@
1503
1564
  const yStart = date.getFullYear();
1504
1565
  const mStart = date.getMonth();
1505
1566
  const dStart = date.getDate();
1506
- const jsDate = new Date(yStart, mStart + months);
1567
+ const jsDate = new DateTime(yStart, mStart + months, 1);
1507
1568
  if (keepEndOfMonth && dStart === getDaysInMonth(date)) {
1508
1569
  jsDate.setDate(getDaysInMonth(jsDate));
1509
1570
  }
@@ -2726,20 +2787,20 @@
2726
2787
  }
2727
2788
 
2728
2789
  function toCriterionDateNumber(dateValue) {
2729
- const today = new Date();
2790
+ const today = DateTime.now();
2730
2791
  switch (dateValue) {
2731
2792
  case "today":
2732
2793
  return jsDateToNumber(today);
2733
2794
  case "yesterday":
2734
- return jsDateToNumber(new Date(today.setDate(today.getDate() - 1)));
2795
+ return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 1)));
2735
2796
  case "tomorrow":
2736
- return jsDateToNumber(new Date(today.setDate(today.getDate() + 1)));
2797
+ return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() + 1)));
2737
2798
  case "lastWeek":
2738
- return jsDateToNumber(new Date(today.setDate(today.getDate() - 7)));
2799
+ return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 7)));
2739
2800
  case "lastMonth":
2740
- return jsDateToNumber(new Date(today.setMonth(today.getMonth() - 1)));
2801
+ return jsDateToNumber(DateTime.fromTimestamp(today.setMonth(today.getMonth() - 1)));
2741
2802
  case "lastYear":
2742
- return jsDateToNumber(new Date(today.setFullYear(today.getFullYear() - 1)));
2803
+ return jsDateToNumber(DateTime.fromTimestamp(today.setFullYear(today.getFullYear() - 1)));
2743
2804
  }
2744
2805
  }
2745
2806
  /** Get all the dates values of a criterion converted to numbers, converting date values such as "today" to actual dates */
@@ -3097,7 +3158,7 @@
3097
3158
  .map((p) => {
3098
3159
  switch (p) {
3099
3160
  case "hhhh":
3100
- const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY) / (60 * 60 * 1000));
3161
+ const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY.getTime()) / (60 * 60 * 1000));
3101
3162
  return helapsedHours.toString();
3102
3163
  case "hh":
3103
3164
  return hours.toString().padStart(2, "0");
@@ -5287,7 +5348,7 @@
5287
5348
  };
5288
5349
  baselineCell = getters.getEvaluatedCell(baselinePosition);
5289
5350
  }
5290
- const background = getters.getBackgroundOfSingleCellChart(chart.background, chart.keyValue);
5351
+ const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
5291
5352
  const locale = getters.getLocale();
5292
5353
  return {
5293
5354
  title: _t(chart.title),
@@ -5296,7 +5357,7 @@
5296
5357
  baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
5297
5358
  baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
5298
5359
  baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
5299
- fontColor: chartFontColor(background),
5360
+ fontColor,
5300
5361
  background,
5301
5362
  baselineStyle: chart.baselineMode !== "percentage" && baseline
5302
5363
  ? getters.getCellStyle({
@@ -6591,6 +6652,7 @@
6591
6652
  },
6592
6653
  };
6593
6654
 
6655
+ const macRegex = /Mac/i;
6594
6656
  /**
6595
6657
  * Return true if the event was triggered from
6596
6658
  * a child element.
@@ -6630,7 +6692,15 @@
6630
6692
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
6631
6693
  }
6632
6694
  function isMacOS() {
6633
- return navigator.userAgent.toUpperCase().indexOf("MAC") >= 0;
6695
+ return Boolean(macRegex.test(navigator.userAgent));
6696
+ }
6697
+ /**
6698
+ * @param {KeyboardEvent | MouseEvent} ev
6699
+ * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
6700
+ * On Mac, this is the "meta" or "command" key.
6701
+ */
6702
+ function isCtrlKey(ev) {
6703
+ return isMacOS() ? ev.metaKey : ev.ctrlKey;
6634
6704
  }
6635
6705
 
6636
6706
  /**
@@ -8879,7 +8949,7 @@
8879
8949
  });
8880
8950
  return {
8881
8951
  chartJsConfig: config,
8882
- background: getters.getBackgroundOfSingleCellChart(chart.background, dataRange),
8952
+ background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
8883
8953
  };
8884
8954
  }
8885
8955
 
@@ -8960,7 +9030,7 @@
8960
9030
  else if (format.includes("h") || format.includes("H")) {
8961
9031
  return "hour";
8962
9032
  }
8963
- else if (format.includes("D")) {
9033
+ else if (format.includes("d")) {
8964
9034
  return "day";
8965
9035
  }
8966
9036
  else if (format.includes("M")) {
@@ -10908,6 +10978,9 @@
10908
10978
  function makeArg(str, description) {
10909
10979
  let parts = str.match(ARG_REGEXP);
10910
10980
  let name = parts[1].trim();
10981
+ if (!name) {
10982
+ throw new Error(`Function argument definition is missing a name: '${str}'.`);
10983
+ }
10911
10984
  let types = [];
10912
10985
  let isOptional = false;
10913
10986
  let isRepeating = false;
@@ -14613,7 +14686,7 @@
14613
14686
  if (_year < 1900) {
14614
14687
  _year += 1900;
14615
14688
  }
14616
- const jsDate = new Date(_year, _month - 1, _day);
14689
+ const jsDate = new DateTime(_year, _month - 1, _day);
14617
14690
  const result = jsDateToRoundNumber(jsDate);
14618
14691
  assert(() => result >= 0, _t("The function [[FUNCTION_NAME]] result must be greater than or equal 01/01/1900."));
14619
14692
  return result;
@@ -14656,7 +14729,7 @@
14656
14729
  // See: https://support.microsoft.com/en-us/office/datedif-function-25dba1a4-2812-480b-84dd-8b32a451b35c
14657
14730
  let days = jsEndDate.getDate() - jsStartDate.getDate();
14658
14731
  if (days < 0) {
14659
- const monthBeforeEndMonth = new Date(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
14732
+ const monthBeforeEndMonth = new DateTime(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
14660
14733
  const daysInMonthBeforeEndMonth = getDaysInMonth(monthBeforeEndMonth);
14661
14734
  days = daysInMonthBeforeEndMonth - Math.abs(days);
14662
14735
  }
@@ -14665,7 +14738,7 @@
14665
14738
  if (areTwoDatesWithinOneYear(_startDate, _endDate)) {
14666
14739
  return getTimeDifferenceInWholeDays(jsStartDate, jsEndDate);
14667
14740
  }
14668
- const endDateWithinOneYear = new Date(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
14741
+ const endDateWithinOneYear = new DateTime(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
14669
14742
  let days = getTimeDifferenceInWholeDays(jsStartDate, endDateWithinOneYear);
14670
14743
  if (days < 0) {
14671
14744
  endDateWithinOneYear.setFullYear(jsStartDate.getFullYear() + 1);
@@ -14782,7 +14855,7 @@
14782
14855
  const _months = Math.trunc(toNumber(months, this.locale));
14783
14856
  const yStart = _startDate.getFullYear();
14784
14857
  const mStart = _startDate.getMonth();
14785
- const jsDate = new Date(yStart, mStart + _months + 1, 0);
14858
+ const jsDate = new DateTime(yStart, mStart + _months + 1, 0);
14786
14859
  return jsDateToRoundNumber(jsDate);
14787
14860
  },
14788
14861
  isExported: true,
@@ -14819,17 +14892,17 @@
14819
14892
  // The first week of the year is the week that contains the first
14820
14893
  // Thursday of the year.
14821
14894
  let firstThursday = 1;
14822
- while (new Date(y, 0, firstThursday).getDay() !== 4) {
14895
+ while (new DateTime(y, 0, firstThursday).getDay() !== 4) {
14823
14896
  firstThursday += 1;
14824
14897
  }
14825
- const firstDayOfFirstWeek = new Date(y, 0, firstThursday - 3);
14898
+ const firstDayOfFirstWeek = new DateTime(y, 0, firstThursday - 3);
14826
14899
  // The last week of the year is the week that contains the last Thursday of
14827
14900
  // the year.
14828
14901
  let lastThursday = 31;
14829
- while (new Date(y, 11, lastThursday).getDay() !== 4) {
14902
+ while (new DateTime(y, 11, lastThursday).getDay() !== 4) {
14830
14903
  lastThursday -= 1;
14831
14904
  }
14832
- const lastDayOfLastWeek = new Date(y, 11, lastThursday + 3);
14905
+ const lastDayOfLastWeek = new DateTime(y, 11, lastThursday + 3);
14833
14906
  // B - If our date > lastDayOfLastWeek then it's in the weeks of the year after
14834
14907
  // If our date < firstDayOfFirstWeek then it's in the weeks of the year before
14835
14908
  let offsetYear;
@@ -14855,17 +14928,17 @@
14855
14928
  case 1:
14856
14929
  // firstDay is the 1st day of the 1st week of the year after
14857
14930
  // firstDay = lastDayOfLastWeek + 1 Day
14858
- firstDay = new Date(y, 11, lastThursday + 3 + 1);
14931
+ firstDay = new DateTime(y, 11, lastThursday + 3 + 1);
14859
14932
  break;
14860
14933
  case -1:
14861
14934
  // firstDay is the 1st day of the 1st week of the previous year.
14862
14935
  // The first week of the previous year is the week that contains the
14863
14936
  // first Thursday of the previous year.
14864
14937
  let firstThursdayPreviousYear = 1;
14865
- while (new Date(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
14938
+ while (new DateTime(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
14866
14939
  firstThursdayPreviousYear += 1;
14867
14940
  }
14868
- firstDay = new Date(y - 1, 0, firstThursdayPreviousYear - 3);
14941
+ firstDay = new DateTime(y - 1, 0, firstThursdayPreviousYear - 3);
14869
14942
  break;
14870
14943
  }
14871
14944
  const diff = (_date.getTime() - firstDay.getTime()) / MS_PER_DAY;
@@ -15000,8 +15073,8 @@
15000
15073
  });
15001
15074
  }
15002
15075
  const invertDate = _startDate.getTime() > _endDate.getTime();
15003
- const stopDate = new Date((invertDate ? _startDate : _endDate).getTime());
15004
- let stepDate = new Date((invertDate ? _endDate : _startDate).getTime());
15076
+ const stopDate = DateTime.fromTimestamp((invertDate ? _startDate : _endDate).getTime());
15077
+ let stepDate = DateTime.fromTimestamp((invertDate ? _endDate : _startDate).getTime());
15005
15078
  const timeStopDate = stopDate.getTime();
15006
15079
  let timeStepDate = stepDate.getTime();
15007
15080
  let netWorkingDay = 0;
@@ -15027,8 +15100,7 @@
15027
15100
  return getDateTimeFormat(this.locale);
15028
15101
  },
15029
15102
  compute: function () {
15030
- let today = new Date();
15031
- today.setMilliseconds(0);
15103
+ let today = DateTime.now();
15032
15104
  const delta = today.getTime() - INITIAL_1900_DAY.getTime();
15033
15105
  const time = today.getHours() / 24 + today.getMinutes() / 1440 + today.getSeconds() / 86400;
15034
15106
  return Math.floor(delta / MS_PER_DAY) + time;
@@ -15102,8 +15174,8 @@
15102
15174
  return this.locale.dateFormat;
15103
15175
  },
15104
15176
  compute: function () {
15105
- const today = new Date();
15106
- const jsDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());
15177
+ const today = DateTime.now();
15178
+ const jsDate = new DateTime(today.getFullYear(), today.getMonth(), today.getDate());
15107
15179
  return jsDateToRoundNumber(jsDate);
15108
15180
  },
15109
15181
  isExported: true,
@@ -15158,10 +15230,10 @@
15158
15230
  }
15159
15231
  const y = _date.getFullYear();
15160
15232
  let dayStart = 1;
15161
- let startDayOfFirstWeek = new Date(y, 0, dayStart);
15233
+ let startDayOfFirstWeek = new DateTime(y, 0, dayStart);
15162
15234
  while (startDayOfFirstWeek.getDay() !== startDayOfWeek) {
15163
15235
  dayStart += 1;
15164
- startDayOfFirstWeek = new Date(y, 0, dayStart);
15236
+ startDayOfFirstWeek = new DateTime(y, 0, dayStart);
15165
15237
  }
15166
15238
  const dif = (_date.getTime() - startDayOfFirstWeek.getTime()) / MS_PER_DAY;
15167
15239
  if (dif < 0) {
@@ -15219,7 +15291,7 @@
15219
15291
  timesHoliday.add(holiday.getTime());
15220
15292
  });
15221
15293
  }
15222
- let stepDate = new Date(_startDate.getTime());
15294
+ let stepDate = DateTime.fromTimestamp(_startDate.getTime());
15223
15295
  let timeStepDate = stepDate.getTime();
15224
15296
  const unitDay = Math.sign(_numDays);
15225
15297
  let stepDay = Math.abs(_numDays);
@@ -15283,7 +15355,7 @@
15283
15355
  const _startDate = toJsDate(date, this.locale);
15284
15356
  const yStart = _startDate.getFullYear();
15285
15357
  const mStart = _startDate.getMonth();
15286
- const jsDate = new Date(yStart, mStart, 1);
15358
+ const jsDate = new DateTime(yStart, mStart, 1);
15287
15359
  return jsDateToRoundNumber(jsDate);
15288
15360
  },
15289
15361
  };
@@ -15325,7 +15397,7 @@
15325
15397
  compute: function (date) {
15326
15398
  const quarter = QUARTER.compute.bind(this)(date);
15327
15399
  const year = YEAR.compute.bind(this)(date);
15328
- const jsDate = new Date(year, (quarter - 1) * 3, 1);
15400
+ const jsDate = new DateTime(year, (quarter - 1) * 3, 1);
15329
15401
  return jsDateToRoundNumber(jsDate);
15330
15402
  },
15331
15403
  };
@@ -15342,7 +15414,7 @@
15342
15414
  compute: function (date) {
15343
15415
  const quarter = QUARTER.compute.bind(this)(date);
15344
15416
  const year = YEAR.compute.bind(this)(date);
15345
- const jsDate = new Date(year, quarter * 3, 0);
15417
+ const jsDate = new DateTime(year, quarter * 3, 0);
15346
15418
  return jsDateToRoundNumber(jsDate);
15347
15419
  },
15348
15420
  };
@@ -15358,7 +15430,7 @@
15358
15430
  },
15359
15431
  compute: function (date) {
15360
15432
  const year = YEAR.compute.bind(this)(date);
15361
- const jsDate = new Date(year, 0, 1);
15433
+ const jsDate = new DateTime(year, 0, 1);
15362
15434
  return jsDateToRoundNumber(jsDate);
15363
15435
  },
15364
15436
  };
@@ -15374,7 +15446,7 @@
15374
15446
  },
15375
15447
  compute: function (date) {
15376
15448
  const year = YEAR.compute.bind(this)(date);
15377
- const jsDate = new Date(year + 1, 0, 0);
15449
+ const jsDate = new DateTime(year + 1, 0, 0);
15378
15450
  return jsDateToRoundNumber(jsDate);
15379
15451
  },
15380
15452
  };
@@ -15422,8 +15494,8 @@
15422
15494
  const DELTA = {
15423
15495
  description: _t("Compare two numeric values, returning 1 if they're equal."),
15424
15496
  args: [
15425
- arg(" (number)", _t("The first number to compare.")),
15426
- arg(` (number, default=${DEFAULT_DELTA_ARG})`, _t("The second number to compare.")),
15497
+ arg("number1 (number)", _t("The first number to compare.")),
15498
+ arg(`number2 (number, default=${DEFAULT_DELTA_ARG})`, _t("The second number to compare.")),
15427
15499
  ],
15428
15500
  returns: ["NUMBER"],
15429
15501
  compute: function (number1, number2 = DEFAULT_DELTA_ARG) {
@@ -15850,7 +15922,7 @@
15850
15922
  function assertSettlementLessThanOneYearBeforeMaturity(settlement, maturity, locale) {
15851
15923
  const startDate = toJsDate(settlement, locale);
15852
15924
  const endDate = toJsDate(maturity, locale);
15853
- const startDatePlusOneYear = new Date(startDate);
15925
+ const startDatePlusOneYear = toJsDate(settlement, locale);
15854
15926
  startDatePlusOneYear.setFullYear(startDate.getFullYear() + 1);
15855
15927
  assert(() => endDate.getTime() <= startDatePlusOneYear.getTime(), _t("The settlement date (%s) must at most one year after the maturity date (%s).", settlement.toString(), maturity.toString()));
15856
15928
  }
@@ -15985,7 +16057,7 @@
15985
16057
  arg("salvage (number)", _t("The value of the asset at the end of depreciation.")),
15986
16058
  arg("period (number)", _t("The single period within life for which to calculate depreciation.")),
15987
16059
  arg("rate (number)", _t("The deprecation rate.")),
15988
- arg(" (number, optional)", _t("An indicator of what day count method to use.")),
16060
+ arg("day_count_convention (number, optional)", _t("An indicator of what day count method to use.")),
15989
16061
  ],
15990
16062
  returns: ["NUMBER"],
15991
16063
  compute: function (cost, purchaseDate, firstPeriodEnd, salvage, period, rate, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {
@@ -19009,8 +19081,8 @@
19009
19081
  description: _t("A segment of a string."),
19010
19082
  args: [
19011
19083
  arg("text (string)", _t("The string to extract a segment from.")),
19012
- arg(" (number)", _t("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
19013
- arg(" (number)", _t("The length of the segment to extract.")),
19084
+ arg("starting_at (number)", _t("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
19085
+ arg("extract_length (number)", _t("The length of the segment to extract.")),
19014
19086
  ],
19015
19087
  returns: ["STRING"],
19016
19088
  compute: function (text, starting_at, extract_length) {
@@ -21213,10 +21285,10 @@
21213
21285
  function updateSelectionWithArrowKeys(ev, selection) {
21214
21286
  const direction = arrowMap[ev.key];
21215
21287
  if (ev.shiftKey) {
21216
- selection.resizeAnchorZone(direction, ev.ctrlKey ? "end" : 1);
21288
+ selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
21217
21289
  }
21218
21290
  else {
21219
- selection.moveAnchorCell(direction, ev.ctrlKey ? "end" : 1);
21291
+ selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
21220
21292
  }
21221
21293
  }
21222
21294
 
@@ -24204,7 +24276,7 @@
24204
24276
  return false;
24205
24277
  }
24206
24278
  if (["lastWeek", "lastMonth", "lastYear"].includes(criterion.dateValue)) {
24207
- const today = jsDateToRoundNumber(new Date());
24279
+ const today = jsDateToRoundNumber(DateTime.now());
24208
24280
  return isDateBetween(dateValue, today, criterionValue);
24209
24281
  }
24210
24282
  return areDatesSameDay(dateValue, criterionValue);
@@ -28201,7 +28273,10 @@
28201
28273
  return;
28202
28274
  }
28203
28275
  const [col, row] = this.getCartesianCoordinates(ev);
28204
- this.props.onCellClicked(col, row, { shiftKey: ev.shiftKey, ctrlKey: ev.ctrlKey });
28276
+ this.props.onCellClicked(col, row, {
28277
+ expandZone: ev.shiftKey,
28278
+ addZone: isCtrlKey(ev),
28279
+ });
28205
28280
  }
28206
28281
  onDoubleClick(ev) {
28207
28282
  const [col, row] = this.getCartesianCoordinates(ev);
@@ -28431,7 +28506,7 @@
28431
28506
  this._increaseSelection(index);
28432
28507
  }
28433
28508
  else {
28434
- this._selectElement(index, ev.ctrlKey);
28509
+ this._selectElement(index, isCtrlKey(ev));
28435
28510
  }
28436
28511
  this.lastSelectedElementIndex = index;
28437
28512
  const mouseMoveSelect = (col, row) => {
@@ -28595,8 +28670,8 @@
28595
28670
  this.env.raiseError(MergeErrorMessage);
28596
28671
  }
28597
28672
  }
28598
- _selectElement(index, ctrlKey) {
28599
- this.env.model.selection.selectColumn(index, ctrlKey ? "newAnchor" : "overrideSelection");
28673
+ _selectElement(index, addDistinctHeader) {
28674
+ this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
28600
28675
  }
28601
28676
  _increaseSelection(index) {
28602
28677
  this.env.model.selection.selectColumn(index, "updateAnchor");
@@ -28769,8 +28844,8 @@
28769
28844
  this.env.raiseError(MergeErrorMessage);
28770
28845
  }
28771
28846
  }
28772
- _selectElement(index, ctrlKey) {
28773
- this.env.model.selection.selectRow(index, ctrlKey ? "newAnchor" : "overrideSelection");
28847
+ _selectElement(index, addDistinctHeader) {
28848
+ this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
28774
28849
  }
28775
28850
  _increaseSelection(index) {
28776
28851
  this.env.model.selection.selectRow(index, "updateAnchor");
@@ -29572,17 +29647,17 @@
29572
29647
  // ---------------------------------------------------------------------------
29573
29648
  // Zone selection with mouse
29574
29649
  // ---------------------------------------------------------------------------
29575
- onCellClicked(col, row, { ctrlKey, shiftKey }) {
29650
+ onCellClicked(col, row, { addZone, expandZone }) {
29576
29651
  if (this.env.model.getters.hasOpenedPopover()) {
29577
29652
  this.closeOpenedPopover();
29578
29653
  }
29579
29654
  if (this.env.model.getters.getEditionMode() === "editing") {
29580
29655
  interactiveStopEdition(this.env);
29581
29656
  }
29582
- if (shiftKey) {
29657
+ if (expandZone) {
29583
29658
  this.env.model.selection.setAnchorCorner(col, row);
29584
29659
  }
29585
- else if (ctrlKey) {
29660
+ else if (addZone) {
29586
29661
  this.env.model.selection.addCellToSelection(col, row);
29587
29662
  }
29588
29663
  else {
@@ -29643,7 +29718,7 @@
29643
29718
  onKeydown(ev) {
29644
29719
  const eventKey = ev.key.toUpperCase();
29645
29720
  let keyDownString = "";
29646
- if (ev.ctrlKey && eventKey !== "CTRL")
29721
+ if (isCtrlKey(ev) && eventKey !== "CTRL")
29647
29722
  keyDownString += "CTRL+";
29648
29723
  if (ev.metaKey)
29649
29724
  keyDownString += "CTRL+";
@@ -34534,7 +34609,7 @@
34534
34609
  */
34535
34610
  function getBorderId(border) {
34536
34611
  for (let [key, value] of Object.entries(borders)) {
34537
- if (stringify(value) === stringify(border)) {
34612
+ if (deepEquals(value, border)) {
34538
34613
  return parseInt(key, 10);
34539
34614
  }
34540
34615
  }
@@ -39853,56 +39928,758 @@
39853
39928
  }
39854
39929
  }
39855
39930
 
39931
+ function quickselect(arr, k, left, right, compare) {
39932
+ quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
39933
+ }
39934
+
39935
+ function quickselectStep(arr, k, left, right, compare) {
39936
+
39937
+ while (right > left) {
39938
+ if (right - left > 600) {
39939
+ var n = right - left + 1;
39940
+ var m = k - left + 1;
39941
+ var z = Math.log(n);
39942
+ var s = 0.5 * Math.exp(2 * z / 3);
39943
+ var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
39944
+ var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
39945
+ var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
39946
+ quickselectStep(arr, k, newLeft, newRight, compare);
39947
+ }
39948
+
39949
+ var t = arr[k];
39950
+ var i = left;
39951
+ var j = right;
39952
+
39953
+ swap(arr, left, k);
39954
+ if (compare(arr[right], t) > 0) swap(arr, left, right);
39955
+
39956
+ while (i < j) {
39957
+ swap(arr, i, j);
39958
+ i++;
39959
+ j--;
39960
+ while (compare(arr[i], t) < 0) i++;
39961
+ while (compare(arr[j], t) > 0) j--;
39962
+ }
39963
+
39964
+ if (compare(arr[left], t) === 0) swap(arr, left, j);
39965
+ else {
39966
+ j++;
39967
+ swap(arr, j, right);
39968
+ }
39969
+
39970
+ if (j <= k) left = j + 1;
39971
+ if (k <= j) right = j - 1;
39972
+ }
39973
+ }
39974
+
39975
+ function swap(arr, i, j) {
39976
+ var tmp = arr[i];
39977
+ arr[i] = arr[j];
39978
+ arr[j] = tmp;
39979
+ }
39980
+
39981
+ function defaultCompare(a, b) {
39982
+ return a < b ? -1 : a > b ? 1 : 0;
39983
+ }
39984
+
39985
+ class RBush {
39986
+ constructor(maxEntries = 9) {
39987
+ // max entries in a node is 9 by default; min node fill is 40% for best performance
39988
+ this._maxEntries = Math.max(4, maxEntries);
39989
+ this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
39990
+ this.clear();
39991
+ }
39992
+
39993
+ all() {
39994
+ return this._all(this.data, []);
39995
+ }
39996
+
39997
+ search(bbox) {
39998
+ let node = this.data;
39999
+ const result = [];
40000
+
40001
+ if (!intersects(bbox, node)) return result;
40002
+
40003
+ const toBBox = this.toBBox;
40004
+ const nodesToSearch = [];
40005
+
40006
+ while (node) {
40007
+ for (let i = 0; i < node.children.length; i++) {
40008
+ const child = node.children[i];
40009
+ const childBBox = node.leaf ? toBBox(child) : child;
40010
+
40011
+ if (intersects(bbox, childBBox)) {
40012
+ if (node.leaf) result.push(child);
40013
+ else if (contains(bbox, childBBox)) this._all(child, result);
40014
+ else nodesToSearch.push(child);
40015
+ }
40016
+ }
40017
+ node = nodesToSearch.pop();
40018
+ }
40019
+
40020
+ return result;
40021
+ }
40022
+
40023
+ collides(bbox) {
40024
+ let node = this.data;
40025
+
40026
+ if (!intersects(bbox, node)) return false;
40027
+
40028
+ const nodesToSearch = [];
40029
+ while (node) {
40030
+ for (let i = 0; i < node.children.length; i++) {
40031
+ const child = node.children[i];
40032
+ const childBBox = node.leaf ? this.toBBox(child) : child;
40033
+
40034
+ if (intersects(bbox, childBBox)) {
40035
+ if (node.leaf || contains(bbox, childBBox)) return true;
40036
+ nodesToSearch.push(child);
40037
+ }
40038
+ }
40039
+ node = nodesToSearch.pop();
40040
+ }
40041
+
40042
+ return false;
40043
+ }
40044
+
40045
+ load(data) {
40046
+ if (!(data && data.length)) return this;
40047
+
40048
+ if (data.length < this._minEntries) {
40049
+ for (let i = 0; i < data.length; i++) {
40050
+ this.insert(data[i]);
40051
+ }
40052
+ return this;
40053
+ }
40054
+
40055
+ // recursively build the tree with the given data from scratch using OMT algorithm
40056
+ let node = this._build(data.slice(), 0, data.length - 1, 0);
40057
+
40058
+ if (!this.data.children.length) {
40059
+ // save as is if tree is empty
40060
+ this.data = node;
40061
+
40062
+ } else if (this.data.height === node.height) {
40063
+ // split root if trees have the same height
40064
+ this._splitRoot(this.data, node);
40065
+
40066
+ } else {
40067
+ if (this.data.height < node.height) {
40068
+ // swap trees if inserted one is bigger
40069
+ const tmpNode = this.data;
40070
+ this.data = node;
40071
+ node = tmpNode;
40072
+ }
40073
+
40074
+ // insert the small tree into the large tree at appropriate level
40075
+ this._insert(node, this.data.height - node.height - 1, true);
40076
+ }
40077
+
40078
+ return this;
40079
+ }
40080
+
40081
+ insert(item) {
40082
+ if (item) this._insert(item, this.data.height - 1);
40083
+ return this;
40084
+ }
40085
+
40086
+ clear() {
40087
+ this.data = createNode([]);
40088
+ return this;
40089
+ }
40090
+
40091
+ remove(item, equalsFn) {
40092
+ if (!item) return this;
40093
+
40094
+ let node = this.data;
40095
+ const bbox = this.toBBox(item);
40096
+ const path = [];
40097
+ const indexes = [];
40098
+ let i, parent, goingUp;
40099
+
40100
+ // depth-first iterative tree traversal
40101
+ while (node || path.length) {
40102
+
40103
+ if (!node) { // go up
40104
+ node = path.pop();
40105
+ parent = path[path.length - 1];
40106
+ i = indexes.pop();
40107
+ goingUp = true;
40108
+ }
40109
+
40110
+ if (node.leaf) { // check current node
40111
+ const index = findItem(item, node.children, equalsFn);
40112
+
40113
+ if (index !== -1) {
40114
+ // item found, remove the item and condense tree upwards
40115
+ node.children.splice(index, 1);
40116
+ path.push(node);
40117
+ this._condense(path);
40118
+ return this;
40119
+ }
40120
+ }
40121
+
40122
+ if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
40123
+ path.push(node);
40124
+ indexes.push(i);
40125
+ i = 0;
40126
+ parent = node;
40127
+ node = node.children[0];
40128
+
40129
+ } else if (parent) { // go right
40130
+ i++;
40131
+ node = parent.children[i];
40132
+ goingUp = false;
40133
+
40134
+ } else node = null; // nothing found
40135
+ }
40136
+
40137
+ return this;
40138
+ }
40139
+
40140
+ toBBox(item) { return item; }
40141
+
40142
+ compareMinX(a, b) { return a.minX - b.minX; }
40143
+ compareMinY(a, b) { return a.minY - b.minY; }
40144
+
40145
+ toJSON() { return this.data; }
40146
+
40147
+ fromJSON(data) {
40148
+ this.data = data;
40149
+ return this;
40150
+ }
40151
+
40152
+ _all(node, result) {
40153
+ const nodesToSearch = [];
40154
+ while (node) {
40155
+ if (node.leaf) result.push(...node.children);
40156
+ else nodesToSearch.push(...node.children);
40157
+
40158
+ node = nodesToSearch.pop();
40159
+ }
40160
+ return result;
40161
+ }
40162
+
40163
+ _build(items, left, right, height) {
40164
+
40165
+ const N = right - left + 1;
40166
+ let M = this._maxEntries;
40167
+ let node;
40168
+
40169
+ if (N <= M) {
40170
+ // reached leaf level; return leaf
40171
+ node = createNode(items.slice(left, right + 1));
40172
+ calcBBox(node, this.toBBox);
40173
+ return node;
40174
+ }
40175
+
40176
+ if (!height) {
40177
+ // target height of the bulk-loaded tree
40178
+ height = Math.ceil(Math.log(N) / Math.log(M));
40179
+
40180
+ // target number of root entries to maximize storage utilization
40181
+ M = Math.ceil(N / Math.pow(M, height - 1));
40182
+ }
40183
+
40184
+ node = createNode([]);
40185
+ node.leaf = false;
40186
+ node.height = height;
40187
+
40188
+ // split the items into M mostly square tiles
40189
+
40190
+ const N2 = Math.ceil(N / M);
40191
+ const N1 = N2 * Math.ceil(Math.sqrt(M));
40192
+
40193
+ multiSelect(items, left, right, N1, this.compareMinX);
40194
+
40195
+ for (let i = left; i <= right; i += N1) {
40196
+
40197
+ const right2 = Math.min(i + N1 - 1, right);
40198
+
40199
+ multiSelect(items, i, right2, N2, this.compareMinY);
40200
+
40201
+ for (let j = i; j <= right2; j += N2) {
40202
+
40203
+ const right3 = Math.min(j + N2 - 1, right2);
40204
+
40205
+ // pack each entry recursively
40206
+ node.children.push(this._build(items, j, right3, height - 1));
40207
+ }
40208
+ }
40209
+
40210
+ calcBBox(node, this.toBBox);
40211
+
40212
+ return node;
40213
+ }
40214
+
40215
+ _chooseSubtree(bbox, node, level, path) {
40216
+ while (true) {
40217
+ path.push(node);
40218
+
40219
+ if (node.leaf || path.length - 1 === level) break;
40220
+
40221
+ let minArea = Infinity;
40222
+ let minEnlargement = Infinity;
40223
+ let targetNode;
40224
+
40225
+ for (let i = 0; i < node.children.length; i++) {
40226
+ const child = node.children[i];
40227
+ const area = bboxArea(child);
40228
+ const enlargement = enlargedArea(bbox, child) - area;
40229
+
40230
+ // choose entry with the least area enlargement
40231
+ if (enlargement < minEnlargement) {
40232
+ minEnlargement = enlargement;
40233
+ minArea = area < minArea ? area : minArea;
40234
+ targetNode = child;
40235
+
40236
+ } else if (enlargement === minEnlargement) {
40237
+ // otherwise choose one with the smallest area
40238
+ if (area < minArea) {
40239
+ minArea = area;
40240
+ targetNode = child;
40241
+ }
40242
+ }
40243
+ }
40244
+
40245
+ node = targetNode || node.children[0];
40246
+ }
40247
+
40248
+ return node;
40249
+ }
40250
+
40251
+ _insert(item, level, isNode) {
40252
+ const bbox = isNode ? item : this.toBBox(item);
40253
+ const insertPath = [];
40254
+
40255
+ // find the best node for accommodating the item, saving all nodes along the path too
40256
+ const node = this._chooseSubtree(bbox, this.data, level, insertPath);
40257
+
40258
+ // put the item into the node
40259
+ node.children.push(item);
40260
+ extend(node, bbox);
40261
+
40262
+ // split on node overflow; propagate upwards if necessary
40263
+ while (level >= 0) {
40264
+ if (insertPath[level].children.length > this._maxEntries) {
40265
+ this._split(insertPath, level);
40266
+ level--;
40267
+ } else break;
40268
+ }
40269
+
40270
+ // adjust bboxes along the insertion path
40271
+ this._adjustParentBBoxes(bbox, insertPath, level);
40272
+ }
40273
+
40274
+ // split overflowed node into two
40275
+ _split(insertPath, level) {
40276
+ const node = insertPath[level];
40277
+ const M = node.children.length;
40278
+ const m = this._minEntries;
40279
+
40280
+ this._chooseSplitAxis(node, m, M);
40281
+
40282
+ const splitIndex = this._chooseSplitIndex(node, m, M);
40283
+
40284
+ const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
40285
+ newNode.height = node.height;
40286
+ newNode.leaf = node.leaf;
40287
+
40288
+ calcBBox(node, this.toBBox);
40289
+ calcBBox(newNode, this.toBBox);
40290
+
40291
+ if (level) insertPath[level - 1].children.push(newNode);
40292
+ else this._splitRoot(node, newNode);
40293
+ }
40294
+
40295
+ _splitRoot(node, newNode) {
40296
+ // split root node
40297
+ this.data = createNode([node, newNode]);
40298
+ this.data.height = node.height + 1;
40299
+ this.data.leaf = false;
40300
+ calcBBox(this.data, this.toBBox);
40301
+ }
40302
+
40303
+ _chooseSplitIndex(node, m, M) {
40304
+ let index;
40305
+ let minOverlap = Infinity;
40306
+ let minArea = Infinity;
40307
+
40308
+ for (let i = m; i <= M - m; i++) {
40309
+ const bbox1 = distBBox(node, 0, i, this.toBBox);
40310
+ const bbox2 = distBBox(node, i, M, this.toBBox);
40311
+
40312
+ const overlap = intersectionArea(bbox1, bbox2);
40313
+ const area = bboxArea(bbox1) + bboxArea(bbox2);
40314
+
40315
+ // choose distribution with minimum overlap
40316
+ if (overlap < minOverlap) {
40317
+ minOverlap = overlap;
40318
+ index = i;
40319
+
40320
+ minArea = area < minArea ? area : minArea;
40321
+
40322
+ } else if (overlap === minOverlap) {
40323
+ // otherwise choose distribution with minimum area
40324
+ if (area < minArea) {
40325
+ minArea = area;
40326
+ index = i;
40327
+ }
40328
+ }
40329
+ }
40330
+
40331
+ return index || M - m;
40332
+ }
40333
+
40334
+ // sorts node children by the best axis for split
40335
+ _chooseSplitAxis(node, m, M) {
40336
+ const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
40337
+ const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
40338
+ const xMargin = this._allDistMargin(node, m, M, compareMinX);
40339
+ const yMargin = this._allDistMargin(node, m, M, compareMinY);
40340
+
40341
+ // if total distributions margin value is minimal for x, sort by minX,
40342
+ // otherwise it's already sorted by minY
40343
+ if (xMargin < yMargin) node.children.sort(compareMinX);
40344
+ }
40345
+
40346
+ // total margin of all possible split distributions where each node is at least m full
40347
+ _allDistMargin(node, m, M, compare) {
40348
+ node.children.sort(compare);
40349
+
40350
+ const toBBox = this.toBBox;
40351
+ const leftBBox = distBBox(node, 0, m, toBBox);
40352
+ const rightBBox = distBBox(node, M - m, M, toBBox);
40353
+ let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
40354
+
40355
+ for (let i = m; i < M - m; i++) {
40356
+ const child = node.children[i];
40357
+ extend(leftBBox, node.leaf ? toBBox(child) : child);
40358
+ margin += bboxMargin(leftBBox);
40359
+ }
40360
+
40361
+ for (let i = M - m - 1; i >= m; i--) {
40362
+ const child = node.children[i];
40363
+ extend(rightBBox, node.leaf ? toBBox(child) : child);
40364
+ margin += bboxMargin(rightBBox);
40365
+ }
40366
+
40367
+ return margin;
40368
+ }
40369
+
40370
+ _adjustParentBBoxes(bbox, path, level) {
40371
+ // adjust bboxes along the given tree path
40372
+ for (let i = level; i >= 0; i--) {
40373
+ extend(path[i], bbox);
40374
+ }
40375
+ }
40376
+
40377
+ _condense(path) {
40378
+ // go through the path, removing empty nodes and updating bboxes
40379
+ for (let i = path.length - 1, siblings; i >= 0; i--) {
40380
+ if (path[i].children.length === 0) {
40381
+ if (i > 0) {
40382
+ siblings = path[i - 1].children;
40383
+ siblings.splice(siblings.indexOf(path[i]), 1);
40384
+
40385
+ } else this.clear();
40386
+
40387
+ } else calcBBox(path[i], this.toBBox);
40388
+ }
40389
+ }
40390
+ }
40391
+
40392
+ function findItem(item, items, equalsFn) {
40393
+ if (!equalsFn) return items.indexOf(item);
40394
+
40395
+ for (let i = 0; i < items.length; i++) {
40396
+ if (equalsFn(item, items[i])) return i;
40397
+ }
40398
+ return -1;
40399
+ }
40400
+
40401
+ // calculate node's bbox from bboxes of its children
40402
+ function calcBBox(node, toBBox) {
40403
+ distBBox(node, 0, node.children.length, toBBox, node);
40404
+ }
40405
+
40406
+ // min bounding rectangle of node children from k to p-1
40407
+ function distBBox(node, k, p, toBBox, destNode) {
40408
+ if (!destNode) destNode = createNode(null);
40409
+ destNode.minX = Infinity;
40410
+ destNode.minY = Infinity;
40411
+ destNode.maxX = -Infinity;
40412
+ destNode.maxY = -Infinity;
40413
+
40414
+ for (let i = k; i < p; i++) {
40415
+ const child = node.children[i];
40416
+ extend(destNode, node.leaf ? toBBox(child) : child);
40417
+ }
40418
+
40419
+ return destNode;
40420
+ }
40421
+
40422
+ function extend(a, b) {
40423
+ a.minX = Math.min(a.minX, b.minX);
40424
+ a.minY = Math.min(a.minY, b.minY);
40425
+ a.maxX = Math.max(a.maxX, b.maxX);
40426
+ a.maxY = Math.max(a.maxY, b.maxY);
40427
+ return a;
40428
+ }
40429
+
40430
+ function compareNodeMinX(a, b) { return a.minX - b.minX; }
40431
+ function compareNodeMinY(a, b) { return a.minY - b.minY; }
40432
+
40433
+ function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
40434
+ function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
40435
+
40436
+ function enlargedArea(a, b) {
40437
+ return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
40438
+ (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
40439
+ }
40440
+
40441
+ function intersectionArea(a, b) {
40442
+ const minX = Math.max(a.minX, b.minX);
40443
+ const minY = Math.max(a.minY, b.minY);
40444
+ const maxX = Math.min(a.maxX, b.maxX);
40445
+ const maxY = Math.min(a.maxY, b.maxY);
40446
+
40447
+ return Math.max(0, maxX - minX) *
40448
+ Math.max(0, maxY - minY);
40449
+ }
40450
+
40451
+ function contains(a, b) {
40452
+ return a.minX <= b.minX &&
40453
+ a.minY <= b.minY &&
40454
+ b.maxX <= a.maxX &&
40455
+ b.maxY <= a.maxY;
40456
+ }
40457
+
40458
+ function intersects(a, b) {
40459
+ return b.minX <= a.maxX &&
40460
+ b.minY <= a.maxY &&
40461
+ b.maxX >= a.minX &&
40462
+ b.maxY >= a.minY;
40463
+ }
40464
+
40465
+ function createNode(children) {
40466
+ return {
40467
+ children,
40468
+ height: 1,
40469
+ leaf: true,
40470
+ minX: Infinity,
40471
+ minY: Infinity,
40472
+ maxX: -Infinity,
40473
+ maxY: -Infinity
40474
+ };
40475
+ }
40476
+
40477
+ // sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
40478
+ // combines selection algorithm with binary divide & conquer approach
40479
+
40480
+ function multiSelect(arr, left, right, n, compare) {
40481
+ const stack = [left, right];
40482
+
40483
+ while (stack.length) {
40484
+ right = stack.pop();
40485
+ left = stack.pop();
40486
+
40487
+ if (right - left <= n) continue;
40488
+
40489
+ const mid = left + Math.ceil((right - left) / n / 2) * n;
40490
+ quickselect(arr, mid, left, right, compare);
40491
+
40492
+ stack.push(left, mid, mid, right);
40493
+ }
40494
+ }
40495
+
39856
40496
  /**
39857
- * This class is an implementation of a dependency Graph.
40497
+ * R-Tree Data Structure
40498
+ *
40499
+ * R-Tree is a spatial data structure used for efficient indexing and querying
40500
+ * of multi-dimensional objects, particularly in geometric and spatial applications.
40501
+ *
40502
+ * It organizes objects into a tree hierarchy, grouping nearby objects together
40503
+ * in bounding boxes. Each node in the tree represents a bounding box that
40504
+ * contains its child nodes or leaf objects. This hierarchical structure allows
40505
+ * for faster spatial queries.
40506
+ *
40507
+ * @see https://en.wikipedia.org/wiki/R-tree
40508
+ *
40509
+ * Consider a 2D Space with four zones: A, B, C, D
40510
+ * +--------------------------+
40511
+ * | |
40512
+ * | +---+ +-------+ |
40513
+ * | | A | | B | |
40514
+ * | +---+ +-------+ |
40515
+ * | |
40516
+ * | |
40517
+ * | +---+ |
40518
+ * | | C | |
40519
+ * | +---+ |
40520
+ * | +-----------+ |
40521
+ * | | D | |
40522
+ * | +-----------+ |
40523
+ * | |
40524
+ * +--------------------------+
40525
+ *
40526
+ * It groups together zones that are spatially close into a minimum bounding box.
40527
+ * For example, A and B are grouped together in rectangle R1, and C and D are grouped
40528
+ * in R2.
40529
+ *
40530
+ * R0
40531
+ * +--------------------------+
40532
+ * | R1 |
40533
+ * | +-----------------+ |
40534
+ * | | A | | B | |
40535
+ * | +-----------------+ |
40536
+ * | |
40537
+ * | R2 |
40538
+ * | +---+---+---+ |
40539
+ * | | | C | | |
40540
+ * | | +---+ | |
40541
+ * | +-----------+ |
40542
+ * | | D | |
40543
+ * | +-----------+ |
40544
+ * | |
40545
+ * +--------------------------+
40546
+ *
40547
+ * The tree would look like this:
40548
+ * R0
40549
+ * / \
40550
+ * / \
40551
+ * R1 R2
40552
+ * | |
40553
+ * A,B C,D
40554
+
40555
+ * Choosing how to group the zones is crucial for the performance of the tree.
40556
+ * Key considerations include avoiding excessive empty space coverage and minimizing overlap
40557
+ * to reduce the number of subtrees processed during searches.
40558
+ *
40559
+ * Various heuristics exist for determining the optimal grouping strategy, such as "least enlargement"
40560
+ * which prioritizes grouping nodes resulting in the smallest increase in bounding box size. In cases where
40561
+ * the choice cannot be made based on this criterion due to the same enlargement for different groupings,
40562
+ * we then evaluate "least area," aiming to minimize the overall area of bounding boxes.
40563
+ *
40564
+ * This implementation is tailored for spreadsheet use, indexing objects associated
40565
+ * with a zone and a sheet.
40566
+ *
40567
+ * It uses the RBush library under the hood. One 2D RBush R-tree per sheet.
40568
+ * @see https://github.com/mourner/rbush
40569
+ */
40570
+ class SpreadsheetRTree {
40571
+ /**
40572
+ * One 2D R-tree per sheet
40573
+ */
40574
+ rTrees = {};
40575
+ /**
40576
+ * Bulk-inserts the given items into the tree. Bulk insertion is usually ~2-3 times
40577
+ * faster than inserting items one by one. After bulk loading (bulk insertion into
40578
+ * an empty tree), subsequent query performance is also ~20-30% better.
40579
+ */
40580
+ constructor(items = []) {
40581
+ const rangesPerSheet = {};
40582
+ for (const item of items) {
40583
+ const sheetId = item.boundingBox.sheetId;
40584
+ if (!rangesPerSheet[sheetId]) {
40585
+ rangesPerSheet[sheetId] = [];
40586
+ }
40587
+ rangesPerSheet[sheetId].push(item);
40588
+ }
40589
+ for (const sheetId in rangesPerSheet) {
40590
+ this.rTrees[sheetId] = new ZoneRBush();
40591
+ this.rTrees[sheetId].load(rangesPerSheet[sheetId]); // bulk-insert
40592
+ }
40593
+ }
40594
+ insert(item) {
40595
+ const sheetId = item.boundingBox.sheetId;
40596
+ if (!this.rTrees[sheetId]) {
40597
+ this.rTrees[sheetId] = new ZoneRBush();
40598
+ }
40599
+ this.rTrees[sheetId].insert(item);
40600
+ }
40601
+ search({ zone, sheetId }) {
40602
+ if (!this.rTrees[sheetId]) {
40603
+ return [];
40604
+ }
40605
+ return this.rTrees[sheetId].search({
40606
+ minX: zone.left,
40607
+ minY: zone.top,
40608
+ maxX: zone.right,
40609
+ maxY: zone.bottom,
40610
+ });
40611
+ }
40612
+ remove(item) {
40613
+ const sheetId = item.boundingBox.sheetId;
40614
+ if (!this.rTrees[sheetId]) {
40615
+ return;
40616
+ }
40617
+ this.rTrees[sheetId].remove(item, deepEquals);
40618
+ }
40619
+ }
40620
+ /**
40621
+ * RBush extension to use zones as bounding boxes
40622
+ */
40623
+ class ZoneRBush extends RBush {
40624
+ toBBox({ boundingBox }) {
40625
+ const zone = boundingBox.zone;
40626
+ return {
40627
+ minX: zone.left,
40628
+ minY: zone.top,
40629
+ maxX: zone.right,
40630
+ maxY: zone.bottom,
40631
+ };
40632
+ }
40633
+ compareMinX(a, b) {
40634
+ return a.boundingBox.zone.left - b.boundingBox.zone.left;
40635
+ }
40636
+ compareMinY(a, b) {
40637
+ return a.boundingBox.zone.top - b.boundingBox.zone.top;
40638
+ }
40639
+ }
40640
+
40641
+ /**
40642
+ * Implementation of a dependency Graph.
39858
40643
  * The graph is used to evaluate the cells in the correct
39859
40644
  * order, and should be updated each time a cell's content is modified
39860
40645
  *
40646
+ * It uses an R-Tree data structure to efficiently find dependent cells.
39861
40647
  */
39862
40648
  class FormulaDependencyGraph {
39863
- /**
39864
- * Internal structure:
39865
- * - key: a cell position (encoded as an integer)
39866
- * - value: a set of cell positions that depends on the key
39867
- *
39868
- * Given
39869
- * - A1:"= B1 + SQRT(B2)"
39870
- * - C1:"= B1";
39871
- * - C2:"= C1"
39872
- *
39873
- * we will have something like:
39874
- * - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
39875
- * - B2 ---> (A1) meaning A1 depends on B2
39876
- * - C1 ---> (C2) meaning C2 depends on C1
39877
- */
39878
- inverseDependencies = new Map();
40649
+ encoder;
39879
40650
  dependencies = new Map();
40651
+ rTree;
40652
+ constructor(encoder, data = []) {
40653
+ this.encoder = encoder;
40654
+ this.rTree = new SpreadsheetRTree(data);
40655
+ }
39880
40656
  removeAllDependencies(formulaPositionId) {
39881
- const dependencies = this.dependencies.get(formulaPositionId);
39882
- if (!dependencies) {
40657
+ const ranges = this.dependencies.get(formulaPositionId);
40658
+ if (!ranges) {
39883
40659
  return;
39884
40660
  }
39885
- for (const dependency of dependencies) {
39886
- this.inverseDependencies.get(dependency)?.delete(formulaPositionId);
40661
+ for (const range of ranges) {
40662
+ this.rTree.remove(range);
39887
40663
  }
39888
40664
  this.dependencies.delete(formulaPositionId);
39889
40665
  }
39890
40666
  addDependencies(formulaPositionId, dependencies) {
39891
- for (const dependency of dependencies) {
39892
- const inverseDependencies = this.inverseDependencies.get(dependency);
39893
- if (inverseDependencies) {
39894
- inverseDependencies.add(formulaPositionId);
39895
- }
39896
- else {
39897
- this.inverseDependencies.set(dependency, new Set([formulaPositionId]));
39898
- }
40667
+ const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
40668
+ data: formulaPositionId,
40669
+ boundingBox: {
40670
+ zone,
40671
+ sheetId,
40672
+ },
40673
+ }));
40674
+ for (const item of rTreeItems) {
40675
+ this.rTree.insert(item);
39899
40676
  }
39900
40677
  const existingDependencies = this.dependencies.get(formulaPositionId);
39901
40678
  if (existingDependencies) {
39902
- existingDependencies.push(...dependencies);
40679
+ existingDependencies.push(...rTreeItems);
39903
40680
  }
39904
40681
  else {
39905
- this.dependencies.set(formulaPositionId, dependencies);
40682
+ this.dependencies.set(formulaPositionId, rTreeItems);
39906
40683
  }
39907
40684
  }
39908
40685
  /**
@@ -39910,20 +40687,20 @@
39910
40687
  * in the correct order they should be evaluated.
39911
40688
  * This is called a topological ordering (excluding cycles)
39912
40689
  */
39913
- getCellsDependingOn(positionIds) {
40690
+ getCellsDependingOn(ranges) {
39914
40691
  const visited = new JetSet();
39915
- const queue = Array.from(positionIds).reverse();
40692
+ const queue = Array.from(ranges).reverse();
39916
40693
  while (queue.length > 0) {
39917
- const node = queue.pop();
39918
- visited.add(node);
39919
- const adjacentNodes = this.inverseDependencies.get(node) || new Set();
39920
- for (const adjacentNode of adjacentNodes) {
39921
- if (!visited.has(adjacentNode)) {
39922
- queue.push(adjacentNode);
40694
+ const range = queue.pop();
40695
+ visited.add(...this.encoder.encodeBoundingBox(range));
40696
+ const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
40697
+ for (const positionId of impactedPositionIds) {
40698
+ if (!visited.has(positionId)) {
40699
+ queue.push(this.encoder.decodeToBoundingBox(positionId));
39923
40700
  }
39924
40701
  }
39925
40702
  }
39926
- visited.delete(...positionIds);
40703
+ visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
39927
40704
  return visited;
39928
40705
  }
39929
40706
  }
@@ -40011,9 +40788,9 @@
40011
40788
  context;
40012
40789
  getters;
40013
40790
  compilationParams;
40014
- positionEncoder = new PositionBitsEncoder();
40791
+ encoder = new PositionBitsEncoder();
40015
40792
  evaluatedCells = new Map();
40016
- formulaDependencies = lazy(new FormulaDependencyGraph());
40793
+ formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
40017
40794
  blockedArrayFormulas = new Set();
40018
40795
  spreadingRelations = new SpreadingRelation();
40019
40796
  constructor(context, getters) {
@@ -40022,23 +40799,23 @@
40022
40799
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
40023
40800
  }
40024
40801
  getEvaluatedCell(position) {
40025
- return (this.evaluatedCells.get(this.encodePosition(position)) ||
40802
+ return (this.evaluatedCells.get(this.encoder.encode(position)) ||
40026
40803
  createEvaluatedCell("", { locale: this.getters.getLocale() }));
40027
40804
  }
40028
40805
  getSpreadPositionsOf(position) {
40029
- const positionId = this.encodePosition(position);
40806
+ const positionId = this.encoder.encode(position);
40030
40807
  if (!this.spreadingRelations.isArrayFormula(positionId)) {
40031
40808
  return [];
40032
40809
  }
40033
- return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map(this.decodePosition.bind(this));
40810
+ return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map((positionId) => this.encoder.decode(positionId));
40034
40811
  }
40035
40812
  getArrayFormulaSpreadingOn(position) {
40036
- const positionId = this.encodePosition(position);
40813
+ const positionId = this.encoder.encode(position);
40037
40814
  const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
40038
- return formulaPosition !== undefined ? this.decodePosition(formulaPosition) : undefined;
40815
+ return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
40039
40816
  }
40040
40817
  getEvaluatedPositions() {
40041
- return [...this.evaluatedCells.keys()].map(this.decodePosition.bind(this));
40818
+ return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
40042
40819
  }
40043
40820
  getArrayFormulaSpreadingOnId(positionId) {
40044
40821
  if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
@@ -40048,7 +40825,7 @@
40048
40825
  return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
40049
40826
  }
40050
40827
  updateDependencies(position) {
40051
- const positionId = this.encodePosition(position);
40828
+ const positionId = this.encoder.encode(position);
40052
40829
  this.formulaDependencies().removeAllDependencies(positionId);
40053
40830
  const dependencies = this.getDirectDependencies(positionId);
40054
40831
  this.formulaDependencies().addDependencies(positionId, dependencies);
@@ -40058,12 +40835,12 @@
40058
40835
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
40059
40836
  }
40060
40837
  evaluateCells(positions) {
40061
- const cells = positions.map(this.encodePosition.bind(this));
40838
+ const cells = positions.map((p) => this.encoder.encode(p));
40062
40839
  const cellsToCompute = new JetSet(cells);
40063
- const arrayFormulas = this.getArrayFormulasImpactedByChangesOf(cells);
40840
+ const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
40064
40841
  cellsToCompute.add(...this.getCellsDependingOn(cells));
40065
- cellsToCompute.add(...arrayFormulas);
40066
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulas));
40842
+ cellsToCompute.add(...arrayFormulasPositionIds);
40843
+ cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
40067
40844
  this.evaluate(cellsToCompute);
40068
40845
  }
40069
40846
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -40086,12 +40863,14 @@
40086
40863
  this.blockedArrayFormulas = new Set();
40087
40864
  this.spreadingRelations = new SpreadingRelation();
40088
40865
  this.formulaDependencies = lazy(() => {
40089
- const dependencyGraph = new FormulaDependencyGraph();
40090
- for (const positionId of this.getAllCells()) {
40091
- const dependencies = this.getDirectDependencies(positionId);
40092
- dependencyGraph.addDependencies(positionId, dependencies);
40093
- }
40094
- return dependencyGraph;
40866
+ const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
40867
+ data: positionId,
40868
+ boundingBox: {
40869
+ zone: range.zone,
40870
+ sheetId: range.sheetId,
40871
+ },
40872
+ })));
40873
+ return new FormulaDependencyGraph(this.encoder, dependencies);
40095
40874
  });
40096
40875
  }
40097
40876
  evaluateAllCells() {
@@ -40113,7 +40892,7 @@
40113
40892
  for (const sheetId of this.getters.getSheetIds()) {
40114
40893
  const cellIds = this.getters.getCells(sheetId);
40115
40894
  for (const cellId in cellIds) {
40116
- positionIds.add(this.encodePosition(this.getters.getCellPosition(cellId)));
40895
+ positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
40117
40896
  }
40118
40897
  }
40119
40898
  return positionIds;
@@ -40180,7 +40959,7 @@
40180
40959
  }
40181
40960
  }
40182
40961
  computeAndSave(position) {
40183
- const positionId = this.encodePosition(position);
40962
+ const positionId = this.encoder.encode(position);
40184
40963
  const evaluatedCell = this.computeCell(positionId);
40185
40964
  if (!this.evaluatedCells.has(positionId)) {
40186
40965
  this.setEvaluatedCell(positionId, evaluatedCell);
@@ -40240,15 +41019,15 @@
40240
41019
  throw new Error(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
40241
41020
  }
40242
41021
  updateSpreadRelation({ sheetId, col, row, }) {
40243
- const arrayFormulaPositionId = this.encodePosition({ sheetId, col, row });
41022
+ const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
40244
41023
  return (i, j) => {
40245
41024
  const position = { sheetId, col: i + col, row: j + row };
40246
- const resultPositionId = this.encodePosition(position);
41025
+ const resultPositionId = this.encoder.encode(position);
40247
41026
  this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
40248
41027
  };
40249
41028
  }
40250
41029
  checkCollision({ sheetId, col, row }) {
40251
- const formulaPositionId = this.encodePosition({ sheetId, col, row });
41030
+ const formulaPositionId = this.encoder.encode({ sheetId, col, row });
40252
41031
  return (i, j) => {
40253
41032
  const position = { sheetId: sheetId, col: i + col, row: j + row };
40254
41033
  const rawCell = this.getters.getCell(position);
@@ -40269,7 +41048,7 @@
40269
41048
  format: format || matrixResult[i][j]?.format,
40270
41049
  locale: this.getters.getLocale(),
40271
41050
  });
40272
- const positionId = this.encodePosition(position);
41051
+ const positionId = this.encoder.encode(position);
40273
41052
  this.setEvaluatedCell(positionId, evaluatedCell);
40274
41053
  // check if formula dependencies present in the spread zone
40275
41054
  // if so, they need to be recomputed
@@ -40301,29 +41080,17 @@
40301
41080
  if (!cell?.isFormula) {
40302
41081
  return [];
40303
41082
  }
40304
- const dependencies = [];
40305
- for (const range of cell.compiledFormula.dependencies) {
40306
- if (range.invalidSheetName || range.invalidXc) {
40307
- continue;
40308
- }
40309
- const sheetId = range.sheetId;
40310
- forEachPositionsInZone(range.zone, (col, row) => {
40311
- dependencies.push(this.encodePosition({ sheetId, col, row }));
40312
- });
40313
- }
40314
- return dependencies;
41083
+ return cell.compiledFormula.dependencies;
40315
41084
  }
40316
41085
  getCellsDependingOn(positionIds) {
40317
- return this.formulaDependencies().getCellsDependingOn(positionIds);
41086
+ const ranges = [];
41087
+ for (const positionId of positionIds) {
41088
+ ranges.push(this.encoder.decodeToBoundingBox(positionId));
41089
+ }
41090
+ return this.formulaDependencies().getCellsDependingOn(ranges);
40318
41091
  }
40319
41092
  getCell(positionId) {
40320
- return this.getters.getCell(this.decodePosition(positionId));
40321
- }
40322
- encodePosition(position) {
40323
- return this.positionEncoder.encode(position);
40324
- }
40325
- decodePosition(positionId) {
40326
- return this.positionEncoder.decode(positionId);
41093
+ return this.getters.getCell(this.encoder.decode(positionId));
40327
41094
  }
40328
41095
  }
40329
41096
  function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
@@ -40379,6 +41146,13 @@
40379
41146
  encode({ sheetId, col, row }) {
40380
41147
  return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
40381
41148
  }
41149
+ encodeBoundingBox({ sheetId, zone }) {
41150
+ const positions = [];
41151
+ forEachPositionsInZone(zone, (col, row) => {
41152
+ positions.push(this.encode({ sheetId, col, row }));
41153
+ });
41154
+ return positions;
41155
+ }
40382
41156
  decode(id) {
40383
41157
  // keep only the last 21 bits by AND-ing the bit sequence with 21 ones
40384
41158
  const row = Number(id & 2097151n);
@@ -40386,6 +41160,10 @@
40386
41160
  const sheetId = this.decodeSheet(id >> 42n);
40387
41161
  return { sheetId, col, row };
40388
41162
  }
41163
+ decodeToBoundingBox(id) {
41164
+ const { sheetId, col, row } = this.decode(id);
41165
+ return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
41166
+ }
40389
41167
  encodeSheet(sheetId) {
40390
41168
  const sheetKey = this.sheetMapping[sheetId];
40391
41169
  if (sheetKey === undefined) {
@@ -40556,7 +41334,12 @@
40556
41334
  // Getters
40557
41335
  // ---------------------------------------------------------------------------
40558
41336
  evaluateFormula(sheetId, formulaString) {
40559
- return this.evaluator.evaluateFormula(sheetId, formulaString);
41337
+ try {
41338
+ return this.evaluator.evaluateFormula(sheetId, formulaString);
41339
+ }
41340
+ catch (error) {
41341
+ return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
41342
+ }
40560
41343
  }
40561
41344
  /**
40562
41345
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -40825,7 +41608,7 @@
40825
41608
  }
40826
41609
 
40827
41610
  class EvaluationChartPlugin extends UIPlugin {
40828
- static getters = ["getChartRuntime", "getBackgroundOfSingleCellChart"];
41611
+ static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
40829
41612
  charts = {};
40830
41613
  createRuntimeChart = chartRuntimeFactory(this.getters);
40831
41614
  handle(cmd) {
@@ -40863,25 +41646,26 @@
40863
41646
  return this.charts[figureId];
40864
41647
  }
40865
41648
  /**
40866
- * Get the background color of a chart based on the color of the first cell of the main range
40867
- * of the chart. In order of priority, it will return :
40868
- *
40869
- * - the chart background color if one is defined
40870
- * - the fill color of the cell if one is defined
40871
- * - the fill color of the cell from conditional formats if one is defined
40872
- * - the default chart color if no other color is defined
41649
+ * Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
40873
41650
  */
40874
- getBackgroundOfSingleCellChart(chartBackground, mainRange) {
41651
+ getStyleOfSingleCellChart(chartBackground, mainRange) {
40875
41652
  if (chartBackground)
40876
- return chartBackground;
41653
+ return { background: chartBackground, fontColor: chartFontColor(chartBackground) };
40877
41654
  if (!mainRange) {
40878
- return BACKGROUND_CHART_COLOR;
41655
+ return {
41656
+ background: BACKGROUND_CHART_COLOR,
41657
+ fontColor: chartFontColor(BACKGROUND_CHART_COLOR),
41658
+ };
40879
41659
  }
40880
41660
  const col = mainRange.zone.left;
40881
41661
  const row = mainRange.zone.top;
40882
41662
  const sheetId = mainRange.sheetId;
40883
41663
  const style = this.getters.getCellComputedStyle({ sheetId, col, row });
40884
- return style.fillColor || BACKGROUND_CHART_COLOR;
41664
+ const background = style.fillColor || BACKGROUND_CHART_COLOR;
41665
+ return {
41666
+ background,
41667
+ fontColor: style.textColor || chartFontColor(background),
41668
+ };
40885
41669
  }
40886
41670
  exportForExcel(data) {
40887
41671
  for (const sheet of data.sheets) {
@@ -40992,45 +41776,40 @@
40992
41776
  getComputedStyles(sheetId) {
40993
41777
  const computedStyle = {};
40994
41778
  for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
40995
- try {
40996
- switch (cf.rule.type) {
40997
- case "ColorScaleRule":
40998
- for (let range of cf.ranges) {
40999
- this.applyColorScale(sheetId, range, cf.rule, computedStyle);
41000
- }
41001
- break;
41002
- case "CellIsRule":
41003
- const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
41004
- for (let ref of cf.ranges) {
41005
- const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
41006
- for (let row = zone.top; row <= zone.bottom; row++) {
41007
- for (let col = zone.left; col <= zone.right; col++) {
41008
- const predicate = this.rulePredicate[cf.rule.type];
41009
- const target = { sheetId, col, row };
41010
- const values = cf.rule.values.map((value, i) => {
41011
- const compiledFormula = formulas[i];
41012
- if (compiledFormula) {
41013
- return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, {
41014
- ...compiledFormula,
41015
- dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
41016
- });
41017
- }
41018
- return value;
41019
- });
41020
- if (predicate && predicate(target, { ...cf.rule, values })) {
41021
- if (!computedStyle[col])
41022
- computedStyle[col] = [];
41023
- // we must combine all the properties of all the CF rules applied to the given cell
41024
- computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
41779
+ switch (cf.rule.type) {
41780
+ case "ColorScaleRule":
41781
+ for (let range of cf.ranges) {
41782
+ this.applyColorScale(sheetId, range, cf.rule, computedStyle);
41783
+ }
41784
+ break;
41785
+ case "CellIsRule":
41786
+ const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
41787
+ for (let ref of cf.ranges) {
41788
+ const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
41789
+ for (let row = zone.top; row <= zone.bottom; row++) {
41790
+ for (let col = zone.left; col <= zone.right; col++) {
41791
+ const predicate = this.rulePredicate[cf.rule.type];
41792
+ const target = { sheetId, col, row };
41793
+ const values = cf.rule.values.map((value, i) => {
41794
+ const compiledFormula = formulas[i];
41795
+ if (compiledFormula) {
41796
+ return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, {
41797
+ ...compiledFormula,
41798
+ dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
41799
+ });
41025
41800
  }
41801
+ return value;
41802
+ });
41803
+ if (predicate && predicate(target, { ...cf.rule, values })) {
41804
+ if (!computedStyle[col])
41805
+ computedStyle[col] = [];
41806
+ // we must combine all the properties of all the CF rules applied to the given cell
41807
+ computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
41026
41808
  }
41027
41809
  }
41028
41810
  }
41029
- break;
41030
- }
41031
- }
41032
- catch (_) {
41033
- // we don't care about the errors within the evaluation of a rule
41811
+ }
41812
+ break;
41034
41813
  }
41035
41814
  }
41036
41815
  return computedStyle;
@@ -41802,7 +42581,7 @@
41802
42581
  let row = zone.bottom;
41803
42582
  if (col > 0) {
41804
42583
  let leftPosition = { sheetId, col: col - 1, row };
41805
- while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
42584
+ while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
41806
42585
  this.getters.getCell(leftPosition)?.content) {
41807
42586
  row += 1;
41808
42587
  leftPosition = { sheetId, col: col - 1, row };
@@ -41812,7 +42591,7 @@
41812
42591
  col = zone.right;
41813
42592
  if (col <= this.getters.getNumberCols(sheetId)) {
41814
42593
  let rightPosition = { sheetId, col: col + 1, row };
41815
- while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
42594
+ while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
41816
42595
  this.getters.getCell(rightPosition)?.content) {
41817
42596
  row += 1;
41818
42597
  rightPosition = { sheetId, col: col + 1, row };
@@ -52193,7 +52972,7 @@
52193
52972
  }
52194
52973
  onKeydown(ev) {
52195
52974
  let keyDownString = "";
52196
- if (ev.ctrlKey || ev.metaKey) {
52975
+ if (isCtrlKey(ev)) {
52197
52976
  keyDownString += "CTRL+";
52198
52977
  }
52199
52978
  keyDownString += ev.key.toUpperCase();
@@ -55905,6 +56684,7 @@
55905
56684
  colorToRGBA,
55906
56685
  positionToZone,
55907
56686
  isDefined: isDefined$1,
56687
+ isMatrix,
55908
56688
  lazy,
55909
56689
  genericRepeat,
55910
56690
  createAction,
@@ -55988,9 +56768,9 @@
55988
56768
  exports.tokenize = tokenize;
55989
56769
 
55990
56770
 
55991
- __info__.version = "17.0.5";
55992
- __info__.date = "2023-12-05T09:51:44.657Z";
55993
- __info__.hash = "2480a5d";
56771
+ __info__.version = "17.0.7";
56772
+ __info__.date = "2024-01-12T13:42:04.654Z";
56773
+ __info__.hash = "cadb817";
55994
56774
 
55995
56775
 
55996
56776
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);