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