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