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