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