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