@odoo/o-spreadsheet 17.1.0-alpha.4 → 17.1.0-alpha.6
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 +1056 -284
- package/dist/o-spreadsheet.d.ts +37 -44
- package/dist/o-spreadsheet.esm.js +1056 -284
- package/dist/o-spreadsheet.iife.js +1056 -284
- package/dist/o-spreadsheet.iife.min.js +277 -273
- package/dist/o_spreadsheet.xml +4 -4
- package/package.json +6 -2
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
4
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
5
|
-
* @version 17.1.0-alpha.
|
|
6
|
-
* @date
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 17.1.0-alpha.6
|
|
6
|
+
* @date 2024-01-04T12:22:45.921Z
|
|
7
|
+
* @hash 56dbdc9
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
@@ -330,13 +330,6 @@ const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 18, 24, 36];
|
|
|
330
330
|
//------------------------------------------------------------------------------
|
|
331
331
|
// Miscellaneous
|
|
332
332
|
//------------------------------------------------------------------------------
|
|
333
|
-
/**
|
|
334
|
-
* Stringify an object, like JSON.stringify, except that the first level of keys
|
|
335
|
-
* is ordered.
|
|
336
|
-
*/
|
|
337
|
-
function stringify(obj) {
|
|
338
|
-
return JSON.stringify(obj, Object.keys(obj).sort());
|
|
339
|
-
}
|
|
340
333
|
/**
|
|
341
334
|
* Remove quotes from a quoted string
|
|
342
335
|
* ```js
|
|
@@ -559,7 +552,7 @@ function isObjectEmptyRecursive(argument) {
|
|
|
559
552
|
*/
|
|
560
553
|
function getItemId(item, itemsDic) {
|
|
561
554
|
for (let [key, value] of Object.entries(itemsDic)) {
|
|
562
|
-
if (
|
|
555
|
+
if (deepEquals(value, item)) {
|
|
563
556
|
return parseInt(key, 10);
|
|
564
557
|
}
|
|
565
558
|
}
|
|
@@ -2062,6 +2055,8 @@ exports.CommandResult = void 0;
|
|
|
2062
2055
|
CommandResult["NoChanges"] = "NoChanges";
|
|
2063
2056
|
})(exports.CommandResult || (exports.CommandResult = {}));
|
|
2064
2057
|
|
|
2058
|
+
const PLAIN_TEXT_FORMAT = "@"; // see OpenXML spec §18.8.31
|
|
2059
|
+
|
|
2065
2060
|
const DEFAULT_LOCALES = [
|
|
2066
2061
|
{
|
|
2067
2062
|
name: "English (US)",
|
|
@@ -2825,6 +2820,9 @@ function parseFormat(formatString) {
|
|
|
2825
2820
|
* Formats a cell value with its format.
|
|
2826
2821
|
*/
|
|
2827
2822
|
function formatValue(value, { format, locale }) {
|
|
2823
|
+
if (format === PLAIN_TEXT_FORMAT) {
|
|
2824
|
+
return toString(value) || "";
|
|
2825
|
+
}
|
|
2828
2826
|
switch (typeof value) {
|
|
2829
2827
|
case "string":
|
|
2830
2828
|
return value;
|
|
@@ -3388,8 +3386,10 @@ function convertInternalFormatToFormat(internalFormat) {
|
|
|
3388
3386
|
const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
|
|
3389
3387
|
// Same as above, but matches the exact string (nothing before or after)
|
|
3390
3388
|
const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
|
|
3391
|
-
/** Reference of a column header (eg. A, AB) */
|
|
3392
|
-
const colHeader = new RegExp(
|
|
3389
|
+
/** Reference of a column header (eg. A, AB, $A) */
|
|
3390
|
+
const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
|
|
3391
|
+
/** Reference of a row header (eg. 1, $1) */
|
|
3392
|
+
const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
|
|
3393
3393
|
/** Reference of a column (eg. A, $CA, Sheet1!B) */
|
|
3394
3394
|
const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
|
|
3395
3395
|
/** Reference of a row (eg. 1, 59, Sheet1!9) */
|
|
@@ -3419,6 +3419,9 @@ function isRowReference(xc) {
|
|
|
3419
3419
|
function isColHeader(str) {
|
|
3420
3420
|
return colHeader.test(str);
|
|
3421
3421
|
}
|
|
3422
|
+
function isRowHeader(str) {
|
|
3423
|
+
return rowHeader.test(str);
|
|
3424
|
+
}
|
|
3422
3425
|
/**
|
|
3423
3426
|
* Return true if the given xc is the reference of a single cell,
|
|
3424
3427
|
* without any specified sheet (e.g. A1)
|
|
@@ -4109,18 +4112,10 @@ class RangeImpl {
|
|
|
4109
4112
|
if (isFullCol) {
|
|
4110
4113
|
parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
|
|
4111
4114
|
parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
|
|
4112
|
-
if (zone.left === zone.right) {
|
|
4113
|
-
parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
|
|
4114
|
-
parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
|
|
4115
|
-
}
|
|
4116
4115
|
}
|
|
4117
4116
|
if (isFullRow) {
|
|
4118
4117
|
parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
|
|
4119
4118
|
parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
|
|
4120
|
-
if (zone.top === zone.bottom) {
|
|
4121
|
-
parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
|
|
4122
|
-
parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
|
|
4123
|
-
}
|
|
4124
4119
|
}
|
|
4125
4120
|
return parts;
|
|
4126
4121
|
}
|
|
@@ -4362,7 +4357,7 @@ function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
|
|
|
4362
4357
|
function getDefaultCellHeight(ctx, cell, colSize) {
|
|
4363
4358
|
if (!cell || !cell.content)
|
|
4364
4359
|
return DEFAULT_CELL_HEIGHT;
|
|
4365
|
-
const maxWidth = cell.style?.wrapping ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
|
|
4360
|
+
const maxWidth = cell.style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
|
|
4366
4361
|
const numberOfLines = cell.isFormula
|
|
4367
4362
|
? 1
|
|
4368
4363
|
: splitTextToWidth(ctx, cell.content, cell.style, maxWidth).length;
|
|
@@ -5299,7 +5294,7 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
5299
5294
|
};
|
|
5300
5295
|
baselineCell = getters.getEvaluatedCell(baselinePosition);
|
|
5301
5296
|
}
|
|
5302
|
-
const background = getters.
|
|
5297
|
+
const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
|
|
5303
5298
|
const locale = getters.getLocale();
|
|
5304
5299
|
return {
|
|
5305
5300
|
title: _t(chart.title),
|
|
@@ -5308,7 +5303,7 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
5308
5303
|
baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
|
|
5309
5304
|
baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
|
|
5310
5305
|
baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
|
|
5311
|
-
fontColor
|
|
5306
|
+
fontColor,
|
|
5312
5307
|
background,
|
|
5313
5308
|
baselineStyle: chart.baselineMode !== "percentage" && baseline
|
|
5314
5309
|
? getters.getCellStyle({
|
|
@@ -5734,6 +5729,9 @@ function detectLink(value) {
|
|
|
5734
5729
|
}
|
|
5735
5730
|
|
|
5736
5731
|
function evaluateLiteral(content, localeFormat) {
|
|
5732
|
+
if (localeFormat.format === PLAIN_TEXT_FORMAT) {
|
|
5733
|
+
return textCell(content || "", localeFormat);
|
|
5734
|
+
}
|
|
5737
5735
|
return createEvaluatedCell(parseLiteral(content || "", localeFormat.locale), localeFormat);
|
|
5738
5736
|
}
|
|
5739
5737
|
function parseLiteral(content, locale) {
|
|
@@ -5768,6 +5766,9 @@ function createEvaluatedCell(value, localeFormat) {
|
|
|
5768
5766
|
}
|
|
5769
5767
|
function _createEvaluatedCell(value, localeFormat) {
|
|
5770
5768
|
try {
|
|
5769
|
+
if (localeFormat.format === PLAIN_TEXT_FORMAT) {
|
|
5770
|
+
return textCell(toString(value), localeFormat);
|
|
5771
|
+
}
|
|
5771
5772
|
for (const builder of builders) {
|
|
5772
5773
|
const evaluateCell = builder(value, localeFormat);
|
|
5773
5774
|
if (evaluateCell) {
|
|
@@ -6259,10 +6260,6 @@ css /* scss */ `
|
|
|
6259
6260
|
|
|
6260
6261
|
.o-error-tooltip-message {
|
|
6261
6262
|
overflow: hidden;
|
|
6262
|
-
display: -webkit-box; /* Limit to 3 lines */
|
|
6263
|
-
-webkit-line-clamp: 3;
|
|
6264
|
-
line-clamp: 3;
|
|
6265
|
-
-webkit-box-orient: vertical;
|
|
6266
6263
|
}
|
|
6267
6264
|
}
|
|
6268
6265
|
`;
|
|
@@ -6607,6 +6604,7 @@ const FilterMenuPopoverBuilder = {
|
|
|
6607
6604
|
},
|
|
6608
6605
|
};
|
|
6609
6606
|
|
|
6607
|
+
const macRegex = /Mac/i;
|
|
6610
6608
|
/**
|
|
6611
6609
|
* Return true if the event was triggered from
|
|
6612
6610
|
* a child element.
|
|
@@ -6658,7 +6656,7 @@ const letterRegex = /^[a-zA-Z]$/;
|
|
|
6658
6656
|
*/
|
|
6659
6657
|
function keyboardEventToShortcutString(ev, mode = "key") {
|
|
6660
6658
|
let keyDownString = "";
|
|
6661
|
-
if (ev
|
|
6659
|
+
if (isCtrlKey(ev) && ev.key !== "Ctrl")
|
|
6662
6660
|
keyDownString += "Ctrl+";
|
|
6663
6661
|
if (ev.metaKey)
|
|
6664
6662
|
keyDownString += "Ctrl+";
|
|
@@ -6670,6 +6668,17 @@ function keyboardEventToShortcutString(ev, mode = "key") {
|
|
|
6670
6668
|
keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
|
|
6671
6669
|
return keyDownString;
|
|
6672
6670
|
}
|
|
6671
|
+
function isMacOS() {
|
|
6672
|
+
return Boolean(macRegex.test(navigator.userAgent));
|
|
6673
|
+
}
|
|
6674
|
+
/**
|
|
6675
|
+
* @param {KeyboardEvent | MouseEvent} ev
|
|
6676
|
+
* @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
|
|
6677
|
+
* On Mac, this is the "meta" or "command" key.
|
|
6678
|
+
*/
|
|
6679
|
+
function isCtrlKey(ev) {
|
|
6680
|
+
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
6681
|
+
}
|
|
6673
6682
|
|
|
6674
6683
|
/**
|
|
6675
6684
|
* Return the o-spreadsheet element position relative
|
|
@@ -8907,7 +8916,7 @@ function createGaugeChartRuntime(chart, getters) {
|
|
|
8907
8916
|
});
|
|
8908
8917
|
return {
|
|
8909
8918
|
chartJsConfig: config,
|
|
8910
|
-
background: getters.
|
|
8919
|
+
background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
|
|
8911
8920
|
};
|
|
8912
8921
|
}
|
|
8913
8922
|
|
|
@@ -8988,7 +8997,7 @@ function getFormatMinDisplayUnit(format) {
|
|
|
8988
8997
|
else if (format.includes("h") || format.includes("H")) {
|
|
8989
8998
|
return "hour";
|
|
8990
8999
|
}
|
|
8991
|
-
else if (format.includes("
|
|
9000
|
+
else if (format.includes("d")) {
|
|
8992
9001
|
return "day";
|
|
8993
9002
|
}
|
|
8994
9003
|
else if (format.includes("M")) {
|
|
@@ -9500,6 +9509,27 @@ function calculatePercentage(dataset, dataIndex) {
|
|
|
9500
9509
|
const percentage = (dataset[dataIndex] / total) * 100;
|
|
9501
9510
|
return percentage.toFixed(2);
|
|
9502
9511
|
}
|
|
9512
|
+
function filterNegativeValues(labels, datasets) {
|
|
9513
|
+
const dataPointsIndexes = labels.reduce((indexes, label, i) => {
|
|
9514
|
+
const shouldKeep = datasets.some((dataset) => {
|
|
9515
|
+
const dataPoint = dataset.data[i];
|
|
9516
|
+
return typeof dataPoint !== "number" || dataPoint >= 0;
|
|
9517
|
+
});
|
|
9518
|
+
if (shouldKeep) {
|
|
9519
|
+
indexes.push(i);
|
|
9520
|
+
}
|
|
9521
|
+
return indexes;
|
|
9522
|
+
}, []);
|
|
9523
|
+
const filteredLabels = dataPointsIndexes.map((i) => labels[i] || "");
|
|
9524
|
+
const filteredDatasets = datasets.map((dataset) => ({
|
|
9525
|
+
...dataset,
|
|
9526
|
+
data: dataPointsIndexes.map((i) => {
|
|
9527
|
+
const dataPoint = dataset.data[i];
|
|
9528
|
+
return typeof dataPoint !== "number" || dataPoint >= 0 ? dataPoint : 0;
|
|
9529
|
+
}),
|
|
9530
|
+
}));
|
|
9531
|
+
return { labels: filteredLabels, dataSetsValues: filteredDatasets };
|
|
9532
|
+
}
|
|
9503
9533
|
function createPieChartRuntime(chart, getters) {
|
|
9504
9534
|
const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
|
|
9505
9535
|
let labels = labelValues.formattedValues;
|
|
@@ -9513,6 +9543,7 @@ function createPieChartRuntime(chart, getters) {
|
|
|
9513
9543
|
if (chart.aggregated) {
|
|
9514
9544
|
({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
|
|
9515
9545
|
}
|
|
9546
|
+
({ dataSetsValues, labels } = filterNegativeValues(labels, dataSetsValues));
|
|
9516
9547
|
const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
|
|
9517
9548
|
const locale = getters.getLocale();
|
|
9518
9549
|
const config = getPieConfiguration(chart, labels, { format: dataSetFormat, locale });
|
|
@@ -10350,7 +10381,7 @@ function setStyle(env, style) {
|
|
|
10350
10381
|
// Simple actions
|
|
10351
10382
|
//------------------------------------------------------------------------------
|
|
10352
10383
|
const PASTE_ACTION = async (env) => paste$1(env);
|
|
10353
|
-
const
|
|
10384
|
+
const PASTE_AS_VALUE_ACTION = async (env) => paste$1(env, "asValue");
|
|
10354
10385
|
async function paste$1(env, pasteOption) {
|
|
10355
10386
|
const spreadsheetClipboard = env.model.getters.getClipboardTextContent();
|
|
10356
10387
|
const osClipboard = await env.clipboard.readText();
|
|
@@ -10363,7 +10394,7 @@ async function paste$1(env, pasteOption) {
|
|
|
10363
10394
|
else {
|
|
10364
10395
|
interactivePaste(env, target, pasteOption);
|
|
10365
10396
|
}
|
|
10366
|
-
if (env.model.getters.isCutOperation() && pasteOption !== "
|
|
10397
|
+
if (env.model.getters.isCutOperation() && pasteOption !== "asValue") {
|
|
10367
10398
|
await env.clipboard.write({ [ClipboardMIMEType.PlainText]: "" });
|
|
10368
10399
|
}
|
|
10369
10400
|
break;
|
|
@@ -10778,9 +10809,9 @@ const pasteSpecial = {
|
|
|
10778
10809
|
icon: "o-spreadsheet-Icon.PASTE",
|
|
10779
10810
|
};
|
|
10780
10811
|
const pasteSpecialValue = {
|
|
10781
|
-
name: _t("Paste value
|
|
10812
|
+
name: _t("Paste as value"),
|
|
10782
10813
|
description: "Ctrl+Shift+V",
|
|
10783
|
-
execute:
|
|
10814
|
+
execute: PASTE_AS_VALUE_ACTION,
|
|
10784
10815
|
};
|
|
10785
10816
|
const pasteSpecialFormat = {
|
|
10786
10817
|
name: _t("Paste format only"),
|
|
@@ -17949,6 +17980,10 @@ function setXcToFixedReferenceType(xc, referenceType) {
|
|
|
17949
17980
|
return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
|
|
17950
17981
|
case "colrow":
|
|
17951
17982
|
indexOfNumber = xc.search(/[0-9]/);
|
|
17983
|
+
if (indexOfNumber === -1 || indexOfNumber === 0) {
|
|
17984
|
+
// no row number (eg. A) or no column (eg. 1)
|
|
17985
|
+
return "$" + xc;
|
|
17986
|
+
}
|
|
17952
17987
|
xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
|
|
17953
17988
|
return "$" + xc;
|
|
17954
17989
|
case "none":
|
|
@@ -20015,6 +20050,11 @@ const formatNumberAutomatic = {
|
|
|
20015
20050
|
execute: (env) => setFormatter(env, ""),
|
|
20016
20051
|
isActive: (env) => isAutomaticFormatSelected(env),
|
|
20017
20052
|
};
|
|
20053
|
+
const formatNumberPlainText = {
|
|
20054
|
+
name: _t("Plain text"),
|
|
20055
|
+
execute: (env) => setFormatter(env, PLAIN_TEXT_FORMAT),
|
|
20056
|
+
isActive: (env) => isFormatSelected(env, PLAIN_TEXT_FORMAT),
|
|
20057
|
+
};
|
|
20018
20058
|
const formatNumberNumber = createFormatActionSpec({
|
|
20019
20059
|
name: _t("Number"),
|
|
20020
20060
|
descriptionValue: 1000.12,
|
|
@@ -20374,6 +20414,7 @@ var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
|
|
|
20374
20414
|
formatNumberFullWeekDayAndMonth: formatNumberFullWeekDayAndMonth,
|
|
20375
20415
|
formatNumberNumber: formatNumberNumber,
|
|
20376
20416
|
formatNumberPercent: formatNumberPercent,
|
|
20417
|
+
formatNumberPlainText: formatNumberPlainText,
|
|
20377
20418
|
formatNumberShortMonth: formatNumberShortMonth,
|
|
20378
20419
|
formatNumberShortWeekDay: formatNumberShortWeekDay,
|
|
20379
20420
|
formatNumberTime: formatNumberTime,
|
|
@@ -20804,6 +20845,10 @@ numberFormatMenuRegistry
|
|
|
20804
20845
|
.add("format_number_automatic", {
|
|
20805
20846
|
...formatNumberAutomatic,
|
|
20806
20847
|
sequence: 10,
|
|
20848
|
+
})
|
|
20849
|
+
.add("format_number_plain_text", {
|
|
20850
|
+
...formatNumberPlainText,
|
|
20851
|
+
sequence: 15,
|
|
20807
20852
|
separator: true,
|
|
20808
20853
|
})
|
|
20809
20854
|
.add("format_number_number", {
|
|
@@ -21402,10 +21447,10 @@ const arrowMap = {
|
|
|
21402
21447
|
function updateSelectionWithArrowKeys(ev, selection) {
|
|
21403
21448
|
const direction = arrowMap[ev.key];
|
|
21404
21449
|
if (ev.shiftKey) {
|
|
21405
|
-
selection.resizeAnchorZone(direction, ev
|
|
21450
|
+
selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
|
|
21406
21451
|
}
|
|
21407
21452
|
else {
|
|
21408
|
-
selection.moveAnchorCell(direction, ev
|
|
21453
|
+
selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
|
|
21409
21454
|
}
|
|
21410
21455
|
}
|
|
21411
21456
|
|
|
@@ -24014,6 +24059,12 @@ css /* scss */ `
|
|
|
24014
24059
|
padding: 4px 0 4px 4px;
|
|
24015
24060
|
}
|
|
24016
24061
|
}
|
|
24062
|
+
|
|
24063
|
+
.o-matches-count div {
|
|
24064
|
+
text-overflow: ellipsis;
|
|
24065
|
+
overflow: hidden;
|
|
24066
|
+
white-space: nowrap;
|
|
24067
|
+
}
|
|
24017
24068
|
}
|
|
24018
24069
|
`;
|
|
24019
24070
|
class FindAndReplacePanel extends owl.Component {
|
|
@@ -28444,7 +28495,10 @@ class GridOverlay extends owl.Component {
|
|
|
28444
28495
|
return;
|
|
28445
28496
|
}
|
|
28446
28497
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
28447
|
-
this.props.onCellClicked(col, row, {
|
|
28498
|
+
this.props.onCellClicked(col, row, {
|
|
28499
|
+
expandZone: ev.shiftKey,
|
|
28500
|
+
addZone: isCtrlKey(ev),
|
|
28501
|
+
});
|
|
28448
28502
|
}
|
|
28449
28503
|
onDoubleClick(ev) {
|
|
28450
28504
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
@@ -28519,6 +28573,7 @@ class AbstractResizer extends owl.Component {
|
|
|
28519
28573
|
draggerShadowThickness: 0,
|
|
28520
28574
|
delta: 0,
|
|
28521
28575
|
base: 0,
|
|
28576
|
+
position: "before",
|
|
28522
28577
|
});
|
|
28523
28578
|
_computeHandleDisplay(ev) {
|
|
28524
28579
|
const position = this._getEvOffset(ev);
|
|
@@ -28643,11 +28698,13 @@ class AbstractResizer extends owl.Component {
|
|
|
28643
28698
|
this.state.draggerLinePosition = dimensions.start;
|
|
28644
28699
|
this.state.draggerShadowPosition = dimensions.start;
|
|
28645
28700
|
this.state.base = elementIndex;
|
|
28701
|
+
this.state.position = "before";
|
|
28646
28702
|
}
|
|
28647
28703
|
else if (this._getSelectedZoneEnd() < elementIndex) {
|
|
28648
28704
|
this.state.draggerLinePosition = dimensions.end;
|
|
28649
28705
|
this.state.draggerShadowPosition = dimensions.end - this.state.draggerShadowThickness;
|
|
28650
|
-
this.state.base = elementIndex
|
|
28706
|
+
this.state.base = elementIndex;
|
|
28707
|
+
this.state.position = "after";
|
|
28651
28708
|
}
|
|
28652
28709
|
else {
|
|
28653
28710
|
this.state.draggerLinePosition = startDimensions.start;
|
|
@@ -28671,7 +28728,7 @@ class AbstractResizer extends owl.Component {
|
|
|
28671
28728
|
this._increaseSelection(index);
|
|
28672
28729
|
}
|
|
28673
28730
|
else {
|
|
28674
|
-
this._selectElement(index, ev
|
|
28731
|
+
this._selectElement(index, isCtrlKey(ev));
|
|
28675
28732
|
}
|
|
28676
28733
|
this.lastSelectedElementIndex = index;
|
|
28677
28734
|
const mouseMoveSelect = (col, row) => {
|
|
@@ -28822,13 +28879,14 @@ class ColResizer extends AbstractResizer {
|
|
|
28822
28879
|
dimension: "COL",
|
|
28823
28880
|
base: this.state.base,
|
|
28824
28881
|
elements,
|
|
28882
|
+
position: this.state.position,
|
|
28825
28883
|
});
|
|
28826
28884
|
if (!result.isSuccessful && result.reasons.includes("WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */)) {
|
|
28827
28885
|
this.env.raiseError(MergeErrorMessage);
|
|
28828
28886
|
}
|
|
28829
28887
|
}
|
|
28830
|
-
_selectElement(index,
|
|
28831
|
-
this.env.model.selection.selectColumn(index,
|
|
28888
|
+
_selectElement(index, addDistinctHeader) {
|
|
28889
|
+
this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
28832
28890
|
}
|
|
28833
28891
|
_increaseSelection(index) {
|
|
28834
28892
|
this.env.model.selection.selectColumn(index, "updateAnchor");
|
|
@@ -28986,13 +29044,14 @@ class RowResizer extends AbstractResizer {
|
|
|
28986
29044
|
dimension: "ROW",
|
|
28987
29045
|
base: this.state.base,
|
|
28988
29046
|
elements,
|
|
29047
|
+
position: this.state.position,
|
|
28989
29048
|
});
|
|
28990
29049
|
if (!result.isSuccessful && result.reasons.includes("WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */)) {
|
|
28991
29050
|
this.env.raiseError(MergeErrorMessage);
|
|
28992
29051
|
}
|
|
28993
29052
|
}
|
|
28994
|
-
_selectElement(index,
|
|
28995
|
-
this.env.model.selection.selectRow(index,
|
|
29053
|
+
_selectElement(index, addDistinctHeader) {
|
|
29054
|
+
this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
28996
29055
|
}
|
|
28997
29056
|
_increaseSelection(index) {
|
|
28998
29057
|
this.env.model.selection.selectRow(index, "updateAnchor");
|
|
@@ -29093,8 +29152,8 @@ function useWheelHandler(handler) {
|
|
|
29093
29152
|
return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT);
|
|
29094
29153
|
}
|
|
29095
29154
|
const onMouseWheel = (ev) => {
|
|
29096
|
-
const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode);
|
|
29097
|
-
const deltaY = normalize(ev.shiftKey ? ev.deltaX : ev.deltaY, ev.deltaMode);
|
|
29155
|
+
const deltaX = normalize(ev.shiftKey && !isMacOS() ? ev.deltaY : ev.deltaX, ev.deltaMode);
|
|
29156
|
+
const deltaY = normalize(ev.shiftKey && !isMacOS() ? ev.deltaX : ev.deltaY, ev.deltaMode);
|
|
29098
29157
|
handler(deltaX, deltaY);
|
|
29099
29158
|
};
|
|
29100
29159
|
return onMouseWheel;
|
|
@@ -29685,7 +29744,7 @@ class Grid extends owl.Component {
|
|
|
29685
29744
|
"Ctrl+Shift+E": () => this.setHorizontalAlign("center"),
|
|
29686
29745
|
"Ctrl+Shift+L": () => this.setHorizontalAlign("left"),
|
|
29687
29746
|
"Ctrl+Shift+R": () => this.setHorizontalAlign("right"),
|
|
29688
|
-
"Ctrl+Shift+V": () =>
|
|
29747
|
+
"Ctrl+Shift+V": () => PASTE_AS_VALUE_ACTION(this.env),
|
|
29689
29748
|
"Ctrl+Shift+<": () => this.clearFormatting(),
|
|
29690
29749
|
"Ctrl+<": () => this.clearFormatting(),
|
|
29691
29750
|
"Ctrl+Shift+ ": () => {
|
|
@@ -29793,17 +29852,17 @@ class Grid extends owl.Component {
|
|
|
29793
29852
|
// ---------------------------------------------------------------------------
|
|
29794
29853
|
// Zone selection with mouse
|
|
29795
29854
|
// ---------------------------------------------------------------------------
|
|
29796
|
-
onCellClicked(col, row, {
|
|
29855
|
+
onCellClicked(col, row, { addZone, expandZone }) {
|
|
29797
29856
|
if (this.env.model.getters.hasOpenedPopover()) {
|
|
29798
29857
|
this.closeOpenedPopover();
|
|
29799
29858
|
}
|
|
29800
29859
|
if (this.env.model.getters.getEditionMode() === "editing") {
|
|
29801
29860
|
interactiveStopEdition(this.env);
|
|
29802
29861
|
}
|
|
29803
|
-
if (
|
|
29862
|
+
if (expandZone) {
|
|
29804
29863
|
this.env.model.selection.setAnchorCorner(col, row);
|
|
29805
29864
|
}
|
|
29806
|
-
else if (
|
|
29865
|
+
else if (addZone) {
|
|
29807
29866
|
this.env.model.selection.addCellToSelection(col, row);
|
|
29808
29867
|
}
|
|
29809
29868
|
else {
|
|
@@ -30606,7 +30665,7 @@ const XLSX_FORMATS_CONVERSION_MAP = {
|
|
|
30606
30665
|
46: "hhhh:mm:ss",
|
|
30607
30666
|
47: "hhhh:mm:ss",
|
|
30608
30667
|
48: undefined,
|
|
30609
|
-
49:
|
|
30668
|
+
49: PLAIN_TEXT_FORMAT,
|
|
30610
30669
|
};
|
|
30611
30670
|
/**
|
|
30612
30671
|
* Mapping format index to format defined by default
|
|
@@ -33531,7 +33590,7 @@ const machine = {
|
|
|
33531
33590
|
SPACE: goTo(State.RightRef),
|
|
33532
33591
|
NUMBER: goTo(State.Found),
|
|
33533
33592
|
REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
|
|
33534
|
-
SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
|
|
33593
|
+
SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
|
|
33535
33594
|
},
|
|
33536
33595
|
[State.RightColumnRef]: {
|
|
33537
33596
|
SPACE: goTo(State.RightColumnRef),
|
|
@@ -33542,6 +33601,7 @@ const machine = {
|
|
|
33542
33601
|
SPACE: goTo(State.RightRowRef),
|
|
33543
33602
|
NUMBER: goTo(State.Found),
|
|
33544
33603
|
REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
|
|
33604
|
+
SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
|
|
33545
33605
|
},
|
|
33546
33606
|
[State.Found]: {},
|
|
33547
33607
|
};
|
|
@@ -34738,7 +34798,7 @@ class BordersPlugin extends CorePlugin {
|
|
|
34738
34798
|
*/
|
|
34739
34799
|
function getBorderId(border) {
|
|
34740
34800
|
for (let [key, value] of Object.entries(borders)) {
|
|
34741
|
-
if (
|
|
34801
|
+
if (deepEquals(value, border)) {
|
|
34742
34802
|
return parseInt(key, 10);
|
|
34743
34803
|
}
|
|
34744
34804
|
}
|
|
@@ -35909,7 +35969,9 @@ class CellPlugin extends CorePlugin {
|
|
|
35909
35969
|
}
|
|
35910
35970
|
createLiteralCell(id, content, format, style) {
|
|
35911
35971
|
const locale = this.getters.getLocale();
|
|
35912
|
-
|
|
35972
|
+
if (format !== PLAIN_TEXT_FORMAT) {
|
|
35973
|
+
content = toString(parseLiteral(content, locale));
|
|
35974
|
+
}
|
|
35913
35975
|
return {
|
|
35914
35976
|
id,
|
|
35915
35977
|
content,
|
|
@@ -37521,7 +37583,16 @@ class HeaderVisibilityPlugin extends CorePlugin {
|
|
|
37521
37583
|
return consecutiveIndexes;
|
|
37522
37584
|
}
|
|
37523
37585
|
getAllVisibleHeaders(sheetId, dimension) {
|
|
37524
|
-
|
|
37586
|
+
const headers = range(0, this.getters.getNumberHeaders(sheetId, dimension));
|
|
37587
|
+
const foldedHeaders = [];
|
|
37588
|
+
this.getters.getHeaderGroups(sheetId, dimension).forEach((group) => {
|
|
37589
|
+
if (group.isFolded) {
|
|
37590
|
+
foldedHeaders.push(...range(group.start, group.end + 1));
|
|
37591
|
+
}
|
|
37592
|
+
});
|
|
37593
|
+
return headers.filter((i) => {
|
|
37594
|
+
return !this.hiddenHeaders[sheetId][dimension][i] && !foldedHeaders.includes(i);
|
|
37595
|
+
});
|
|
37525
37596
|
}
|
|
37526
37597
|
import(data) {
|
|
37527
37598
|
for (let sheet of data.sheets) {
|
|
@@ -39982,10 +40053,13 @@ class CompilationParametersBuilder {
|
|
|
39982
40053
|
if (evaluatedCell === undefined) {
|
|
39983
40054
|
return { value: null, format: this.getters.getCell(position)?.format };
|
|
39984
40055
|
}
|
|
40056
|
+
if (evaluatedCell.type === CellValueType.error) {
|
|
40057
|
+
throw evaluatedCell.error;
|
|
40058
|
+
}
|
|
39985
40059
|
return evaluatedCell;
|
|
39986
40060
|
}
|
|
39987
40061
|
getEvaluatedCellIfNotEmpty(position) {
|
|
39988
|
-
const evaluatedCell = this.
|
|
40062
|
+
const evaluatedCell = this.computeCell(position);
|
|
39989
40063
|
if (evaluatedCell.type === CellValueType.empty) {
|
|
39990
40064
|
const cell = this.getters.getCell(position);
|
|
39991
40065
|
if (!cell || (!cell.isFormula && cell.content === "")) {
|
|
@@ -39994,13 +40068,6 @@ class CompilationParametersBuilder {
|
|
|
39994
40068
|
}
|
|
39995
40069
|
return evaluatedCell;
|
|
39996
40070
|
}
|
|
39997
|
-
getEvaluatedCell(position) {
|
|
39998
|
-
const evaluatedCell = this.computeCell(position);
|
|
39999
|
-
if (evaluatedCell.type === CellValueType.error) {
|
|
40000
|
-
throw evaluatedCell.error;
|
|
40001
|
-
}
|
|
40002
|
-
return evaluatedCell;
|
|
40003
|
-
}
|
|
40004
40071
|
/**
|
|
40005
40072
|
* Return the values of the cell(s) used in reference, but always in the format of a range even
|
|
40006
40073
|
* if a single cell is referenced. It is a list of col values. This is useful for the formulas that describe parameters as
|
|
@@ -40023,7 +40090,11 @@ class CompilationParametersBuilder {
|
|
|
40023
40090
|
const { top, left, bottom, right } = zone;
|
|
40024
40091
|
const cacheKey = `${sheetId}-${top}-${left}-${bottom}-${right}`;
|
|
40025
40092
|
if (cacheKey in this.rangeCache) {
|
|
40026
|
-
|
|
40093
|
+
const result = this.rangeCache[cacheKey];
|
|
40094
|
+
if (result instanceof EvaluationError) {
|
|
40095
|
+
throw result;
|
|
40096
|
+
}
|
|
40097
|
+
return result;
|
|
40027
40098
|
}
|
|
40028
40099
|
const height = _zone.bottom - _zone.top + 1;
|
|
40029
40100
|
const width = _zone.right - _zone.left + 1;
|
|
@@ -40033,6 +40104,11 @@ class CompilationParametersBuilder {
|
|
|
40033
40104
|
const colIndex = col - _zone.left;
|
|
40034
40105
|
matrix[colIndex] = new Array(height);
|
|
40035
40106
|
for (let row = _zone.top; row <= _zone.bottom; row++) {
|
|
40107
|
+
const evaluatedCell = this.getEvaluatedCellIfNotEmpty({ sheetId, col, row });
|
|
40108
|
+
if (evaluatedCell?.type === CellValueType.error) {
|
|
40109
|
+
this.rangeCache[cacheKey] = evaluatedCell.error;
|
|
40110
|
+
throw evaluatedCell.error;
|
|
40111
|
+
}
|
|
40036
40112
|
const rowIndex = row - _zone.top;
|
|
40037
40113
|
matrix[colIndex][rowIndex] = this.readCell({ sheetId, col, row });
|
|
40038
40114
|
}
|
|
@@ -40042,56 +40118,758 @@ class CompilationParametersBuilder {
|
|
|
40042
40118
|
}
|
|
40043
40119
|
}
|
|
40044
40120
|
|
|
40121
|
+
function quickselect(arr, k, left, right, compare) {
|
|
40122
|
+
quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
|
|
40123
|
+
}
|
|
40124
|
+
|
|
40125
|
+
function quickselectStep(arr, k, left, right, compare) {
|
|
40126
|
+
|
|
40127
|
+
while (right > left) {
|
|
40128
|
+
if (right - left > 600) {
|
|
40129
|
+
var n = right - left + 1;
|
|
40130
|
+
var m = k - left + 1;
|
|
40131
|
+
var z = Math.log(n);
|
|
40132
|
+
var s = 0.5 * Math.exp(2 * z / 3);
|
|
40133
|
+
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
|
|
40134
|
+
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
|
|
40135
|
+
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
|
|
40136
|
+
quickselectStep(arr, k, newLeft, newRight, compare);
|
|
40137
|
+
}
|
|
40138
|
+
|
|
40139
|
+
var t = arr[k];
|
|
40140
|
+
var i = left;
|
|
40141
|
+
var j = right;
|
|
40142
|
+
|
|
40143
|
+
swap(arr, left, k);
|
|
40144
|
+
if (compare(arr[right], t) > 0) swap(arr, left, right);
|
|
40145
|
+
|
|
40146
|
+
while (i < j) {
|
|
40147
|
+
swap(arr, i, j);
|
|
40148
|
+
i++;
|
|
40149
|
+
j--;
|
|
40150
|
+
while (compare(arr[i], t) < 0) i++;
|
|
40151
|
+
while (compare(arr[j], t) > 0) j--;
|
|
40152
|
+
}
|
|
40153
|
+
|
|
40154
|
+
if (compare(arr[left], t) === 0) swap(arr, left, j);
|
|
40155
|
+
else {
|
|
40156
|
+
j++;
|
|
40157
|
+
swap(arr, j, right);
|
|
40158
|
+
}
|
|
40159
|
+
|
|
40160
|
+
if (j <= k) left = j + 1;
|
|
40161
|
+
if (k <= j) right = j - 1;
|
|
40162
|
+
}
|
|
40163
|
+
}
|
|
40164
|
+
|
|
40165
|
+
function swap(arr, i, j) {
|
|
40166
|
+
var tmp = arr[i];
|
|
40167
|
+
arr[i] = arr[j];
|
|
40168
|
+
arr[j] = tmp;
|
|
40169
|
+
}
|
|
40170
|
+
|
|
40171
|
+
function defaultCompare(a, b) {
|
|
40172
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
40173
|
+
}
|
|
40174
|
+
|
|
40175
|
+
class RBush {
|
|
40176
|
+
constructor(maxEntries = 9) {
|
|
40177
|
+
// max entries in a node is 9 by default; min node fill is 40% for best performance
|
|
40178
|
+
this._maxEntries = Math.max(4, maxEntries);
|
|
40179
|
+
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
|
|
40180
|
+
this.clear();
|
|
40181
|
+
}
|
|
40182
|
+
|
|
40183
|
+
all() {
|
|
40184
|
+
return this._all(this.data, []);
|
|
40185
|
+
}
|
|
40186
|
+
|
|
40187
|
+
search(bbox) {
|
|
40188
|
+
let node = this.data;
|
|
40189
|
+
const result = [];
|
|
40190
|
+
|
|
40191
|
+
if (!intersects(bbox, node)) return result;
|
|
40192
|
+
|
|
40193
|
+
const toBBox = this.toBBox;
|
|
40194
|
+
const nodesToSearch = [];
|
|
40195
|
+
|
|
40196
|
+
while (node) {
|
|
40197
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
40198
|
+
const child = node.children[i];
|
|
40199
|
+
const childBBox = node.leaf ? toBBox(child) : child;
|
|
40200
|
+
|
|
40201
|
+
if (intersects(bbox, childBBox)) {
|
|
40202
|
+
if (node.leaf) result.push(child);
|
|
40203
|
+
else if (contains(bbox, childBBox)) this._all(child, result);
|
|
40204
|
+
else nodesToSearch.push(child);
|
|
40205
|
+
}
|
|
40206
|
+
}
|
|
40207
|
+
node = nodesToSearch.pop();
|
|
40208
|
+
}
|
|
40209
|
+
|
|
40210
|
+
return result;
|
|
40211
|
+
}
|
|
40212
|
+
|
|
40213
|
+
collides(bbox) {
|
|
40214
|
+
let node = this.data;
|
|
40215
|
+
|
|
40216
|
+
if (!intersects(bbox, node)) return false;
|
|
40217
|
+
|
|
40218
|
+
const nodesToSearch = [];
|
|
40219
|
+
while (node) {
|
|
40220
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
40221
|
+
const child = node.children[i];
|
|
40222
|
+
const childBBox = node.leaf ? this.toBBox(child) : child;
|
|
40223
|
+
|
|
40224
|
+
if (intersects(bbox, childBBox)) {
|
|
40225
|
+
if (node.leaf || contains(bbox, childBBox)) return true;
|
|
40226
|
+
nodesToSearch.push(child);
|
|
40227
|
+
}
|
|
40228
|
+
}
|
|
40229
|
+
node = nodesToSearch.pop();
|
|
40230
|
+
}
|
|
40231
|
+
|
|
40232
|
+
return false;
|
|
40233
|
+
}
|
|
40234
|
+
|
|
40235
|
+
load(data) {
|
|
40236
|
+
if (!(data && data.length)) return this;
|
|
40237
|
+
|
|
40238
|
+
if (data.length < this._minEntries) {
|
|
40239
|
+
for (let i = 0; i < data.length; i++) {
|
|
40240
|
+
this.insert(data[i]);
|
|
40241
|
+
}
|
|
40242
|
+
return this;
|
|
40243
|
+
}
|
|
40244
|
+
|
|
40245
|
+
// recursively build the tree with the given data from scratch using OMT algorithm
|
|
40246
|
+
let node = this._build(data.slice(), 0, data.length - 1, 0);
|
|
40247
|
+
|
|
40248
|
+
if (!this.data.children.length) {
|
|
40249
|
+
// save as is if tree is empty
|
|
40250
|
+
this.data = node;
|
|
40251
|
+
|
|
40252
|
+
} else if (this.data.height === node.height) {
|
|
40253
|
+
// split root if trees have the same height
|
|
40254
|
+
this._splitRoot(this.data, node);
|
|
40255
|
+
|
|
40256
|
+
} else {
|
|
40257
|
+
if (this.data.height < node.height) {
|
|
40258
|
+
// swap trees if inserted one is bigger
|
|
40259
|
+
const tmpNode = this.data;
|
|
40260
|
+
this.data = node;
|
|
40261
|
+
node = tmpNode;
|
|
40262
|
+
}
|
|
40263
|
+
|
|
40264
|
+
// insert the small tree into the large tree at appropriate level
|
|
40265
|
+
this._insert(node, this.data.height - node.height - 1, true);
|
|
40266
|
+
}
|
|
40267
|
+
|
|
40268
|
+
return this;
|
|
40269
|
+
}
|
|
40270
|
+
|
|
40271
|
+
insert(item) {
|
|
40272
|
+
if (item) this._insert(item, this.data.height - 1);
|
|
40273
|
+
return this;
|
|
40274
|
+
}
|
|
40275
|
+
|
|
40276
|
+
clear() {
|
|
40277
|
+
this.data = createNode([]);
|
|
40278
|
+
return this;
|
|
40279
|
+
}
|
|
40280
|
+
|
|
40281
|
+
remove(item, equalsFn) {
|
|
40282
|
+
if (!item) return this;
|
|
40283
|
+
|
|
40284
|
+
let node = this.data;
|
|
40285
|
+
const bbox = this.toBBox(item);
|
|
40286
|
+
const path = [];
|
|
40287
|
+
const indexes = [];
|
|
40288
|
+
let i, parent, goingUp;
|
|
40289
|
+
|
|
40290
|
+
// depth-first iterative tree traversal
|
|
40291
|
+
while (node || path.length) {
|
|
40292
|
+
|
|
40293
|
+
if (!node) { // go up
|
|
40294
|
+
node = path.pop();
|
|
40295
|
+
parent = path[path.length - 1];
|
|
40296
|
+
i = indexes.pop();
|
|
40297
|
+
goingUp = true;
|
|
40298
|
+
}
|
|
40299
|
+
|
|
40300
|
+
if (node.leaf) { // check current node
|
|
40301
|
+
const index = findItem(item, node.children, equalsFn);
|
|
40302
|
+
|
|
40303
|
+
if (index !== -1) {
|
|
40304
|
+
// item found, remove the item and condense tree upwards
|
|
40305
|
+
node.children.splice(index, 1);
|
|
40306
|
+
path.push(node);
|
|
40307
|
+
this._condense(path);
|
|
40308
|
+
return this;
|
|
40309
|
+
}
|
|
40310
|
+
}
|
|
40311
|
+
|
|
40312
|
+
if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
|
|
40313
|
+
path.push(node);
|
|
40314
|
+
indexes.push(i);
|
|
40315
|
+
i = 0;
|
|
40316
|
+
parent = node;
|
|
40317
|
+
node = node.children[0];
|
|
40318
|
+
|
|
40319
|
+
} else if (parent) { // go right
|
|
40320
|
+
i++;
|
|
40321
|
+
node = parent.children[i];
|
|
40322
|
+
goingUp = false;
|
|
40323
|
+
|
|
40324
|
+
} else node = null; // nothing found
|
|
40325
|
+
}
|
|
40326
|
+
|
|
40327
|
+
return this;
|
|
40328
|
+
}
|
|
40329
|
+
|
|
40330
|
+
toBBox(item) { return item; }
|
|
40331
|
+
|
|
40332
|
+
compareMinX(a, b) { return a.minX - b.minX; }
|
|
40333
|
+
compareMinY(a, b) { return a.minY - b.minY; }
|
|
40334
|
+
|
|
40335
|
+
toJSON() { return this.data; }
|
|
40336
|
+
|
|
40337
|
+
fromJSON(data) {
|
|
40338
|
+
this.data = data;
|
|
40339
|
+
return this;
|
|
40340
|
+
}
|
|
40341
|
+
|
|
40342
|
+
_all(node, result) {
|
|
40343
|
+
const nodesToSearch = [];
|
|
40344
|
+
while (node) {
|
|
40345
|
+
if (node.leaf) result.push(...node.children);
|
|
40346
|
+
else nodesToSearch.push(...node.children);
|
|
40347
|
+
|
|
40348
|
+
node = nodesToSearch.pop();
|
|
40349
|
+
}
|
|
40350
|
+
return result;
|
|
40351
|
+
}
|
|
40352
|
+
|
|
40353
|
+
_build(items, left, right, height) {
|
|
40354
|
+
|
|
40355
|
+
const N = right - left + 1;
|
|
40356
|
+
let M = this._maxEntries;
|
|
40357
|
+
let node;
|
|
40358
|
+
|
|
40359
|
+
if (N <= M) {
|
|
40360
|
+
// reached leaf level; return leaf
|
|
40361
|
+
node = createNode(items.slice(left, right + 1));
|
|
40362
|
+
calcBBox(node, this.toBBox);
|
|
40363
|
+
return node;
|
|
40364
|
+
}
|
|
40365
|
+
|
|
40366
|
+
if (!height) {
|
|
40367
|
+
// target height of the bulk-loaded tree
|
|
40368
|
+
height = Math.ceil(Math.log(N) / Math.log(M));
|
|
40369
|
+
|
|
40370
|
+
// target number of root entries to maximize storage utilization
|
|
40371
|
+
M = Math.ceil(N / Math.pow(M, height - 1));
|
|
40372
|
+
}
|
|
40373
|
+
|
|
40374
|
+
node = createNode([]);
|
|
40375
|
+
node.leaf = false;
|
|
40376
|
+
node.height = height;
|
|
40377
|
+
|
|
40378
|
+
// split the items into M mostly square tiles
|
|
40379
|
+
|
|
40380
|
+
const N2 = Math.ceil(N / M);
|
|
40381
|
+
const N1 = N2 * Math.ceil(Math.sqrt(M));
|
|
40382
|
+
|
|
40383
|
+
multiSelect(items, left, right, N1, this.compareMinX);
|
|
40384
|
+
|
|
40385
|
+
for (let i = left; i <= right; i += N1) {
|
|
40386
|
+
|
|
40387
|
+
const right2 = Math.min(i + N1 - 1, right);
|
|
40388
|
+
|
|
40389
|
+
multiSelect(items, i, right2, N2, this.compareMinY);
|
|
40390
|
+
|
|
40391
|
+
for (let j = i; j <= right2; j += N2) {
|
|
40392
|
+
|
|
40393
|
+
const right3 = Math.min(j + N2 - 1, right2);
|
|
40394
|
+
|
|
40395
|
+
// pack each entry recursively
|
|
40396
|
+
node.children.push(this._build(items, j, right3, height - 1));
|
|
40397
|
+
}
|
|
40398
|
+
}
|
|
40399
|
+
|
|
40400
|
+
calcBBox(node, this.toBBox);
|
|
40401
|
+
|
|
40402
|
+
return node;
|
|
40403
|
+
}
|
|
40404
|
+
|
|
40405
|
+
_chooseSubtree(bbox, node, level, path) {
|
|
40406
|
+
while (true) {
|
|
40407
|
+
path.push(node);
|
|
40408
|
+
|
|
40409
|
+
if (node.leaf || path.length - 1 === level) break;
|
|
40410
|
+
|
|
40411
|
+
let minArea = Infinity;
|
|
40412
|
+
let minEnlargement = Infinity;
|
|
40413
|
+
let targetNode;
|
|
40414
|
+
|
|
40415
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
40416
|
+
const child = node.children[i];
|
|
40417
|
+
const area = bboxArea(child);
|
|
40418
|
+
const enlargement = enlargedArea(bbox, child) - area;
|
|
40419
|
+
|
|
40420
|
+
// choose entry with the least area enlargement
|
|
40421
|
+
if (enlargement < minEnlargement) {
|
|
40422
|
+
minEnlargement = enlargement;
|
|
40423
|
+
minArea = area < minArea ? area : minArea;
|
|
40424
|
+
targetNode = child;
|
|
40425
|
+
|
|
40426
|
+
} else if (enlargement === minEnlargement) {
|
|
40427
|
+
// otherwise choose one with the smallest area
|
|
40428
|
+
if (area < minArea) {
|
|
40429
|
+
minArea = area;
|
|
40430
|
+
targetNode = child;
|
|
40431
|
+
}
|
|
40432
|
+
}
|
|
40433
|
+
}
|
|
40434
|
+
|
|
40435
|
+
node = targetNode || node.children[0];
|
|
40436
|
+
}
|
|
40437
|
+
|
|
40438
|
+
return node;
|
|
40439
|
+
}
|
|
40440
|
+
|
|
40441
|
+
_insert(item, level, isNode) {
|
|
40442
|
+
const bbox = isNode ? item : this.toBBox(item);
|
|
40443
|
+
const insertPath = [];
|
|
40444
|
+
|
|
40445
|
+
// find the best node for accommodating the item, saving all nodes along the path too
|
|
40446
|
+
const node = this._chooseSubtree(bbox, this.data, level, insertPath);
|
|
40447
|
+
|
|
40448
|
+
// put the item into the node
|
|
40449
|
+
node.children.push(item);
|
|
40450
|
+
extend(node, bbox);
|
|
40451
|
+
|
|
40452
|
+
// split on node overflow; propagate upwards if necessary
|
|
40453
|
+
while (level >= 0) {
|
|
40454
|
+
if (insertPath[level].children.length > this._maxEntries) {
|
|
40455
|
+
this._split(insertPath, level);
|
|
40456
|
+
level--;
|
|
40457
|
+
} else break;
|
|
40458
|
+
}
|
|
40459
|
+
|
|
40460
|
+
// adjust bboxes along the insertion path
|
|
40461
|
+
this._adjustParentBBoxes(bbox, insertPath, level);
|
|
40462
|
+
}
|
|
40463
|
+
|
|
40464
|
+
// split overflowed node into two
|
|
40465
|
+
_split(insertPath, level) {
|
|
40466
|
+
const node = insertPath[level];
|
|
40467
|
+
const M = node.children.length;
|
|
40468
|
+
const m = this._minEntries;
|
|
40469
|
+
|
|
40470
|
+
this._chooseSplitAxis(node, m, M);
|
|
40471
|
+
|
|
40472
|
+
const splitIndex = this._chooseSplitIndex(node, m, M);
|
|
40473
|
+
|
|
40474
|
+
const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
|
|
40475
|
+
newNode.height = node.height;
|
|
40476
|
+
newNode.leaf = node.leaf;
|
|
40477
|
+
|
|
40478
|
+
calcBBox(node, this.toBBox);
|
|
40479
|
+
calcBBox(newNode, this.toBBox);
|
|
40480
|
+
|
|
40481
|
+
if (level) insertPath[level - 1].children.push(newNode);
|
|
40482
|
+
else this._splitRoot(node, newNode);
|
|
40483
|
+
}
|
|
40484
|
+
|
|
40485
|
+
_splitRoot(node, newNode) {
|
|
40486
|
+
// split root node
|
|
40487
|
+
this.data = createNode([node, newNode]);
|
|
40488
|
+
this.data.height = node.height + 1;
|
|
40489
|
+
this.data.leaf = false;
|
|
40490
|
+
calcBBox(this.data, this.toBBox);
|
|
40491
|
+
}
|
|
40492
|
+
|
|
40493
|
+
_chooseSplitIndex(node, m, M) {
|
|
40494
|
+
let index;
|
|
40495
|
+
let minOverlap = Infinity;
|
|
40496
|
+
let minArea = Infinity;
|
|
40497
|
+
|
|
40498
|
+
for (let i = m; i <= M - m; i++) {
|
|
40499
|
+
const bbox1 = distBBox(node, 0, i, this.toBBox);
|
|
40500
|
+
const bbox2 = distBBox(node, i, M, this.toBBox);
|
|
40501
|
+
|
|
40502
|
+
const overlap = intersectionArea(bbox1, bbox2);
|
|
40503
|
+
const area = bboxArea(bbox1) + bboxArea(bbox2);
|
|
40504
|
+
|
|
40505
|
+
// choose distribution with minimum overlap
|
|
40506
|
+
if (overlap < minOverlap) {
|
|
40507
|
+
minOverlap = overlap;
|
|
40508
|
+
index = i;
|
|
40509
|
+
|
|
40510
|
+
minArea = area < minArea ? area : minArea;
|
|
40511
|
+
|
|
40512
|
+
} else if (overlap === minOverlap) {
|
|
40513
|
+
// otherwise choose distribution with minimum area
|
|
40514
|
+
if (area < minArea) {
|
|
40515
|
+
minArea = area;
|
|
40516
|
+
index = i;
|
|
40517
|
+
}
|
|
40518
|
+
}
|
|
40519
|
+
}
|
|
40520
|
+
|
|
40521
|
+
return index || M - m;
|
|
40522
|
+
}
|
|
40523
|
+
|
|
40524
|
+
// sorts node children by the best axis for split
|
|
40525
|
+
_chooseSplitAxis(node, m, M) {
|
|
40526
|
+
const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
|
|
40527
|
+
const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
|
|
40528
|
+
const xMargin = this._allDistMargin(node, m, M, compareMinX);
|
|
40529
|
+
const yMargin = this._allDistMargin(node, m, M, compareMinY);
|
|
40530
|
+
|
|
40531
|
+
// if total distributions margin value is minimal for x, sort by minX,
|
|
40532
|
+
// otherwise it's already sorted by minY
|
|
40533
|
+
if (xMargin < yMargin) node.children.sort(compareMinX);
|
|
40534
|
+
}
|
|
40535
|
+
|
|
40536
|
+
// total margin of all possible split distributions where each node is at least m full
|
|
40537
|
+
_allDistMargin(node, m, M, compare) {
|
|
40538
|
+
node.children.sort(compare);
|
|
40539
|
+
|
|
40540
|
+
const toBBox = this.toBBox;
|
|
40541
|
+
const leftBBox = distBBox(node, 0, m, toBBox);
|
|
40542
|
+
const rightBBox = distBBox(node, M - m, M, toBBox);
|
|
40543
|
+
let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
|
|
40544
|
+
|
|
40545
|
+
for (let i = m; i < M - m; i++) {
|
|
40546
|
+
const child = node.children[i];
|
|
40547
|
+
extend(leftBBox, node.leaf ? toBBox(child) : child);
|
|
40548
|
+
margin += bboxMargin(leftBBox);
|
|
40549
|
+
}
|
|
40550
|
+
|
|
40551
|
+
for (let i = M - m - 1; i >= m; i--) {
|
|
40552
|
+
const child = node.children[i];
|
|
40553
|
+
extend(rightBBox, node.leaf ? toBBox(child) : child);
|
|
40554
|
+
margin += bboxMargin(rightBBox);
|
|
40555
|
+
}
|
|
40556
|
+
|
|
40557
|
+
return margin;
|
|
40558
|
+
}
|
|
40559
|
+
|
|
40560
|
+
_adjustParentBBoxes(bbox, path, level) {
|
|
40561
|
+
// adjust bboxes along the given tree path
|
|
40562
|
+
for (let i = level; i >= 0; i--) {
|
|
40563
|
+
extend(path[i], bbox);
|
|
40564
|
+
}
|
|
40565
|
+
}
|
|
40566
|
+
|
|
40567
|
+
_condense(path) {
|
|
40568
|
+
// go through the path, removing empty nodes and updating bboxes
|
|
40569
|
+
for (let i = path.length - 1, siblings; i >= 0; i--) {
|
|
40570
|
+
if (path[i].children.length === 0) {
|
|
40571
|
+
if (i > 0) {
|
|
40572
|
+
siblings = path[i - 1].children;
|
|
40573
|
+
siblings.splice(siblings.indexOf(path[i]), 1);
|
|
40574
|
+
|
|
40575
|
+
} else this.clear();
|
|
40576
|
+
|
|
40577
|
+
} else calcBBox(path[i], this.toBBox);
|
|
40578
|
+
}
|
|
40579
|
+
}
|
|
40580
|
+
}
|
|
40581
|
+
|
|
40582
|
+
function findItem(item, items, equalsFn) {
|
|
40583
|
+
if (!equalsFn) return items.indexOf(item);
|
|
40584
|
+
|
|
40585
|
+
for (let i = 0; i < items.length; i++) {
|
|
40586
|
+
if (equalsFn(item, items[i])) return i;
|
|
40587
|
+
}
|
|
40588
|
+
return -1;
|
|
40589
|
+
}
|
|
40590
|
+
|
|
40591
|
+
// calculate node's bbox from bboxes of its children
|
|
40592
|
+
function calcBBox(node, toBBox) {
|
|
40593
|
+
distBBox(node, 0, node.children.length, toBBox, node);
|
|
40594
|
+
}
|
|
40595
|
+
|
|
40596
|
+
// min bounding rectangle of node children from k to p-1
|
|
40597
|
+
function distBBox(node, k, p, toBBox, destNode) {
|
|
40598
|
+
if (!destNode) destNode = createNode(null);
|
|
40599
|
+
destNode.minX = Infinity;
|
|
40600
|
+
destNode.minY = Infinity;
|
|
40601
|
+
destNode.maxX = -Infinity;
|
|
40602
|
+
destNode.maxY = -Infinity;
|
|
40603
|
+
|
|
40604
|
+
for (let i = k; i < p; i++) {
|
|
40605
|
+
const child = node.children[i];
|
|
40606
|
+
extend(destNode, node.leaf ? toBBox(child) : child);
|
|
40607
|
+
}
|
|
40608
|
+
|
|
40609
|
+
return destNode;
|
|
40610
|
+
}
|
|
40611
|
+
|
|
40612
|
+
function extend(a, b) {
|
|
40613
|
+
a.minX = Math.min(a.minX, b.minX);
|
|
40614
|
+
a.minY = Math.min(a.minY, b.minY);
|
|
40615
|
+
a.maxX = Math.max(a.maxX, b.maxX);
|
|
40616
|
+
a.maxY = Math.max(a.maxY, b.maxY);
|
|
40617
|
+
return a;
|
|
40618
|
+
}
|
|
40619
|
+
|
|
40620
|
+
function compareNodeMinX(a, b) { return a.minX - b.minX; }
|
|
40621
|
+
function compareNodeMinY(a, b) { return a.minY - b.minY; }
|
|
40622
|
+
|
|
40623
|
+
function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
|
|
40624
|
+
function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
|
|
40625
|
+
|
|
40626
|
+
function enlargedArea(a, b) {
|
|
40627
|
+
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
|
|
40628
|
+
(Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
|
|
40629
|
+
}
|
|
40630
|
+
|
|
40631
|
+
function intersectionArea(a, b) {
|
|
40632
|
+
const minX = Math.max(a.minX, b.minX);
|
|
40633
|
+
const minY = Math.max(a.minY, b.minY);
|
|
40634
|
+
const maxX = Math.min(a.maxX, b.maxX);
|
|
40635
|
+
const maxY = Math.min(a.maxY, b.maxY);
|
|
40636
|
+
|
|
40637
|
+
return Math.max(0, maxX - minX) *
|
|
40638
|
+
Math.max(0, maxY - minY);
|
|
40639
|
+
}
|
|
40640
|
+
|
|
40641
|
+
function contains(a, b) {
|
|
40642
|
+
return a.minX <= b.minX &&
|
|
40643
|
+
a.minY <= b.minY &&
|
|
40644
|
+
b.maxX <= a.maxX &&
|
|
40645
|
+
b.maxY <= a.maxY;
|
|
40646
|
+
}
|
|
40647
|
+
|
|
40648
|
+
function intersects(a, b) {
|
|
40649
|
+
return b.minX <= a.maxX &&
|
|
40650
|
+
b.minY <= a.maxY &&
|
|
40651
|
+
b.maxX >= a.minX &&
|
|
40652
|
+
b.maxY >= a.minY;
|
|
40653
|
+
}
|
|
40654
|
+
|
|
40655
|
+
function createNode(children) {
|
|
40656
|
+
return {
|
|
40657
|
+
children,
|
|
40658
|
+
height: 1,
|
|
40659
|
+
leaf: true,
|
|
40660
|
+
minX: Infinity,
|
|
40661
|
+
minY: Infinity,
|
|
40662
|
+
maxX: -Infinity,
|
|
40663
|
+
maxY: -Infinity
|
|
40664
|
+
};
|
|
40665
|
+
}
|
|
40666
|
+
|
|
40667
|
+
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
|
|
40668
|
+
// combines selection algorithm with binary divide & conquer approach
|
|
40669
|
+
|
|
40670
|
+
function multiSelect(arr, left, right, n, compare) {
|
|
40671
|
+
const stack = [left, right];
|
|
40672
|
+
|
|
40673
|
+
while (stack.length) {
|
|
40674
|
+
right = stack.pop();
|
|
40675
|
+
left = stack.pop();
|
|
40676
|
+
|
|
40677
|
+
if (right - left <= n) continue;
|
|
40678
|
+
|
|
40679
|
+
const mid = left + Math.ceil((right - left) / n / 2) * n;
|
|
40680
|
+
quickselect(arr, mid, left, right, compare);
|
|
40681
|
+
|
|
40682
|
+
stack.push(left, mid, mid, right);
|
|
40683
|
+
}
|
|
40684
|
+
}
|
|
40685
|
+
|
|
40045
40686
|
/**
|
|
40046
|
-
*
|
|
40687
|
+
* R-Tree Data Structure
|
|
40688
|
+
*
|
|
40689
|
+
* R-Tree is a spatial data structure used for efficient indexing and querying
|
|
40690
|
+
* of multi-dimensional objects, particularly in geometric and spatial applications.
|
|
40691
|
+
*
|
|
40692
|
+
* It organizes objects into a tree hierarchy, grouping nearby objects together
|
|
40693
|
+
* in bounding boxes. Each node in the tree represents a bounding box that
|
|
40694
|
+
* contains its child nodes or leaf objects. This hierarchical structure allows
|
|
40695
|
+
* for faster spatial queries.
|
|
40696
|
+
*
|
|
40697
|
+
* @see https://en.wikipedia.org/wiki/R-tree
|
|
40698
|
+
*
|
|
40699
|
+
* Consider a 2D Space with four zones: A, B, C, D
|
|
40700
|
+
* +--------------------------+
|
|
40701
|
+
* | |
|
|
40702
|
+
* | +---+ +-------+ |
|
|
40703
|
+
* | | A | | B | |
|
|
40704
|
+
* | +---+ +-------+ |
|
|
40705
|
+
* | |
|
|
40706
|
+
* | |
|
|
40707
|
+
* | +---+ |
|
|
40708
|
+
* | | C | |
|
|
40709
|
+
* | +---+ |
|
|
40710
|
+
* | +-----------+ |
|
|
40711
|
+
* | | D | |
|
|
40712
|
+
* | +-----------+ |
|
|
40713
|
+
* | |
|
|
40714
|
+
* +--------------------------+
|
|
40715
|
+
*
|
|
40716
|
+
* It groups together zones that are spatially close into a minimum bounding box.
|
|
40717
|
+
* For example, A and B are grouped together in rectangle R1, and C and D are grouped
|
|
40718
|
+
* in R2.
|
|
40719
|
+
*
|
|
40720
|
+
* R0
|
|
40721
|
+
* +--------------------------+
|
|
40722
|
+
* | R1 |
|
|
40723
|
+
* | +-----------------+ |
|
|
40724
|
+
* | | A | | B | |
|
|
40725
|
+
* | +-----------------+ |
|
|
40726
|
+
* | |
|
|
40727
|
+
* | R2 |
|
|
40728
|
+
* | +---+---+---+ |
|
|
40729
|
+
* | | | C | | |
|
|
40730
|
+
* | | +---+ | |
|
|
40731
|
+
* | +-----------+ |
|
|
40732
|
+
* | | D | |
|
|
40733
|
+
* | +-----------+ |
|
|
40734
|
+
* | |
|
|
40735
|
+
* +--------------------------+
|
|
40736
|
+
*
|
|
40737
|
+
* The tree would look like this:
|
|
40738
|
+
* R0
|
|
40739
|
+
* / \
|
|
40740
|
+
* / \
|
|
40741
|
+
* R1 R2
|
|
40742
|
+
* | |
|
|
40743
|
+
* A,B C,D
|
|
40744
|
+
|
|
40745
|
+
* Choosing how to group the zones is crucial for the performance of the tree.
|
|
40746
|
+
* Key considerations include avoiding excessive empty space coverage and minimizing overlap
|
|
40747
|
+
* to reduce the number of subtrees processed during searches.
|
|
40748
|
+
*
|
|
40749
|
+
* Various heuristics exist for determining the optimal grouping strategy, such as "least enlargement"
|
|
40750
|
+
* which prioritizes grouping nodes resulting in the smallest increase in bounding box size. In cases where
|
|
40751
|
+
* the choice cannot be made based on this criterion due to the same enlargement for different groupings,
|
|
40752
|
+
* we then evaluate "least area," aiming to minimize the overall area of bounding boxes.
|
|
40753
|
+
*
|
|
40754
|
+
* This implementation is tailored for spreadsheet use, indexing objects associated
|
|
40755
|
+
* with a zone and a sheet.
|
|
40756
|
+
*
|
|
40757
|
+
* It uses the RBush library under the hood. One 2D RBush R-tree per sheet.
|
|
40758
|
+
* @see https://github.com/mourner/rbush
|
|
40759
|
+
*/
|
|
40760
|
+
class SpreadsheetRTree {
|
|
40761
|
+
/**
|
|
40762
|
+
* One 2D R-tree per sheet
|
|
40763
|
+
*/
|
|
40764
|
+
rTrees = {};
|
|
40765
|
+
/**
|
|
40766
|
+
* Bulk-inserts the given items into the tree. Bulk insertion is usually ~2-3 times
|
|
40767
|
+
* faster than inserting items one by one. After bulk loading (bulk insertion into
|
|
40768
|
+
* an empty tree), subsequent query performance is also ~20-30% better.
|
|
40769
|
+
*/
|
|
40770
|
+
constructor(items = []) {
|
|
40771
|
+
const rangesPerSheet = {};
|
|
40772
|
+
for (const item of items) {
|
|
40773
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40774
|
+
if (!rangesPerSheet[sheetId]) {
|
|
40775
|
+
rangesPerSheet[sheetId] = [];
|
|
40776
|
+
}
|
|
40777
|
+
rangesPerSheet[sheetId].push(item);
|
|
40778
|
+
}
|
|
40779
|
+
for (const sheetId in rangesPerSheet) {
|
|
40780
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
40781
|
+
this.rTrees[sheetId].load(rangesPerSheet[sheetId]); // bulk-insert
|
|
40782
|
+
}
|
|
40783
|
+
}
|
|
40784
|
+
insert(item) {
|
|
40785
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40786
|
+
if (!this.rTrees[sheetId]) {
|
|
40787
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
40788
|
+
}
|
|
40789
|
+
this.rTrees[sheetId].insert(item);
|
|
40790
|
+
}
|
|
40791
|
+
search({ zone, sheetId }) {
|
|
40792
|
+
if (!this.rTrees[sheetId]) {
|
|
40793
|
+
return [];
|
|
40794
|
+
}
|
|
40795
|
+
return this.rTrees[sheetId].search({
|
|
40796
|
+
minX: zone.left,
|
|
40797
|
+
minY: zone.top,
|
|
40798
|
+
maxX: zone.right,
|
|
40799
|
+
maxY: zone.bottom,
|
|
40800
|
+
});
|
|
40801
|
+
}
|
|
40802
|
+
remove(item) {
|
|
40803
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40804
|
+
if (!this.rTrees[sheetId]) {
|
|
40805
|
+
return;
|
|
40806
|
+
}
|
|
40807
|
+
this.rTrees[sheetId].remove(item, deepEquals);
|
|
40808
|
+
}
|
|
40809
|
+
}
|
|
40810
|
+
/**
|
|
40811
|
+
* RBush extension to use zones as bounding boxes
|
|
40812
|
+
*/
|
|
40813
|
+
class ZoneRBush extends RBush {
|
|
40814
|
+
toBBox({ boundingBox }) {
|
|
40815
|
+
const zone = boundingBox.zone;
|
|
40816
|
+
return {
|
|
40817
|
+
minX: zone.left,
|
|
40818
|
+
minY: zone.top,
|
|
40819
|
+
maxX: zone.right,
|
|
40820
|
+
maxY: zone.bottom,
|
|
40821
|
+
};
|
|
40822
|
+
}
|
|
40823
|
+
compareMinX(a, b) {
|
|
40824
|
+
return a.boundingBox.zone.left - b.boundingBox.zone.left;
|
|
40825
|
+
}
|
|
40826
|
+
compareMinY(a, b) {
|
|
40827
|
+
return a.boundingBox.zone.top - b.boundingBox.zone.top;
|
|
40828
|
+
}
|
|
40829
|
+
}
|
|
40830
|
+
|
|
40831
|
+
/**
|
|
40832
|
+
* Implementation of a dependency Graph.
|
|
40047
40833
|
* The graph is used to evaluate the cells in the correct
|
|
40048
40834
|
* order, and should be updated each time a cell's content is modified
|
|
40049
40835
|
*
|
|
40836
|
+
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
40050
40837
|
*/
|
|
40051
40838
|
class FormulaDependencyGraph {
|
|
40052
|
-
|
|
40053
|
-
* Internal structure:
|
|
40054
|
-
* - key: a cell position (encoded as an integer)
|
|
40055
|
-
* - value: a set of cell positions that depends on the key
|
|
40056
|
-
*
|
|
40057
|
-
* Given
|
|
40058
|
-
* - A1:"= B1 + SQRT(B2)"
|
|
40059
|
-
* - C1:"= B1";
|
|
40060
|
-
* - C2:"= C1"
|
|
40061
|
-
*
|
|
40062
|
-
* we will have something like:
|
|
40063
|
-
* - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
|
|
40064
|
-
* - B2 ---> (A1) meaning A1 depends on B2
|
|
40065
|
-
* - C1 ---> (C2) meaning C2 depends on C1
|
|
40066
|
-
*/
|
|
40067
|
-
inverseDependencies = new Map();
|
|
40839
|
+
encoder;
|
|
40068
40840
|
dependencies = new Map();
|
|
40841
|
+
rTree;
|
|
40842
|
+
constructor(encoder, data = []) {
|
|
40843
|
+
this.encoder = encoder;
|
|
40844
|
+
this.rTree = new SpreadsheetRTree(data);
|
|
40845
|
+
}
|
|
40069
40846
|
removeAllDependencies(formulaPositionId) {
|
|
40070
|
-
const
|
|
40071
|
-
if (!
|
|
40847
|
+
const ranges = this.dependencies.get(formulaPositionId);
|
|
40848
|
+
if (!ranges) {
|
|
40072
40849
|
return;
|
|
40073
40850
|
}
|
|
40074
|
-
for (const
|
|
40075
|
-
this.
|
|
40851
|
+
for (const range of ranges) {
|
|
40852
|
+
this.rTree.remove(range);
|
|
40076
40853
|
}
|
|
40077
40854
|
this.dependencies.delete(formulaPositionId);
|
|
40078
40855
|
}
|
|
40079
40856
|
addDependencies(formulaPositionId, dependencies) {
|
|
40080
|
-
|
|
40081
|
-
|
|
40082
|
-
|
|
40083
|
-
|
|
40084
|
-
|
|
40085
|
-
|
|
40086
|
-
|
|
40087
|
-
|
|
40857
|
+
const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
|
|
40858
|
+
data: formulaPositionId,
|
|
40859
|
+
boundingBox: {
|
|
40860
|
+
zone,
|
|
40861
|
+
sheetId,
|
|
40862
|
+
},
|
|
40863
|
+
}));
|
|
40864
|
+
for (const item of rTreeItems) {
|
|
40865
|
+
this.rTree.insert(item);
|
|
40088
40866
|
}
|
|
40089
40867
|
const existingDependencies = this.dependencies.get(formulaPositionId);
|
|
40090
40868
|
if (existingDependencies) {
|
|
40091
|
-
existingDependencies.push(...
|
|
40869
|
+
existingDependencies.push(...rTreeItems);
|
|
40092
40870
|
}
|
|
40093
40871
|
else {
|
|
40094
|
-
this.dependencies.set(formulaPositionId,
|
|
40872
|
+
this.dependencies.set(formulaPositionId, rTreeItems);
|
|
40095
40873
|
}
|
|
40096
40874
|
}
|
|
40097
40875
|
/**
|
|
@@ -40099,20 +40877,20 @@ class FormulaDependencyGraph {
|
|
|
40099
40877
|
* in the correct order they should be evaluated.
|
|
40100
40878
|
* This is called a topological ordering (excluding cycles)
|
|
40101
40879
|
*/
|
|
40102
|
-
getCellsDependingOn(
|
|
40880
|
+
getCellsDependingOn(ranges) {
|
|
40103
40881
|
const visited = new JetSet();
|
|
40104
|
-
const queue = Array.from(
|
|
40882
|
+
const queue = Array.from(ranges).reverse();
|
|
40105
40883
|
while (queue.length > 0) {
|
|
40106
|
-
const
|
|
40107
|
-
visited.add(
|
|
40108
|
-
const
|
|
40109
|
-
for (const
|
|
40110
|
-
if (!visited.has(
|
|
40111
|
-
queue.push(
|
|
40884
|
+
const range = queue.pop();
|
|
40885
|
+
visited.add(...this.encoder.encodeBoundingBox(range));
|
|
40886
|
+
const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
|
|
40887
|
+
for (const positionId of impactedPositionIds) {
|
|
40888
|
+
if (!visited.has(positionId)) {
|
|
40889
|
+
queue.push(this.encoder.decodeToBoundingBox(positionId));
|
|
40112
40890
|
}
|
|
40113
40891
|
}
|
|
40114
40892
|
}
|
|
40115
|
-
visited.delete(...
|
|
40893
|
+
visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
|
|
40116
40894
|
return visited;
|
|
40117
40895
|
}
|
|
40118
40896
|
}
|
|
@@ -40200,9 +40978,9 @@ class Evaluator {
|
|
|
40200
40978
|
context;
|
|
40201
40979
|
getters;
|
|
40202
40980
|
compilationParams;
|
|
40203
|
-
|
|
40981
|
+
encoder = new PositionBitsEncoder();
|
|
40204
40982
|
evaluatedCells = new Map();
|
|
40205
|
-
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
40983
|
+
formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
|
|
40206
40984
|
blockedArrayFormulas = new Set();
|
|
40207
40985
|
spreadingRelations = new SpreadingRelation();
|
|
40208
40986
|
constructor(context, getters) {
|
|
@@ -40211,23 +40989,23 @@ class Evaluator {
|
|
|
40211
40989
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
40212
40990
|
}
|
|
40213
40991
|
getEvaluatedCell(position) {
|
|
40214
|
-
return (this.evaluatedCells.get(this.
|
|
40992
|
+
return (this.evaluatedCells.get(this.encoder.encode(position)) ||
|
|
40215
40993
|
createEvaluatedCell("", { locale: this.getters.getLocale() }));
|
|
40216
40994
|
}
|
|
40217
40995
|
getSpreadPositionsOf(position) {
|
|
40218
|
-
const positionId = this.
|
|
40996
|
+
const positionId = this.encoder.encode(position);
|
|
40219
40997
|
if (!this.spreadingRelations.isArrayFormula(positionId)) {
|
|
40220
40998
|
return [];
|
|
40221
40999
|
}
|
|
40222
|
-
return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map(this.
|
|
41000
|
+
return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map((positionId) => this.encoder.decode(positionId));
|
|
40223
41001
|
}
|
|
40224
41002
|
getArrayFormulaSpreadingOn(position) {
|
|
40225
|
-
const positionId = this.
|
|
41003
|
+
const positionId = this.encoder.encode(position);
|
|
40226
41004
|
const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
|
|
40227
|
-
return formulaPosition !== undefined ? this.
|
|
41005
|
+
return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
|
|
40228
41006
|
}
|
|
40229
41007
|
getEvaluatedPositions() {
|
|
40230
|
-
return [...this.evaluatedCells.keys()].map(this.
|
|
41008
|
+
return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
|
|
40231
41009
|
}
|
|
40232
41010
|
getArrayFormulaSpreadingOnId(positionId) {
|
|
40233
41011
|
if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
|
|
@@ -40237,7 +41015,7 @@ class Evaluator {
|
|
|
40237
41015
|
return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
|
|
40238
41016
|
}
|
|
40239
41017
|
updateDependencies(position) {
|
|
40240
|
-
const positionId = this.
|
|
41018
|
+
const positionId = this.encoder.encode(position);
|
|
40241
41019
|
this.formulaDependencies().removeAllDependencies(positionId);
|
|
40242
41020
|
const dependencies = this.getDirectDependencies(positionId);
|
|
40243
41021
|
this.formulaDependencies().addDependencies(positionId, dependencies);
|
|
@@ -40247,12 +41025,12 @@ class Evaluator {
|
|
|
40247
41025
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
40248
41026
|
}
|
|
40249
41027
|
evaluateCells(positions) {
|
|
40250
|
-
const cells = positions.map(this.
|
|
41028
|
+
const cells = positions.map((p) => this.encoder.encode(p));
|
|
40251
41029
|
const cellsToCompute = new JetSet(cells);
|
|
40252
|
-
const
|
|
41030
|
+
const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
|
|
40253
41031
|
cellsToCompute.add(...this.getCellsDependingOn(cells));
|
|
40254
|
-
cellsToCompute.add(...
|
|
40255
|
-
cellsToCompute.add(...this.getCellsDependingOn(
|
|
41032
|
+
cellsToCompute.add(...arrayFormulasPositionIds);
|
|
41033
|
+
cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
|
|
40256
41034
|
this.evaluate(cellsToCompute);
|
|
40257
41035
|
}
|
|
40258
41036
|
getArrayFormulasImpactedByChangesOf(positionIds) {
|
|
@@ -40275,12 +41053,14 @@ class Evaluator {
|
|
|
40275
41053
|
this.blockedArrayFormulas = new Set();
|
|
40276
41054
|
this.spreadingRelations = new SpreadingRelation();
|
|
40277
41055
|
this.formulaDependencies = lazy(() => {
|
|
40278
|
-
const
|
|
40279
|
-
|
|
40280
|
-
|
|
40281
|
-
|
|
40282
|
-
|
|
40283
|
-
|
|
41056
|
+
const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
|
|
41057
|
+
data: positionId,
|
|
41058
|
+
boundingBox: {
|
|
41059
|
+
zone: range.zone,
|
|
41060
|
+
sheetId: range.sheetId,
|
|
41061
|
+
},
|
|
41062
|
+
})));
|
|
41063
|
+
return new FormulaDependencyGraph(this.encoder, dependencies);
|
|
40284
41064
|
});
|
|
40285
41065
|
}
|
|
40286
41066
|
evaluateAllCells() {
|
|
@@ -40302,7 +41082,7 @@ class Evaluator {
|
|
|
40302
41082
|
for (const sheetId of this.getters.getSheetIds()) {
|
|
40303
41083
|
const cellIds = this.getters.getCells(sheetId);
|
|
40304
41084
|
for (const cellId in cellIds) {
|
|
40305
|
-
positionIds.add(this.
|
|
41085
|
+
positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
|
|
40306
41086
|
}
|
|
40307
41087
|
}
|
|
40308
41088
|
return positionIds;
|
|
@@ -40347,7 +41127,7 @@ class Evaluator {
|
|
|
40347
41127
|
if (!this.blockedArrayFormulas.has(positionId)) {
|
|
40348
41128
|
this.invalidateSpreading(positionId);
|
|
40349
41129
|
}
|
|
40350
|
-
const cellPosition = this.
|
|
41130
|
+
const cellPosition = this.encoder.decode(positionId);
|
|
40351
41131
|
const cell = this.getters.getCell(cellPosition);
|
|
40352
41132
|
if (cell === undefined) {
|
|
40353
41133
|
return createEvaluatedCell("", { locale: this.getters.getLocale() });
|
|
@@ -40370,7 +41150,7 @@ class Evaluator {
|
|
|
40370
41150
|
}
|
|
40371
41151
|
}
|
|
40372
41152
|
computeAndSave(position) {
|
|
40373
|
-
const positionId = this.
|
|
41153
|
+
const positionId = this.encoder.encode(position);
|
|
40374
41154
|
const evaluatedCell = this.computeCell(positionId);
|
|
40375
41155
|
if (!this.evaluatedCells.has(positionId)) {
|
|
40376
41156
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
@@ -40425,15 +41205,15 @@ class Evaluator {
|
|
|
40425
41205
|
throw new Error(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
|
|
40426
41206
|
}
|
|
40427
41207
|
updateSpreadRelation({ sheetId, col, row, }) {
|
|
40428
|
-
const arrayFormulaPositionId = this.
|
|
41208
|
+
const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
40429
41209
|
return (i, j) => {
|
|
40430
41210
|
const position = { sheetId, col: i + col, row: j + row };
|
|
40431
|
-
const resultPositionId = this.
|
|
41211
|
+
const resultPositionId = this.encoder.encode(position);
|
|
40432
41212
|
this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
|
|
40433
41213
|
};
|
|
40434
41214
|
}
|
|
40435
41215
|
checkCollision({ sheetId, col, row }) {
|
|
40436
|
-
const formulaPositionId = this.
|
|
41216
|
+
const formulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
40437
41217
|
return (i, j) => {
|
|
40438
41218
|
const position = { sheetId: sheetId, col: i + col, row: j + row };
|
|
40439
41219
|
const rawCell = this.getters.getCell(position);
|
|
@@ -40454,7 +41234,7 @@ class Evaluator {
|
|
|
40454
41234
|
format: format || matrixResult[i][j]?.format,
|
|
40455
41235
|
locale: this.getters.getLocale(),
|
|
40456
41236
|
});
|
|
40457
|
-
const positionId = this.
|
|
41237
|
+
const positionId = this.encoder.encode(position);
|
|
40458
41238
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
40459
41239
|
// check if formula dependencies present in the spread zone
|
|
40460
41240
|
// if so, they need to be recomputed
|
|
@@ -40486,29 +41266,17 @@ class Evaluator {
|
|
|
40486
41266
|
if (!cell?.isFormula) {
|
|
40487
41267
|
return [];
|
|
40488
41268
|
}
|
|
40489
|
-
|
|
40490
|
-
for (const range of cell.compiledFormula.dependencies) {
|
|
40491
|
-
if (range.invalidSheetName || range.invalidXc) {
|
|
40492
|
-
continue;
|
|
40493
|
-
}
|
|
40494
|
-
const sheetId = range.sheetId;
|
|
40495
|
-
forEachPositionsInZone(range.zone, (col, row) => {
|
|
40496
|
-
dependencies.push(this.encodePosition({ sheetId, col, row }));
|
|
40497
|
-
});
|
|
40498
|
-
}
|
|
40499
|
-
return dependencies;
|
|
41269
|
+
return cell.compiledFormula.dependencies;
|
|
40500
41270
|
}
|
|
40501
41271
|
getCellsDependingOn(positionIds) {
|
|
40502
|
-
|
|
41272
|
+
const ranges = [];
|
|
41273
|
+
for (const positionId of positionIds) {
|
|
41274
|
+
ranges.push(this.encoder.decodeToBoundingBox(positionId));
|
|
41275
|
+
}
|
|
41276
|
+
return this.formulaDependencies().getCellsDependingOn(ranges);
|
|
40503
41277
|
}
|
|
40504
41278
|
getCell(positionId) {
|
|
40505
|
-
return this.getters.getCell(this.
|
|
40506
|
-
}
|
|
40507
|
-
encodePosition(position) {
|
|
40508
|
-
return this.positionEncoder.encode(position);
|
|
40509
|
-
}
|
|
40510
|
-
decodePosition(positionId) {
|
|
40511
|
-
return this.positionEncoder.decode(positionId);
|
|
41279
|
+
return this.getters.getCell(this.encoder.decode(positionId));
|
|
40512
41280
|
}
|
|
40513
41281
|
}
|
|
40514
41282
|
function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
|
|
@@ -40564,6 +41332,13 @@ class PositionBitsEncoder {
|
|
|
40564
41332
|
encode({ sheetId, col, row }) {
|
|
40565
41333
|
return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
|
|
40566
41334
|
}
|
|
41335
|
+
encodeBoundingBox({ sheetId, zone }) {
|
|
41336
|
+
const positions = [];
|
|
41337
|
+
forEachPositionsInZone(zone, (col, row) => {
|
|
41338
|
+
positions.push(this.encode({ sheetId, col, row }));
|
|
41339
|
+
});
|
|
41340
|
+
return positions;
|
|
41341
|
+
}
|
|
40567
41342
|
decode(id) {
|
|
40568
41343
|
// keep only the last 21 bits by AND-ing the bit sequence with 21 ones
|
|
40569
41344
|
const row = Number(id & 2097151n);
|
|
@@ -40571,6 +41346,10 @@ class PositionBitsEncoder {
|
|
|
40571
41346
|
const sheetId = this.decodeSheet(id >> 42n);
|
|
40572
41347
|
return { sheetId, col, row };
|
|
40573
41348
|
}
|
|
41349
|
+
decodeToBoundingBox(id) {
|
|
41350
|
+
const { sheetId, col, row } = this.decode(id);
|
|
41351
|
+
return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
|
|
41352
|
+
}
|
|
40574
41353
|
encodeSheet(sheetId) {
|
|
40575
41354
|
const sheetKey = this.sheetMapping[sheetId];
|
|
40576
41355
|
if (sheetKey === undefined) {
|
|
@@ -40753,7 +41532,12 @@ class EvaluationPlugin extends UIPlugin {
|
|
|
40753
41532
|
// Getters
|
|
40754
41533
|
// ---------------------------------------------------------------------------
|
|
40755
41534
|
evaluateFormula(sheetId, formulaString) {
|
|
40756
|
-
|
|
41535
|
+
try {
|
|
41536
|
+
return this.evaluator.evaluateFormula(sheetId, formulaString);
|
|
41537
|
+
}
|
|
41538
|
+
catch (error) {
|
|
41539
|
+
return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
|
|
41540
|
+
}
|
|
40757
41541
|
}
|
|
40758
41542
|
/**
|
|
40759
41543
|
* Return the value of each cell in the range as they are displayed in the grid.
|
|
@@ -41022,7 +41806,7 @@ class CustomColorsPlugin extends UIPlugin {
|
|
|
41022
41806
|
}
|
|
41023
41807
|
|
|
41024
41808
|
class EvaluationChartPlugin extends UIPlugin {
|
|
41025
|
-
static getters = ["getChartRuntime", "
|
|
41809
|
+
static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
|
|
41026
41810
|
charts = {};
|
|
41027
41811
|
createRuntimeChart = chartRuntimeFactory(this.getters);
|
|
41028
41812
|
handle(cmd) {
|
|
@@ -41060,25 +41844,26 @@ class EvaluationChartPlugin extends UIPlugin {
|
|
|
41060
41844
|
return this.charts[figureId];
|
|
41061
41845
|
}
|
|
41062
41846
|
/**
|
|
41063
|
-
* Get the background
|
|
41064
|
-
* of the chart. In order of priority, it will return :
|
|
41065
|
-
*
|
|
41066
|
-
* - the chart background color if one is defined
|
|
41067
|
-
* - the fill color of the cell if one is defined
|
|
41068
|
-
* - the fill color of the cell from conditional formats if one is defined
|
|
41069
|
-
* - the default chart color if no other color is defined
|
|
41847
|
+
* Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
|
|
41070
41848
|
*/
|
|
41071
|
-
|
|
41849
|
+
getStyleOfSingleCellChart(chartBackground, mainRange) {
|
|
41072
41850
|
if (chartBackground)
|
|
41073
|
-
return chartBackground;
|
|
41851
|
+
return { background: chartBackground, fontColor: chartFontColor(chartBackground) };
|
|
41074
41852
|
if (!mainRange) {
|
|
41075
|
-
return
|
|
41853
|
+
return {
|
|
41854
|
+
background: BACKGROUND_CHART_COLOR,
|
|
41855
|
+
fontColor: chartFontColor(BACKGROUND_CHART_COLOR),
|
|
41856
|
+
};
|
|
41076
41857
|
}
|
|
41077
41858
|
const col = mainRange.zone.left;
|
|
41078
41859
|
const row = mainRange.zone.top;
|
|
41079
41860
|
const sheetId = mainRange.sheetId;
|
|
41080
41861
|
const style = this.getters.getCellComputedStyle({ sheetId, col, row });
|
|
41081
|
-
|
|
41862
|
+
const background = style.fillColor || BACKGROUND_CHART_COLOR;
|
|
41863
|
+
return {
|
|
41864
|
+
background,
|
|
41865
|
+
fontColor: style.textColor || chartFontColor(background),
|
|
41866
|
+
};
|
|
41082
41867
|
}
|
|
41083
41868
|
exportForExcel(data) {
|
|
41084
41869
|
for (const sheet of data.sheets) {
|
|
@@ -41134,7 +41919,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
|
|
|
41134
41919
|
// ---------------------------------------------------------------------------
|
|
41135
41920
|
handle(cmd) {
|
|
41136
41921
|
if (invalidateCFEvaluationCommands.has(cmd.type) ||
|
|
41137
|
-
(cmd.type === "UPDATE_CELL" && "content" in cmd)) {
|
|
41922
|
+
(cmd.type === "UPDATE_CELL" && ("content" in cmd || "format" in cmd))) {
|
|
41138
41923
|
this.isStale = true;
|
|
41139
41924
|
}
|
|
41140
41925
|
}
|
|
@@ -41189,45 +41974,40 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
|
|
|
41189
41974
|
getComputedStyles(sheetId) {
|
|
41190
41975
|
const computedStyle = {};
|
|
41191
41976
|
for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
|
|
41192
|
-
|
|
41193
|
-
|
|
41194
|
-
|
|
41195
|
-
|
|
41196
|
-
|
|
41197
|
-
|
|
41198
|
-
|
|
41199
|
-
|
|
41200
|
-
|
|
41201
|
-
|
|
41202
|
-
|
|
41203
|
-
for (let
|
|
41204
|
-
|
|
41205
|
-
|
|
41206
|
-
|
|
41207
|
-
const
|
|
41208
|
-
|
|
41209
|
-
|
|
41210
|
-
|
|
41211
|
-
|
|
41212
|
-
|
|
41213
|
-
});
|
|
41214
|
-
}
|
|
41215
|
-
return value;
|
|
41216
|
-
});
|
|
41217
|
-
if (predicate && predicate(target, { ...cf.rule, values })) {
|
|
41218
|
-
if (!computedStyle[col])
|
|
41219
|
-
computedStyle[col] = [];
|
|
41220
|
-
// we must combine all the properties of all the CF rules applied to the given cell
|
|
41221
|
-
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
41977
|
+
switch (cf.rule.type) {
|
|
41978
|
+
case "ColorScaleRule":
|
|
41979
|
+
for (let range of cf.ranges) {
|
|
41980
|
+
this.applyColorScale(sheetId, range, cf.rule, computedStyle);
|
|
41981
|
+
}
|
|
41982
|
+
break;
|
|
41983
|
+
case "CellIsRule":
|
|
41984
|
+
const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
|
|
41985
|
+
for (let ref of cf.ranges) {
|
|
41986
|
+
const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
|
|
41987
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
41988
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
41989
|
+
const predicate = this.rulePredicate[cf.rule.type];
|
|
41990
|
+
const target = { sheetId, col, row };
|
|
41991
|
+
const values = cf.rule.values.map((value, i) => {
|
|
41992
|
+
const compiledFormula = formulas[i];
|
|
41993
|
+
if (compiledFormula) {
|
|
41994
|
+
return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, {
|
|
41995
|
+
...compiledFormula,
|
|
41996
|
+
dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
|
|
41997
|
+
});
|
|
41222
41998
|
}
|
|
41999
|
+
return value;
|
|
42000
|
+
});
|
|
42001
|
+
if (predicate && predicate(target, { ...cf.rule, values })) {
|
|
42002
|
+
if (!computedStyle[col])
|
|
42003
|
+
computedStyle[col] = [];
|
|
42004
|
+
// we must combine all the properties of all the CF rules applied to the given cell
|
|
42005
|
+
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
41223
42006
|
}
|
|
41224
42007
|
}
|
|
41225
42008
|
}
|
|
41226
|
-
|
|
41227
|
-
|
|
41228
|
-
}
|
|
41229
|
-
catch (_) {
|
|
41230
|
-
// we don't care about the errors within the evaluation of a rule
|
|
42009
|
+
}
|
|
42010
|
+
break;
|
|
41231
42011
|
}
|
|
41232
42012
|
}
|
|
41233
42013
|
return computedStyle;
|
|
@@ -41460,7 +42240,7 @@ class EvaluationDataValidationPlugin extends UIPlugin {
|
|
|
41460
42240
|
handle(cmd) {
|
|
41461
42241
|
if (invalidateEvaluationCommands.has(cmd.type) ||
|
|
41462
42242
|
cmd.type === "EVALUATE_CELLS" ||
|
|
41463
|
-
(cmd.type === "UPDATE_CELL" && "content" in cmd)) {
|
|
42243
|
+
(cmd.type === "UPDATE_CELL" && ("content" in cmd || "format" in cmd))) {
|
|
41464
42244
|
this.validationResults = {};
|
|
41465
42245
|
return;
|
|
41466
42246
|
}
|
|
@@ -41838,11 +42618,6 @@ class AutofillPlugin extends UIPlugin {
|
|
|
41838
42618
|
return "Success" /* CommandResult.Success */;
|
|
41839
42619
|
}
|
|
41840
42620
|
return "InvalidAutofillSelection" /* CommandResult.InvalidAutofillSelection */;
|
|
41841
|
-
case "AUTOFILL_AUTO":
|
|
41842
|
-
const zone = this.getters.getSelectedZone();
|
|
41843
|
-
return zone.top === zone.bottom
|
|
41844
|
-
? "Success" /* CommandResult.Success */
|
|
41845
|
-
: "CancelledForUnknownReason" /* CommandResult.CancelledForUnknownReason */;
|
|
41846
42621
|
}
|
|
41847
42622
|
return "Success" /* CommandResult.Success */;
|
|
41848
42623
|
}
|
|
@@ -41998,19 +42773,21 @@ class AutofillPlugin extends UIPlugin {
|
|
|
41998
42773
|
let col = zone.left;
|
|
41999
42774
|
let row = zone.bottom;
|
|
42000
42775
|
if (col > 0) {
|
|
42001
|
-
let
|
|
42002
|
-
while (
|
|
42776
|
+
let leftPosition = { sheetId, col: col - 1, row };
|
|
42777
|
+
while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
|
|
42778
|
+
this.getters.getCell(leftPosition)?.content) {
|
|
42003
42779
|
row += 1;
|
|
42004
|
-
|
|
42780
|
+
leftPosition = { sheetId, col: col - 1, row };
|
|
42005
42781
|
}
|
|
42006
42782
|
}
|
|
42007
42783
|
if (row === zone.bottom) {
|
|
42008
42784
|
col = zone.right;
|
|
42009
42785
|
if (col <= this.getters.getNumberCols(sheetId)) {
|
|
42010
|
-
let
|
|
42011
|
-
while (
|
|
42786
|
+
let rightPosition = { sheetId, col: col + 1, row };
|
|
42787
|
+
while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
|
|
42788
|
+
this.getters.getCell(rightPosition)?.content) {
|
|
42012
42789
|
row += 1;
|
|
42013
|
-
|
|
42790
|
+
rightPosition = { sheetId, col: col + 1, row };
|
|
42014
42791
|
}
|
|
42015
42792
|
}
|
|
42016
42793
|
}
|
|
@@ -43002,6 +43779,7 @@ class Session extends EventBus {
|
|
|
43002
43779
|
isReplayingInitialRevisions = false;
|
|
43003
43780
|
processedRevisions = new Set();
|
|
43004
43781
|
uuidGenerator = new UuidGenerator();
|
|
43782
|
+
lastLocalOperation;
|
|
43005
43783
|
/**
|
|
43006
43784
|
* Manages the collaboration between multiple users on the same spreadsheet.
|
|
43007
43785
|
* It can forward local state changes to other users to ensure they all eventually
|
|
@@ -43034,6 +43812,11 @@ class Session extends EventBus {
|
|
|
43034
43812
|
return;
|
|
43035
43813
|
const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, rootCommand, changes, Date.now());
|
|
43036
43814
|
this.revisions.append(revision.id, revision);
|
|
43815
|
+
// REQUEST_REDO just repeats the last operation, the
|
|
43816
|
+
// last operation is still the same and should not change.
|
|
43817
|
+
if (rootCommand.type !== "REQUEST_REDO") {
|
|
43818
|
+
this.lastLocalOperation = revision;
|
|
43819
|
+
}
|
|
43037
43820
|
this.trigger("new-local-state-update", { id: revision.id });
|
|
43038
43821
|
this.sendUpdateMessage({
|
|
43039
43822
|
type: "REMOTE_REVISION",
|
|
@@ -43130,19 +43913,10 @@ class Session extends EventBus {
|
|
|
43130
43913
|
return this.pendingMessages.length === 0;
|
|
43131
43914
|
}
|
|
43132
43915
|
/**
|
|
43133
|
-
* Get the last local revision
|
|
43916
|
+
* Get the last local revision
|
|
43134
43917
|
* */
|
|
43135
|
-
getLastLocalNonEmptyRevision(
|
|
43136
|
-
|
|
43137
|
-
for (const rev of revisions) {
|
|
43138
|
-
if (rev.rootCommand === "SNAPSHOT")
|
|
43139
|
-
return undefined;
|
|
43140
|
-
if (!rev.rootCommand || rev.rootCommand === "REMOTE")
|
|
43141
|
-
continue;
|
|
43142
|
-
if (!ignoredRootCommands.includes(rev.rootCommand?.type) && rev.commands.length)
|
|
43143
|
-
return rev;
|
|
43144
|
-
}
|
|
43145
|
-
return undefined;
|
|
43918
|
+
getLastLocalNonEmptyRevision() {
|
|
43919
|
+
return this.lastLocalOperation;
|
|
43146
43920
|
}
|
|
43147
43921
|
_move(position) {
|
|
43148
43922
|
// this method is debounced and might be called after the client
|
|
@@ -43202,7 +43976,7 @@ class Session extends EventBus {
|
|
|
43202
43976
|
break;
|
|
43203
43977
|
case "REMOTE_REVISION":
|
|
43204
43978
|
const { clientId, commands, timestamp } = message;
|
|
43205
|
-
const revision = new Revision(message.nextRevisionId, clientId, commands,
|
|
43979
|
+
const revision = new Revision(message.nextRevisionId, clientId, commands, undefined, undefined, timestamp);
|
|
43206
43980
|
if (revision.clientId !== this.clientId) {
|
|
43207
43981
|
this.revisions.insert(revision.id, revision, message.serverRevisionId);
|
|
43208
43982
|
const pendingCommands = this.pendingMessages
|
|
@@ -43215,10 +43989,11 @@ class Session extends EventBus {
|
|
|
43215
43989
|
}
|
|
43216
43990
|
break;
|
|
43217
43991
|
case "SNAPSHOT_CREATED": {
|
|
43218
|
-
const revision = new Revision(message.nextRevisionId, "server", [],
|
|
43992
|
+
const revision = new Revision(message.nextRevisionId, "server", [], undefined, undefined, Date.now());
|
|
43219
43993
|
this.revisions.insert(revision.id, revision, message.serverRevisionId);
|
|
43220
43994
|
this.dropPendingHistoryMessages();
|
|
43221
43995
|
this.trigger("snapshot");
|
|
43996
|
+
this.lastLocalOperation = undefined;
|
|
43222
43997
|
break;
|
|
43223
43998
|
}
|
|
43224
43999
|
}
|
|
@@ -43604,7 +44379,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
|
|
|
43604
44379
|
isPasteAllowed(target, clipboardOption) {
|
|
43605
44380
|
const sheetId = this.getters.getActiveSheetId();
|
|
43606
44381
|
if (this.operation === "CUT" && clipboardOption?.pasteOption !== undefined) {
|
|
43607
|
-
// cannot paste only format or
|
|
44382
|
+
// cannot paste only format or as value if the previous operation is a CUT
|
|
43608
44383
|
return "WrongPasteOption" /* CommandResult.WrongPasteOption */;
|
|
43609
44384
|
}
|
|
43610
44385
|
if (target.length > 1) {
|
|
@@ -43786,7 +44561,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
|
|
|
43786
44561
|
// This condition is used to determine if we have to paste the CF or not.
|
|
43787
44562
|
// We have to do it when the command handled is "PASTE", not "INSERT_CELL"
|
|
43788
44563
|
// or "DELETE_CELL". So, the state should be the local state
|
|
43789
|
-
const shouldPasteCF = clipboardOptions?.pasteOption !== "
|
|
44564
|
+
const shouldPasteCF = clipboardOptions?.pasteOption !== "asValue" && clipboardOptions?.shouldPasteCF;
|
|
43790
44565
|
const shouldPasteDV = !clipboardOptions?.pasteOption;
|
|
43791
44566
|
const sheetId = this.getters.getActiveSheetId();
|
|
43792
44567
|
// first, add missing cols/rows if needed
|
|
@@ -43822,10 +44597,11 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
|
|
|
43822
44597
|
pasteCell(origin, target, operation, clipboardOption) {
|
|
43823
44598
|
const { sheetId, col, row } = target;
|
|
43824
44599
|
const targetCell = this.getters.getEvaluatedCell(target);
|
|
43825
|
-
|
|
44600
|
+
const originFormat = origin.cell?.format ?? origin.evaluatedCell.format;
|
|
44601
|
+
if (clipboardOption?.pasteOption === "asValue") {
|
|
43826
44602
|
const locale = this.getters.getLocale();
|
|
43827
44603
|
const content = formatValue(origin.evaluatedCell.value, { locale });
|
|
43828
|
-
this.dispatch("UPDATE_CELL", { ...target, content });
|
|
44604
|
+
this.dispatch("UPDATE_CELL", { ...target, content, format: originFormat });
|
|
43829
44605
|
return;
|
|
43830
44606
|
}
|
|
43831
44607
|
const targetBorders = this.getters.getCellBorder(target);
|
|
@@ -43841,7 +44617,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
|
|
|
43841
44617
|
this.dispatch("UPDATE_CELL", {
|
|
43842
44618
|
...target,
|
|
43843
44619
|
style: origin.cell?.style ?? null,
|
|
43844
|
-
format:
|
|
44620
|
+
format: originFormat ?? targetCell.format,
|
|
43845
44621
|
});
|
|
43846
44622
|
return;
|
|
43847
44623
|
}
|
|
@@ -45863,6 +46639,9 @@ class SelectionInputsManagerPlugin extends UIPlugin {
|
|
|
45863
46639
|
// Other
|
|
45864
46640
|
// ---------------------------------------------------------------------------
|
|
45865
46641
|
initInput(id, initialRanges, inputHasSingleRange = false) {
|
|
46642
|
+
if (this.inputs[id]) {
|
|
46643
|
+
this.unfocus();
|
|
46644
|
+
}
|
|
45866
46645
|
this.inputs[id] = new SelectionInputPlugin(this.config, initialRanges, inputHasSingleRange);
|
|
45867
46646
|
if (initialRanges.length === 0) {
|
|
45868
46647
|
const input = this.inputs[id];
|
|
@@ -46571,7 +47350,7 @@ class HistoryPlugin extends UIPlugin {
|
|
|
46571
47350
|
* Ignore standard undo/redo revisions (that are empty)
|
|
46572
47351
|
*/
|
|
46573
47352
|
getPossibleRevisionToRepeat() {
|
|
46574
|
-
return this.session.getLastLocalNonEmptyRevision(
|
|
47353
|
+
return this.session.getLastLocalNonEmptyRevision();
|
|
46575
47354
|
}
|
|
46576
47355
|
}
|
|
46577
47356
|
|
|
@@ -48042,6 +48821,12 @@ class FilterEvaluationPlugin extends UIPlugin {
|
|
|
48042
48821
|
break;
|
|
48043
48822
|
case "HIDE_COLUMNS_ROWS":
|
|
48044
48823
|
case "UNHIDE_COLUMNS_ROWS":
|
|
48824
|
+
case "GROUP_HEADERS":
|
|
48825
|
+
case "UNGROUP_HEADERS":
|
|
48826
|
+
case "FOLD_HEADER_GROUP":
|
|
48827
|
+
case "UNFOLD_HEADER_GROUP":
|
|
48828
|
+
case "FOLD_ALL_HEADER_GROUPS":
|
|
48829
|
+
case "UNFOLD_ALL_HEADER_GROUPS":
|
|
48045
48830
|
this.updateHiddenRows();
|
|
48046
48831
|
break;
|
|
48047
48832
|
case "UPDATE_FILTER":
|
|
@@ -48743,7 +49528,7 @@ class GridSelectionPlugin extends UIPlugin {
|
|
|
48743
49528
|
sheetId: cmd.sheetId,
|
|
48744
49529
|
base: cmd.base,
|
|
48745
49530
|
quantity: thickness,
|
|
48746
|
-
position:
|
|
49531
|
+
position: cmd.position,
|
|
48747
49532
|
});
|
|
48748
49533
|
const isCol = cmd.dimension === "COL";
|
|
48749
49534
|
const start = cmd.elements[0];
|
|
@@ -48760,12 +49545,13 @@ class GridSelectionPlugin extends UIPlugin {
|
|
|
48760
49545
|
},
|
|
48761
49546
|
];
|
|
48762
49547
|
const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
|
|
49548
|
+
const base = isBasedBefore ? cmd.base : cmd.base + 1;
|
|
48763
49549
|
const pasteTarget = [
|
|
48764
49550
|
{
|
|
48765
|
-
left: isCol ?
|
|
48766
|
-
right: isCol ?
|
|
48767
|
-
top: !isCol ?
|
|
48768
|
-
bottom: !isCol ?
|
|
49551
|
+
left: isCol ? base : 0,
|
|
49552
|
+
right: isCol ? base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
|
|
49553
|
+
top: !isCol ? base : 0,
|
|
49554
|
+
bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
|
|
48769
49555
|
},
|
|
48770
49556
|
];
|
|
48771
49557
|
state.paste(pasteTarget, { selectTarget: true });
|
|
@@ -48800,6 +49586,11 @@ class GridSelectionPlugin extends UIPlugin {
|
|
|
48800
49586
|
doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) {
|
|
48801
49587
|
return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
|
|
48802
49588
|
}
|
|
49589
|
+
const headers = [cmd.base, ...cmd.elements];
|
|
49590
|
+
const maxHeaderValue = isCol ? this.getters.getNumberCols(id) : this.getters.getNumberRows(id);
|
|
49591
|
+
if (headers.some((h) => h < 0 || h >= maxHeaderValue)) {
|
|
49592
|
+
return "InvalidHeaderIndex" /* CommandResult.InvalidHeaderIndex */;
|
|
49593
|
+
}
|
|
48803
49594
|
return "Success" /* CommandResult.Success */;
|
|
48804
49595
|
}
|
|
48805
49596
|
//-------------------------------------------
|
|
@@ -48977,9 +49768,6 @@ class InternalViewport {
|
|
|
48977
49768
|
*/
|
|
48978
49769
|
adjustPosition(position) {
|
|
48979
49770
|
const sheetId = this.sheetId;
|
|
48980
|
-
if (!position) {
|
|
48981
|
-
position = this.getters.getSheetPosition(sheetId);
|
|
48982
|
-
}
|
|
48983
49771
|
const mainCellPosition = this.getters.getMainCellPosition({ sheetId, ...position });
|
|
48984
49772
|
const { col, row } = this.getters.getNextVisibleCellPosition(mainCellPosition);
|
|
48985
49773
|
if (isInside(col, this.boundaries.top, this.boundaries)) {
|
|
@@ -48992,56 +49780,50 @@ class InternalViewport {
|
|
|
48992
49780
|
adjustPositionX(targetCol) {
|
|
48993
49781
|
const sheetId = this.sheetId;
|
|
48994
49782
|
const { end } = this.getters.getColDimensions(sheetId, targetCol);
|
|
48995
|
-
const maxCol = this.getters.getNumberCols(sheetId);
|
|
48996
49783
|
if (this.offsetX + this.offsetCorrectionX + this.viewportWidth < end) {
|
|
49784
|
+
const maxCol = this.getters.getNumberCols(sheetId);
|
|
48997
49785
|
let finalTarget = targetCol;
|
|
48998
|
-
while (this.getters.isColHidden(sheetId, finalTarget) &&
|
|
49786
|
+
while (this.getters.isColHidden(sheetId, finalTarget) && finalTarget < maxCol) {
|
|
48999
49787
|
finalTarget++;
|
|
49000
49788
|
}
|
|
49001
49789
|
const finalTargetEnd = this.getters.getColDimensions(sheetId, finalTarget).end;
|
|
49002
49790
|
const startIndex = this.searchHeaderIndex("COL", finalTargetEnd - this.viewportWidth - this.offsetCorrectionX, this.boundaries.left);
|
|
49003
|
-
this.
|
|
49791
|
+
this.offsetScrollbarX =
|
|
49004
49792
|
this.getters.getColDimensions(sheetId, startIndex).end - this.offsetCorrectionX;
|
|
49005
|
-
this.offsetScrollbarX = this.offsetX;
|
|
49006
|
-
this.adjustViewportZoneX();
|
|
49007
49793
|
}
|
|
49008
49794
|
else if (this.left > targetCol) {
|
|
49009
49795
|
let finalTarget = targetCol;
|
|
49010
|
-
while (this.getters.isColHidden(sheetId, finalTarget) &&
|
|
49796
|
+
while (this.getters.isColHidden(sheetId, finalTarget) && finalTarget > 0) {
|
|
49011
49797
|
finalTarget--;
|
|
49012
49798
|
}
|
|
49013
|
-
this.
|
|
49799
|
+
this.offsetScrollbarX =
|
|
49014
49800
|
this.getters.getColDimensions(sheetId, finalTarget).start - this.offsetCorrectionX;
|
|
49015
|
-
this.offsetScrollbarX = this.offsetX;
|
|
49016
|
-
this.adjustViewportZoneX();
|
|
49017
49801
|
}
|
|
49802
|
+
this.adjustViewportZoneX();
|
|
49018
49803
|
}
|
|
49019
49804
|
adjustPositionY(targetRow) {
|
|
49020
49805
|
const sheetId = this.sheetId;
|
|
49021
49806
|
const { end } = this.getters.getRowDimensions(sheetId, targetRow);
|
|
49022
|
-
const maxRow = this.getters.getNumberRows(sheetId);
|
|
49023
49807
|
if (this.offsetY + this.viewportHeight + this.offsetCorrectionY < end) {
|
|
49808
|
+
const maxRow = this.getters.getNumberRows(sheetId);
|
|
49024
49809
|
let finalTarget = targetRow;
|
|
49025
|
-
while (this.getters.isRowHidden(sheetId, finalTarget) &&
|
|
49810
|
+
while (this.getters.isRowHidden(sheetId, finalTarget) && finalTarget < maxRow) {
|
|
49026
49811
|
finalTarget++;
|
|
49027
49812
|
}
|
|
49028
49813
|
const finalTargetEnd = this.getters.getRowDimensions(sheetId, finalTarget).end;
|
|
49029
49814
|
const startIndex = this.searchHeaderIndex("ROW", finalTargetEnd - this.viewportHeight - this.offsetCorrectionY, this.boundaries.top);
|
|
49030
|
-
this.
|
|
49815
|
+
this.offsetScrollbarY =
|
|
49031
49816
|
this.getters.getRowDimensions(sheetId, startIndex).end - this.offsetCorrectionY;
|
|
49032
|
-
this.offsetScrollbarY = this.offsetY;
|
|
49033
|
-
this.adjustViewportZoneY();
|
|
49034
49817
|
}
|
|
49035
49818
|
else if (this.top > targetRow) {
|
|
49036
49819
|
let finalTarget = targetRow;
|
|
49037
|
-
while (this.getters.isRowHidden(sheetId, finalTarget) &&
|
|
49820
|
+
while (this.getters.isRowHidden(sheetId, finalTarget) && finalTarget > 0) {
|
|
49038
49821
|
finalTarget--;
|
|
49039
49822
|
}
|
|
49040
|
-
this.
|
|
49823
|
+
this.offsetScrollbarY =
|
|
49041
49824
|
this.getters.getRowDimensions(sheetId, finalTarget).start - this.offsetCorrectionY;
|
|
49042
|
-
this.offsetScrollbarY = this.offsetY;
|
|
49043
|
-
this.adjustViewportZoneY();
|
|
49044
49825
|
}
|
|
49826
|
+
this.adjustViewportZoneY();
|
|
49045
49827
|
}
|
|
49046
49828
|
setViewportOffset(offsetX, offsetY) {
|
|
49047
49829
|
this.setViewportOffsetX(offsetX);
|
|
@@ -49057,11 +49839,10 @@ class InternalViewport {
|
|
|
49057
49839
|
* @returns Computes the absolute coordinate of a given zone inside the viewport
|
|
49058
49840
|
*/
|
|
49059
49841
|
getRect(zone) {
|
|
49060
|
-
const targetZone = intersection(zone, this
|
|
49842
|
+
const targetZone = intersection(zone, this);
|
|
49061
49843
|
if (targetZone) {
|
|
49062
|
-
const x = this.getters.getColRowOffset("COL", this.
|
|
49063
|
-
|
|
49064
|
-
const y = this.getters.getColRowOffset("ROW", this.zone.top, targetZone.top) + this.offsetCorrectionY;
|
|
49844
|
+
const x = this.getters.getColRowOffset("COL", this.left, targetZone.left) + this.offsetCorrectionX;
|
|
49845
|
+
const y = this.getters.getColRowOffset("ROW", this.top, targetZone.top) + this.offsetCorrectionY;
|
|
49065
49846
|
const width = Math.min(this.getters.getColRowOffset("COL", targetZone.left, targetZone.right + 1), this.viewportWidth);
|
|
49066
49847
|
const height = Math.min(this.getters.getColRowOffset("ROW", targetZone.top, targetZone.bottom + 1), this.viewportHeight);
|
|
49067
49848
|
return {
|
|
@@ -49071,9 +49852,7 @@ class InternalViewport {
|
|
|
49071
49852
|
height,
|
|
49072
49853
|
};
|
|
49073
49854
|
}
|
|
49074
|
-
|
|
49075
|
-
return undefined;
|
|
49076
|
-
}
|
|
49855
|
+
return undefined;
|
|
49077
49856
|
}
|
|
49078
49857
|
isVisible(col, row) {
|
|
49079
49858
|
const isInside = row <= this.bottom && row >= this.top && col >= this.left && col <= this.right;
|
|
@@ -49081,7 +49860,6 @@ class InternalViewport {
|
|
|
49081
49860
|
!this.getters.isColHidden(this.sheetId, col) &&
|
|
49082
49861
|
!this.getters.isRowHidden(this.sheetId, row));
|
|
49083
49862
|
}
|
|
49084
|
-
// PRIVATE
|
|
49085
49863
|
searchHeaderIndex(dimension, position, startIndex = 0) {
|
|
49086
49864
|
const sheetId = this.sheetId;
|
|
49087
49865
|
const headers = this.getters.getNumberHeaders(sheetId, dimension);
|
|
@@ -49104,9 +49882,6 @@ class InternalViewport {
|
|
|
49104
49882
|
}
|
|
49105
49883
|
return -1;
|
|
49106
49884
|
}
|
|
49107
|
-
get zone() {
|
|
49108
|
-
return { left: this.left, right: this.right, top: this.top, bottom: this.bottom };
|
|
49109
|
-
}
|
|
49110
49885
|
setViewportOffsetX(offsetX) {
|
|
49111
49886
|
if (!this.canScrollHorizontally) {
|
|
49112
49887
|
return;
|
|
@@ -49131,11 +49906,6 @@ class InternalViewport {
|
|
|
49131
49906
|
this.offsetScrollbarX = Math.max(0, viewportWidth - this.viewportWidth);
|
|
49132
49907
|
}
|
|
49133
49908
|
}
|
|
49134
|
-
this.left = this.getColIndex(this.offsetScrollbarX);
|
|
49135
|
-
this.right = this.getColIndex(this.offsetScrollbarX + this.viewportWidth);
|
|
49136
|
-
if (this.right === -1) {
|
|
49137
|
-
this.right = this.boundaries.right;
|
|
49138
|
-
}
|
|
49139
49909
|
this.adjustViewportZoneX();
|
|
49140
49910
|
}
|
|
49141
49911
|
/** Corrects the viewport's vertical offset based on the current structure
|
|
@@ -49148,11 +49918,6 @@ class InternalViewport {
|
|
|
49148
49918
|
this.offsetScrollbarY = Math.max(0, paneHeight - this.viewportHeight);
|
|
49149
49919
|
}
|
|
49150
49920
|
}
|
|
49151
|
-
this.top = this.getRowIndex(this.offsetScrollbarY);
|
|
49152
|
-
this.bottom = this.getRowIndex(this.offsetScrollbarY + this.viewportHeight);
|
|
49153
|
-
if (this.bottom === -1) {
|
|
49154
|
-
this.bottom = this.boundaries.bottom;
|
|
49155
|
-
}
|
|
49156
49921
|
this.adjustViewportZoneY();
|
|
49157
49922
|
}
|
|
49158
49923
|
/** Updates the pane zone and snapped offset based on its horizontal
|
|
@@ -49530,7 +50295,7 @@ class SheetViewPlugin extends UIPlugin {
|
|
|
49530
50295
|
isVisibleInViewport({ sheetId, col, row }) {
|
|
49531
50296
|
return this.getSubViewports(sheetId).some((pane) => pane.isVisible(col, row));
|
|
49532
50297
|
}
|
|
49533
|
-
// =>
|
|
50298
|
+
// => returns the new offset
|
|
49534
50299
|
getEdgeScrollCol(x, previousX, startingX) {
|
|
49535
50300
|
let canEdgeScroll = false;
|
|
49536
50301
|
let direction = 0;
|
|
@@ -49845,10 +50610,13 @@ class HeaderPositionsUIPlugin extends UIPlugin {
|
|
|
49845
50610
|
}
|
|
49846
50611
|
break;
|
|
49847
50612
|
case "UPDATE_CELL":
|
|
49848
|
-
if ("content" in cmd || "format" in cmd
|
|
50613
|
+
if ("content" in cmd || "format" in cmd) {
|
|
49849
50614
|
this.headerPositions = {};
|
|
49850
50615
|
this.isDirty = true;
|
|
49851
50616
|
}
|
|
50617
|
+
else {
|
|
50618
|
+
this.headerPositions[cmd.sheetId] = this.computeHeaderPositionsOfSheet(cmd.sheetId);
|
|
50619
|
+
}
|
|
49852
50620
|
break;
|
|
49853
50621
|
case "UPDATE_FILTER":
|
|
49854
50622
|
case "REMOVE_FILTER_TABLE":
|
|
@@ -52005,6 +52773,8 @@ css /* scss */ `
|
|
|
52005
52773
|
*:before,
|
|
52006
52774
|
*:after {
|
|
52007
52775
|
box-sizing: content-box;
|
|
52776
|
+
/** rtl not supported ATM */
|
|
52777
|
+
direction: ltr;
|
|
52008
52778
|
}
|
|
52009
52779
|
.o-separator {
|
|
52010
52780
|
border-bottom: ${MENU_SEPARATOR_BORDER_WIDTH}px solid ${SEPARATOR_COLOR};
|
|
@@ -52295,7 +53065,7 @@ class Spreadsheet extends owl.Component {
|
|
|
52295
53065
|
}
|
|
52296
53066
|
onKeydown(ev) {
|
|
52297
53067
|
let keyDownString = "";
|
|
52298
|
-
if (ev
|
|
53068
|
+
if (isCtrlKey(ev)) {
|
|
52299
53069
|
keyDownString += "CTRL+";
|
|
52300
53070
|
}
|
|
52301
53071
|
keyDownString += ev.key.toUpperCase();
|
|
@@ -55032,7 +55802,8 @@ function addRows(construct, data, sheet) {
|
|
|
55032
55802
|
}
|
|
55033
55803
|
else if (cell.content && cell.content !== "") {
|
|
55034
55804
|
const isTableHeader = isCellTableHeader(c, r, sheet);
|
|
55035
|
-
|
|
55805
|
+
const isPlainText = !!(cell.format && data.formats[cell.format] === PLAIN_TEXT_FORMAT);
|
|
55806
|
+
({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isPlainText));
|
|
55036
55807
|
}
|
|
55037
55808
|
attributes.push(...additionalAttrs);
|
|
55038
55809
|
cellNodes.push(escapeXml /*xml*/ `
|
|
@@ -55961,6 +56732,7 @@ const helpers = {
|
|
|
55961
56732
|
colorToRGBA,
|
|
55962
56733
|
positionToZone,
|
|
55963
56734
|
isDefined: isDefined$1,
|
|
56735
|
+
isMatrix,
|
|
55964
56736
|
lazy,
|
|
55965
56737
|
genericRepeat,
|
|
55966
56738
|
createAction,
|
|
@@ -56045,6 +56817,6 @@ exports.setTranslationMethod = setTranslationMethod;
|
|
|
56045
56817
|
exports.tokenize = tokenize;
|
|
56046
56818
|
|
|
56047
56819
|
|
|
56048
|
-
__info__.version = "17.1.0-alpha.
|
|
56049
|
-
__info__.date = "
|
|
56050
|
-
__info__.hash = "
|
|
56820
|
+
__info__.version = "17.1.0-alpha.6";
|
|
56821
|
+
__info__.date = "2024-01-04T12:22:45.921Z";
|
|
56822
|
+
__info__.hash = "56dbdc9";
|