@odoo/o-spreadsheet 19.1.2 → 19.2.0-alpha.2
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-engine.d.ts +10 -6
- package/dist/o-spreadsheet-engine.esm.js +130 -16
- package/dist/o-spreadsheet-engine.iife.js +130 -16
- package/dist/o-spreadsheet-engine.min.iife.js +312 -312
- package/dist/o-spreadsheet.d.ts +17 -10
- package/dist/o_spreadsheet.css +20 -10
- package/dist/o_spreadsheet.esm.js +678 -589
- package/dist/o_spreadsheet.iife.js +678 -589
- package/dist/o_spreadsheet.min.iife.js +9 -9
- package/dist/o_spreadsheet.xml +97 -103
- package/package.json +2 -2
|
@@ -143,12 +143,11 @@ type BarChartRuntime = {
|
|
|
143
143
|
interface GenericCriterion {
|
|
144
144
|
type: GenericCriterionType;
|
|
145
145
|
values: string[];
|
|
146
|
+
isPercent?: boolean;
|
|
147
|
+
isBottom?: boolean;
|
|
146
148
|
}
|
|
147
|
-
type GenericCriterionType = "containsText" | "notContainsText" | "isEqualText" | "isEmail" | "isLink" | "dateIs" | "dateIsBefore" | "dateIsOnOrBefore" | "dateIsAfter" | "dateIsOnOrAfter" | "dateIsBetween" | "dateIsNotBetween" | "dateIsValid" | "isEqual" | "isNotEqual" | "isGreaterThan" | "isGreaterOrEqualTo" | "isLessThan" | "isLessOrEqualTo" | "isBetween" | "isNotBetween" | "isBoolean" | "isValueInList" | "isValueInRange" | "customFormula" | "beginsWithText" | "endsWithText" | "isNotEmpty" | "isEmpty";
|
|
149
|
+
type GenericCriterionType = "containsText" | "notContainsText" | "isEqualText" | "isEmail" | "isLink" | "dateIs" | "dateIsBefore" | "dateIsOnOrBefore" | "dateIsAfter" | "dateIsOnOrAfter" | "dateIsBetween" | "dateIsNotBetween" | "dateIsValid" | "isEqual" | "isNotEqual" | "isGreaterThan" | "isGreaterOrEqualTo" | "isLessThan" | "isLessOrEqualTo" | "isBetween" | "isNotBetween" | "isBoolean" | "isValueInList" | "isValueInRange" | "customFormula" | "beginsWithText" | "endsWithText" | "isNotEmpty" | "isEmpty" | "top10";
|
|
148
150
|
type DateCriterionValue = "today" | "tomorrow" | "yesterday" | "lastWeek" | "lastMonth" | "lastYear" | "exactDate";
|
|
149
|
-
type EvaluatedCriterion<T extends GenericCriterion = GenericCriterion> = Omit<T, "values"> & {
|
|
150
|
-
values: CellValue[];
|
|
151
|
-
};
|
|
152
151
|
|
|
153
152
|
interface RangePart {
|
|
154
153
|
readonly colFixed: boolean;
|
|
@@ -646,6 +645,8 @@ interface CellIsRule extends SingleColorRule {
|
|
|
646
645
|
operator: ConditionalFormattingOperatorValues;
|
|
647
646
|
values: string[];
|
|
648
647
|
dateValue?: DateCriterionValue;
|
|
648
|
+
isPercent?: boolean;
|
|
649
|
+
isBottom?: boolean;
|
|
649
650
|
}
|
|
650
651
|
type ThresholdType = "value" | "number" | "percentage" | "percentile" | "formula";
|
|
651
652
|
type ColorScaleThreshold = {
|
|
@@ -688,7 +689,7 @@ interface IconSetRule {
|
|
|
688
689
|
upperInflectionPoint: IconThreshold;
|
|
689
690
|
lowerInflectionPoint: IconThreshold;
|
|
690
691
|
}
|
|
691
|
-
declare const cfOperators: readonly ["containsText", "notContainsText", "isGreaterThan", "isGreaterOrEqualTo", "isLessThan", "isLessOrEqualTo", "isBetween", "isNotBetween", "beginsWithText", "endsWithText", "isNotEmpty", "isEmpty", "isNotEqual", "isEqual", "customFormula", "dateIs", "dateIsBefore", "dateIsAfter", "dateIsOnOrBefore", "dateIsOnOrAfter"];
|
|
692
|
+
declare const cfOperators: readonly ["containsText", "notContainsText", "isGreaterThan", "isGreaterOrEqualTo", "isLessThan", "isLessOrEqualTo", "isBetween", "isNotBetween", "beginsWithText", "endsWithText", "isNotEmpty", "isEmpty", "isNotEqual", "isEqual", "customFormula", "dateIs", "dateIsBefore", "dateIsAfter", "dateIsOnOrBefore", "dateIsOnOrAfter", "top10"];
|
|
692
693
|
type ConditionalFormattingOperatorValues = (typeof cfOperators)[number];
|
|
693
694
|
|
|
694
695
|
interface ConditionalFormatState {
|
|
@@ -2756,6 +2757,9 @@ type SheetValidationResult = {
|
|
|
2756
2757
|
declare class EvaluationDataValidationPlugin extends CoreViewPlugin {
|
|
2757
2758
|
static getters: readonly ["getDataValidationInvalidCriterionValueMessage", "getInvalidDataValidationMessage", "getValidationResultForCellValue", "getDataValidationRangeValues", "isCellValidCheckbox", "getDataValidationCellStyle", "getDataValidationChipStyle", "isDataValidationInvalid"];
|
|
2758
2759
|
validationResults: Record<UID, SheetValidationResult>;
|
|
2760
|
+
criterionPreComputeResult: Record<UID, {
|
|
2761
|
+
[dvRuleId: UID]: unknown;
|
|
2762
|
+
}>;
|
|
2759
2763
|
handle(cmd: CoreViewCommand): void;
|
|
2760
2764
|
isDataValidationInvalid(cellPosition: CellPosition): boolean;
|
|
2761
2765
|
getDataValidationCellStyle(position: CellPosition): Style | undefined;
|
|
@@ -2767,7 +2771,7 @@ declare class EvaluationDataValidationPlugin extends CoreViewPlugin {
|
|
|
2767
2771
|
* The value must be canonicalized.
|
|
2768
2772
|
*/
|
|
2769
2773
|
getDataValidationInvalidCriterionValueMessage(criterionType: DataValidationCriterionType, value: string): string | undefined;
|
|
2770
|
-
getDataValidationRangeValues(sheetId: UID, criterion:
|
|
2774
|
+
getDataValidationRangeValues(sheetId: UID, criterion: GenericCriterion): {
|
|
2771
2775
|
value: string;
|
|
2772
2776
|
label: string;
|
|
2773
2777
|
}[];
|
|
@@ -3,8 +3,8 @@
|
|
|
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
5
|
* @version 19.1.0-alpha.3
|
|
6
|
-
* @date 2026-01-07T16:20:
|
|
7
|
-
* @hash
|
|
6
|
+
* @date 2026-01-07T16:20:57.293Z
|
|
7
|
+
* @hash ac2fa3e
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
class FunctionCodeBuilder {
|
|
@@ -14251,6 +14251,7 @@ const DVTerms = {
|
|
|
14251
14251
|
dateValue: _t("The value must be a date"),
|
|
14252
14252
|
validRange: _t("The value must be a valid range"),
|
|
14253
14253
|
validFormula: _t("The formula must be valid"),
|
|
14254
|
+
positiveNumber: _t("The value must be a positive number"),
|
|
14254
14255
|
},
|
|
14255
14256
|
Errors: {
|
|
14256
14257
|
["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid."),
|
|
@@ -22402,6 +22403,7 @@ const CF_OPERATOR_TYPE_CONVERSION_MAP = {
|
|
|
22402
22403
|
greaterThanOrEqual: "isGreaterOrEqualTo",
|
|
22403
22404
|
lessThan: "isLessThan",
|
|
22404
22405
|
lessThanOrEqual: "isLessOrEqualTo",
|
|
22406
|
+
top10: "top10",
|
|
22405
22407
|
};
|
|
22406
22408
|
/** Conversion map CF types in XLSX <=> Cf types in o_spreadsheet */
|
|
22407
22409
|
const CF_TYPE_CONVERSION_MAP = {
|
|
@@ -22942,6 +22944,7 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
|
|
|
22942
22944
|
const rule = cf.cfRules[0];
|
|
22943
22945
|
let operator;
|
|
22944
22946
|
const values = [];
|
|
22947
|
+
const cfAdditionalProperties = {};
|
|
22945
22948
|
if (rule.dxfId === undefined &&
|
|
22946
22949
|
!(rule.type === "colorScale" || rule.type === "iconSet" || rule.type === "dataBar"))
|
|
22947
22950
|
continue;
|
|
@@ -22950,7 +22953,6 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
|
|
|
22950
22953
|
case "containsErrors":
|
|
22951
22954
|
case "notContainsErrors":
|
|
22952
22955
|
case "duplicateValues":
|
|
22953
|
-
case "top10":
|
|
22954
22956
|
case "uniqueValues":
|
|
22955
22957
|
case "timePeriod":
|
|
22956
22958
|
// Not supported
|
|
@@ -23001,6 +23003,18 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
|
|
|
23001
23003
|
values.push(prefixFormulaWithEqual(rule.formula[1]));
|
|
23002
23004
|
}
|
|
23003
23005
|
break;
|
|
23006
|
+
case "top10":
|
|
23007
|
+
if (rule.rank === undefined)
|
|
23008
|
+
continue;
|
|
23009
|
+
operator = CF_OPERATOR_TYPE_CONVERSION_MAP[rule.type];
|
|
23010
|
+
values.push(rule.rank.toString());
|
|
23011
|
+
if (rule.percent) {
|
|
23012
|
+
cfAdditionalProperties.isPercent = true;
|
|
23013
|
+
}
|
|
23014
|
+
if (rule.bottom) {
|
|
23015
|
+
cfAdditionalProperties.isBottom = true;
|
|
23016
|
+
}
|
|
23017
|
+
break;
|
|
23004
23018
|
}
|
|
23005
23019
|
if (operator && rule.dxfId !== undefined) {
|
|
23006
23020
|
cfs.push({
|
|
@@ -23011,6 +23025,7 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
|
|
|
23011
23025
|
type: "CellIsRule",
|
|
23012
23026
|
operator: operator,
|
|
23013
23027
|
values: values,
|
|
23028
|
+
...cfAdditionalProperties,
|
|
23014
23029
|
style: convertStyle({ fontStyle: dxfs[rule.dxfId].font, fillStyle: dxfs[rule.dxfId].fill }, warningManager),
|
|
23015
23030
|
},
|
|
23016
23031
|
});
|
|
@@ -23304,6 +23319,8 @@ function convertOperator(operator) {
|
|
|
23304
23319
|
return "greaterThanOrEqual";
|
|
23305
23320
|
case "dateIsOnOrBefore":
|
|
23306
23321
|
return "lessThanOrEqual";
|
|
23322
|
+
case "top10":
|
|
23323
|
+
return "top10";
|
|
23307
23324
|
}
|
|
23308
23325
|
}
|
|
23309
23326
|
// -------------------------------------
|
|
@@ -29364,15 +29381,20 @@ criterionEvaluatorRegistry.add("isValueInList", {
|
|
|
29364
29381
|
getPreview: (criterion) => _t("Value one of: %s", criterion.values.join(", ")),
|
|
29365
29382
|
});
|
|
29366
29383
|
criterionEvaluatorRegistry.add("isValueInRange", {
|
|
29367
|
-
type: "
|
|
29368
|
-
|
|
29384
|
+
type: "isValueInRange",
|
|
29385
|
+
preComputeCriterion: (criterion, criterionRanges, getters) => {
|
|
29386
|
+
if (criterionRanges.length === 0) {
|
|
29387
|
+
return new Set();
|
|
29388
|
+
}
|
|
29389
|
+
const sheetId = criterionRanges[0].sheetId;
|
|
29390
|
+
const criterionValues = getters.getDataValidationRangeValues(sheetId, criterion);
|
|
29391
|
+
return new Set(criterionValues.map((value) => value.value.toString().toLowerCase()));
|
|
29392
|
+
},
|
|
29393
|
+
isValueValid: (value, criterion, valuesSet) => {
|
|
29369
29394
|
if (!value) {
|
|
29370
29395
|
return false;
|
|
29371
29396
|
}
|
|
29372
|
-
|
|
29373
|
-
return criterionValues
|
|
29374
|
-
.map((value) => value.value.toLowerCase())
|
|
29375
|
-
.includes(value.toString().toLowerCase());
|
|
29397
|
+
return valuesSet.has(value.toString().toLowerCase());
|
|
29376
29398
|
},
|
|
29377
29399
|
getErrorString: (criterion) => _t("The value must be a value in the range %s", String(criterion.values[0])),
|
|
29378
29400
|
isCriterionValueValid: (value) => rangeReference.test(value),
|
|
@@ -29449,6 +29471,67 @@ criterionEvaluatorRegistry.add("isNotEmpty", {
|
|
|
29449
29471
|
name: _t("Is not empty"),
|
|
29450
29472
|
getPreview: () => _t("Is not empty"),
|
|
29451
29473
|
});
|
|
29474
|
+
criterionEvaluatorRegistry.add("top10", {
|
|
29475
|
+
type: "top10",
|
|
29476
|
+
preComputeCriterion: (criterion, criterionRanges, getters) => {
|
|
29477
|
+
let value = tryToNumber(criterion.values[0], DEFAULT_LOCALE);
|
|
29478
|
+
if (value === undefined || value <= 0) {
|
|
29479
|
+
return undefined;
|
|
29480
|
+
}
|
|
29481
|
+
const numberValues = [];
|
|
29482
|
+
for (const range of criterionRanges) {
|
|
29483
|
+
for (const value of getters.getRangeValues(range)) {
|
|
29484
|
+
if (typeof value === "number") {
|
|
29485
|
+
numberValues.push(value);
|
|
29486
|
+
}
|
|
29487
|
+
}
|
|
29488
|
+
}
|
|
29489
|
+
const sortedValues = numberValues.sort((a, b) => a - b);
|
|
29490
|
+
if (criterion.isPercent) {
|
|
29491
|
+
value = clip(value, 1, 100);
|
|
29492
|
+
}
|
|
29493
|
+
let index = 0;
|
|
29494
|
+
if (criterion.isBottom && !criterion.isPercent) {
|
|
29495
|
+
index = value - 1;
|
|
29496
|
+
}
|
|
29497
|
+
else if (criterion.isBottom && criterion.isPercent) {
|
|
29498
|
+
index = Math.floor((sortedValues.length * value) / 100) - 1;
|
|
29499
|
+
}
|
|
29500
|
+
else if (!criterion.isBottom && criterion.isPercent) {
|
|
29501
|
+
index = sortedValues.length - Math.floor((sortedValues.length * value) / 100);
|
|
29502
|
+
}
|
|
29503
|
+
else {
|
|
29504
|
+
index = sortedValues.length - value;
|
|
29505
|
+
}
|
|
29506
|
+
index = clip(index, 0, sortedValues.length - 1);
|
|
29507
|
+
return sortedValues[index];
|
|
29508
|
+
},
|
|
29509
|
+
isValueValid: (value, criterion, threshold) => {
|
|
29510
|
+
if (typeof value !== "number" || threshold === undefined) {
|
|
29511
|
+
return false;
|
|
29512
|
+
}
|
|
29513
|
+
return criterion.isBottom ? value <= threshold : value >= threshold;
|
|
29514
|
+
},
|
|
29515
|
+
getErrorString: (criterion) => {
|
|
29516
|
+
const args = {
|
|
29517
|
+
value: String(criterion.values[0]),
|
|
29518
|
+
percentSymbol: criterion.isPercent ? "%" : "",
|
|
29519
|
+
};
|
|
29520
|
+
return criterion.isBottom
|
|
29521
|
+
? _t("The value must be in bottom %(value)s%(percentSymbol)s", args)
|
|
29522
|
+
: _t("The value must be in top %(value)s%(percentSymbol)s", args);
|
|
29523
|
+
},
|
|
29524
|
+
isCriterionValueValid: (value) => checkValueIsPositiveNumber(value),
|
|
29525
|
+
criterionValueErrorString: DVTerms.CriterionError.positiveNumber,
|
|
29526
|
+
numberOfValues: () => 1,
|
|
29527
|
+
name: _t("Is in Top/Bottom ranking"),
|
|
29528
|
+
getPreview: (criterion) => {
|
|
29529
|
+
const args = { value: criterion.values[0], percentSymbol: criterion.isPercent ? "%" : "" };
|
|
29530
|
+
return criterion.isBottom
|
|
29531
|
+
? _t("Value is in bottom %(value)s%(percentSymbol)s", args)
|
|
29532
|
+
: _t("Value is in top %(value)s%(percentSymbol)s", args);
|
|
29533
|
+
},
|
|
29534
|
+
});
|
|
29452
29535
|
function getNumberCriterionlocalizedValues(criterion, locale) {
|
|
29453
29536
|
return criterion.values.map((value) => {
|
|
29454
29537
|
return value !== undefined
|
|
@@ -29470,6 +29553,10 @@ function checkValueIsNumber(value) {
|
|
|
29470
29553
|
const valueAsNumber = tryToNumber(value, DEFAULT_LOCALE);
|
|
29471
29554
|
return valueAsNumber !== undefined;
|
|
29472
29555
|
}
|
|
29556
|
+
function checkValueIsPositiveNumber(value) {
|
|
29557
|
+
const valueAsNumber = tryToNumber(value, DEFAULT_LOCALE);
|
|
29558
|
+
return valueAsNumber !== undefined && valueAsNumber > 0;
|
|
29559
|
+
}
|
|
29473
29560
|
|
|
29474
29561
|
// -----------------------------------------------------------------------------
|
|
29475
29562
|
// Constants
|
|
@@ -39800,6 +39887,10 @@ class EvaluationConditionalFormatPlugin extends CoreViewPlugin {
|
|
|
39800
39887
|
break;
|
|
39801
39888
|
case "CellIsRule":
|
|
39802
39889
|
const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
|
|
39890
|
+
const evaluator = criterionEvaluatorRegistry.get(cf.rule.operator);
|
|
39891
|
+
const criterion = { ...cf.rule, type: cf.rule.operator };
|
|
39892
|
+
const ranges = cf.ranges.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));
|
|
39893
|
+
const preComputedCriterion = evaluator.preComputeCriterion?.(criterion, ranges, this.getters);
|
|
39803
39894
|
for (const ref of cf.ranges) {
|
|
39804
39895
|
const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
|
|
39805
39896
|
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
@@ -39812,7 +39903,7 @@ class EvaluationConditionalFormatPlugin extends CoreViewPlugin {
|
|
|
39812
39903
|
}
|
|
39813
39904
|
return value;
|
|
39814
39905
|
});
|
|
39815
|
-
if (this.getRuleResultForTarget(target, { ...cf.rule, values })) {
|
|
39906
|
+
if (this.getRuleResultForTarget(target, { ...cf.rule, values }, preComputedCriterion)) {
|
|
39816
39907
|
if (!computedStyle[col])
|
|
39817
39908
|
computedStyle[col] = [];
|
|
39818
39909
|
// we must combine all the properties of all the CF rules applied to the given cell
|
|
@@ -39975,7 +40066,7 @@ class EvaluationConditionalFormatPlugin extends CoreViewPlugin {
|
|
|
39975
40066
|
}
|
|
39976
40067
|
}
|
|
39977
40068
|
}
|
|
39978
|
-
getRuleResultForTarget(target, rule) {
|
|
40069
|
+
getRuleResultForTarget(target, rule, preComputedCriterion) {
|
|
39979
40070
|
const cell = this.getters.getEvaluatedCell(target);
|
|
39980
40071
|
if (cell.type === CellValueType.error) {
|
|
39981
40072
|
return false;
|
|
@@ -39992,11 +40083,12 @@ class EvaluationConditionalFormatPlugin extends CoreViewPlugin {
|
|
|
39992
40083
|
return false;
|
|
39993
40084
|
}
|
|
39994
40085
|
const evaluatedCriterion = {
|
|
40086
|
+
...rule,
|
|
39995
40087
|
type: rule.operator,
|
|
39996
40088
|
values: evaluatedCriterionValues.map(toScalar),
|
|
39997
40089
|
dateValue: rule.dateValue || "exactDate",
|
|
39998
40090
|
};
|
|
39999
|
-
return evaluator.isValueValid(cell.value ?? "", evaluatedCriterion,
|
|
40091
|
+
return evaluator.isValueValid(cell.value ?? "", evaluatedCriterion, preComputedCriterion);
|
|
40000
40092
|
}
|
|
40001
40093
|
}
|
|
40002
40094
|
|
|
@@ -40013,17 +40105,20 @@ class EvaluationDataValidationPlugin extends CoreViewPlugin {
|
|
|
40013
40105
|
"isDataValidationInvalid",
|
|
40014
40106
|
];
|
|
40015
40107
|
validationResults = {};
|
|
40108
|
+
criterionPreComputeResult = {};
|
|
40016
40109
|
handle(cmd) {
|
|
40017
40110
|
if (invalidateEvaluationCommands.has(cmd.type) ||
|
|
40018
40111
|
cmd.type === "EVALUATE_CELLS" ||
|
|
40019
40112
|
(cmd.type === "UPDATE_CELL" && ("content" in cmd || "format" in cmd))) {
|
|
40020
40113
|
this.validationResults = {};
|
|
40114
|
+
this.criterionPreComputeResult = {};
|
|
40021
40115
|
return;
|
|
40022
40116
|
}
|
|
40023
40117
|
switch (cmd.type) {
|
|
40024
40118
|
case "ADD_DATA_VALIDATION_RULE":
|
|
40025
40119
|
case "REMOVE_DATA_VALIDATION_RULE":
|
|
40026
40120
|
delete this.validationResults[cmd.sheetId];
|
|
40121
|
+
delete this.criterionPreComputeResult[cmd.sheetId];
|
|
40027
40122
|
break;
|
|
40028
40123
|
}
|
|
40029
40124
|
}
|
|
@@ -40166,7 +40261,15 @@ class EvaluationDataValidationPlugin extends CoreViewPlugin {
|
|
|
40166
40261
|
return undefined;
|
|
40167
40262
|
}
|
|
40168
40263
|
const evaluatedCriterion = { ...criterion, values: evaluatedCriterionValues.map(toScalar) };
|
|
40169
|
-
if (
|
|
40264
|
+
if (!this.criterionPreComputeResult[sheetId]) {
|
|
40265
|
+
this.criterionPreComputeResult[sheetId] = {};
|
|
40266
|
+
}
|
|
40267
|
+
let preComputedCriterion = this.criterionPreComputeResult[sheetId][rule.id];
|
|
40268
|
+
if (preComputedCriterion === undefined) {
|
|
40269
|
+
preComputedCriterion = evaluator.preComputeCriterion?.(rule.criterion, rule.ranges, this.getters);
|
|
40270
|
+
this.criterionPreComputeResult[sheetId][rule.id] = preComputedCriterion;
|
|
40271
|
+
}
|
|
40272
|
+
if (evaluator.isValueValid(cellValue, evaluatedCriterion, preComputedCriterion)) {
|
|
40170
40273
|
return undefined;
|
|
40171
40274
|
}
|
|
40172
40275
|
return evaluator.getErrorString(evaluatedCriterion, this.getters, sheetId);
|
|
@@ -45498,6 +45601,7 @@ class FilterEvaluationPlugin extends UIPlugin {
|
|
|
45498
45601
|
if (filterValue.type === "none")
|
|
45499
45602
|
continue;
|
|
45500
45603
|
const evaluator = criterionEvaluatorRegistry.get(filterValue.type);
|
|
45604
|
+
const preComputedCriterion = evaluator.preComputeCriterion?.(filterValue, [filter.filteredRange], this.getters);
|
|
45501
45605
|
const evaluatedCriterionValues = filterValue.values.map((value) => {
|
|
45502
45606
|
if (!value.startsWith("=")) {
|
|
45503
45607
|
return parseLiteral(value, DEFAULT_LOCALE);
|
|
@@ -45515,7 +45619,7 @@ class FilterEvaluationPlugin extends UIPlugin {
|
|
|
45515
45619
|
for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
|
|
45516
45620
|
const position = { sheetId, col: filter.col, row };
|
|
45517
45621
|
const value = this.getters.getEvaluatedCell(position).value ?? "";
|
|
45518
|
-
if (!evaluator.isValueValid(value, evaluatedCriterion,
|
|
45622
|
+
if (!evaluator.isValueValid(value, evaluatedCriterion, preComputedCriterion)) {
|
|
45519
45623
|
hiddenRows.add(row);
|
|
45520
45624
|
}
|
|
45521
45625
|
}
|
|
@@ -49656,6 +49760,8 @@ function cellRuleFormula(ranges, rule) {
|
|
|
49656
49760
|
case undefined:
|
|
49657
49761
|
throw new Error("dateValue should be defined");
|
|
49658
49762
|
}
|
|
49763
|
+
case "top10":
|
|
49764
|
+
return [];
|
|
49659
49765
|
}
|
|
49660
49766
|
}
|
|
49661
49767
|
function cellRuleTypeAttributes(rule) {
|
|
@@ -49688,6 +49794,14 @@ function cellRuleTypeAttributes(rule) {
|
|
|
49688
49794
|
case "dateIs":
|
|
49689
49795
|
case "customFormula":
|
|
49690
49796
|
return [["type", "expression"]];
|
|
49797
|
+
case "top10": {
|
|
49798
|
+
return [
|
|
49799
|
+
["type", "top10"],
|
|
49800
|
+
["rank", rule.values[0]],
|
|
49801
|
+
["percent", rule.isPercent ? "1" : "0"],
|
|
49802
|
+
["bottom", rule.isBottom ? "1" : "0"],
|
|
49803
|
+
];
|
|
49804
|
+
}
|
|
49691
49805
|
}
|
|
49692
49806
|
}
|
|
49693
49807
|
function addDataBarRule(cf, rule) {
|
|
@@ -51560,5 +51674,5 @@ export { BadExpressionError, BasePlugin, CellErrorType, CircularDependencyError,
|
|
|
51560
51674
|
|
|
51561
51675
|
|
|
51562
51676
|
__info__.version = "19.1.0-alpha.3";
|
|
51563
|
-
__info__.date = "2026-01-07T16:20:
|
|
51564
|
-
__info__.hash = "
|
|
51677
|
+
__info__.date = "2026-01-07T16:20:57.293Z";
|
|
51678
|
+
__info__.hash = "ac2fa3e";
|
|
@@ -3,8 +3,8 @@
|
|
|
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
5
|
* @version 19.1.0-alpha.3
|
|
6
|
-
* @date 2026-01-07T16:20:
|
|
7
|
-
* @hash
|
|
6
|
+
* @date 2026-01-07T16:20:57.293Z
|
|
7
|
+
* @hash ac2fa3e
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
(function (exports) {
|
|
@@ -14254,6 +14254,7 @@
|
|
|
14254
14254
|
dateValue: _t("The value must be a date"),
|
|
14255
14255
|
validRange: _t("The value must be a valid range"),
|
|
14256
14256
|
validFormula: _t("The formula must be valid"),
|
|
14257
|
+
positiveNumber: _t("The value must be a positive number"),
|
|
14257
14258
|
},
|
|
14258
14259
|
Errors: {
|
|
14259
14260
|
["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid."),
|
|
@@ -22405,6 +22406,7 @@
|
|
|
22405
22406
|
greaterThanOrEqual: "isGreaterOrEqualTo",
|
|
22406
22407
|
lessThan: "isLessThan",
|
|
22407
22408
|
lessThanOrEqual: "isLessOrEqualTo",
|
|
22409
|
+
top10: "top10",
|
|
22408
22410
|
};
|
|
22409
22411
|
/** Conversion map CF types in XLSX <=> Cf types in o_spreadsheet */
|
|
22410
22412
|
const CF_TYPE_CONVERSION_MAP = {
|
|
@@ -22945,6 +22947,7 @@
|
|
|
22945
22947
|
const rule = cf.cfRules[0];
|
|
22946
22948
|
let operator;
|
|
22947
22949
|
const values = [];
|
|
22950
|
+
const cfAdditionalProperties = {};
|
|
22948
22951
|
if (rule.dxfId === undefined &&
|
|
22949
22952
|
!(rule.type === "colorScale" || rule.type === "iconSet" || rule.type === "dataBar"))
|
|
22950
22953
|
continue;
|
|
@@ -22953,7 +22956,6 @@
|
|
|
22953
22956
|
case "containsErrors":
|
|
22954
22957
|
case "notContainsErrors":
|
|
22955
22958
|
case "duplicateValues":
|
|
22956
|
-
case "top10":
|
|
22957
22959
|
case "uniqueValues":
|
|
22958
22960
|
case "timePeriod":
|
|
22959
22961
|
// Not supported
|
|
@@ -23004,6 +23006,18 @@
|
|
|
23004
23006
|
values.push(prefixFormulaWithEqual(rule.formula[1]));
|
|
23005
23007
|
}
|
|
23006
23008
|
break;
|
|
23009
|
+
case "top10":
|
|
23010
|
+
if (rule.rank === undefined)
|
|
23011
|
+
continue;
|
|
23012
|
+
operator = CF_OPERATOR_TYPE_CONVERSION_MAP[rule.type];
|
|
23013
|
+
values.push(rule.rank.toString());
|
|
23014
|
+
if (rule.percent) {
|
|
23015
|
+
cfAdditionalProperties.isPercent = true;
|
|
23016
|
+
}
|
|
23017
|
+
if (rule.bottom) {
|
|
23018
|
+
cfAdditionalProperties.isBottom = true;
|
|
23019
|
+
}
|
|
23020
|
+
break;
|
|
23007
23021
|
}
|
|
23008
23022
|
if (operator && rule.dxfId !== undefined) {
|
|
23009
23023
|
cfs.push({
|
|
@@ -23014,6 +23028,7 @@
|
|
|
23014
23028
|
type: "CellIsRule",
|
|
23015
23029
|
operator: operator,
|
|
23016
23030
|
values: values,
|
|
23031
|
+
...cfAdditionalProperties,
|
|
23017
23032
|
style: convertStyle({ fontStyle: dxfs[rule.dxfId].font, fillStyle: dxfs[rule.dxfId].fill }, warningManager),
|
|
23018
23033
|
},
|
|
23019
23034
|
});
|
|
@@ -23307,6 +23322,8 @@
|
|
|
23307
23322
|
return "greaterThanOrEqual";
|
|
23308
23323
|
case "dateIsOnOrBefore":
|
|
23309
23324
|
return "lessThanOrEqual";
|
|
23325
|
+
case "top10":
|
|
23326
|
+
return "top10";
|
|
23310
23327
|
}
|
|
23311
23328
|
}
|
|
23312
23329
|
// -------------------------------------
|
|
@@ -29367,15 +29384,20 @@
|
|
|
29367
29384
|
getPreview: (criterion) => _t("Value one of: %s", criterion.values.join(", ")),
|
|
29368
29385
|
});
|
|
29369
29386
|
criterionEvaluatorRegistry.add("isValueInRange", {
|
|
29370
|
-
type: "
|
|
29371
|
-
|
|
29387
|
+
type: "isValueInRange",
|
|
29388
|
+
preComputeCriterion: (criterion, criterionRanges, getters) => {
|
|
29389
|
+
if (criterionRanges.length === 0) {
|
|
29390
|
+
return new Set();
|
|
29391
|
+
}
|
|
29392
|
+
const sheetId = criterionRanges[0].sheetId;
|
|
29393
|
+
const criterionValues = getters.getDataValidationRangeValues(sheetId, criterion);
|
|
29394
|
+
return new Set(criterionValues.map((value) => value.value.toString().toLowerCase()));
|
|
29395
|
+
},
|
|
29396
|
+
isValueValid: (value, criterion, valuesSet) => {
|
|
29372
29397
|
if (!value) {
|
|
29373
29398
|
return false;
|
|
29374
29399
|
}
|
|
29375
|
-
|
|
29376
|
-
return criterionValues
|
|
29377
|
-
.map((value) => value.value.toLowerCase())
|
|
29378
|
-
.includes(value.toString().toLowerCase());
|
|
29400
|
+
return valuesSet.has(value.toString().toLowerCase());
|
|
29379
29401
|
},
|
|
29380
29402
|
getErrorString: (criterion) => _t("The value must be a value in the range %s", String(criterion.values[0])),
|
|
29381
29403
|
isCriterionValueValid: (value) => rangeReference.test(value),
|
|
@@ -29452,6 +29474,67 @@
|
|
|
29452
29474
|
name: _t("Is not empty"),
|
|
29453
29475
|
getPreview: () => _t("Is not empty"),
|
|
29454
29476
|
});
|
|
29477
|
+
criterionEvaluatorRegistry.add("top10", {
|
|
29478
|
+
type: "top10",
|
|
29479
|
+
preComputeCriterion: (criterion, criterionRanges, getters) => {
|
|
29480
|
+
let value = tryToNumber(criterion.values[0], DEFAULT_LOCALE);
|
|
29481
|
+
if (value === undefined || value <= 0) {
|
|
29482
|
+
return undefined;
|
|
29483
|
+
}
|
|
29484
|
+
const numberValues = [];
|
|
29485
|
+
for (const range of criterionRanges) {
|
|
29486
|
+
for (const value of getters.getRangeValues(range)) {
|
|
29487
|
+
if (typeof value === "number") {
|
|
29488
|
+
numberValues.push(value);
|
|
29489
|
+
}
|
|
29490
|
+
}
|
|
29491
|
+
}
|
|
29492
|
+
const sortedValues = numberValues.sort((a, b) => a - b);
|
|
29493
|
+
if (criterion.isPercent) {
|
|
29494
|
+
value = clip(value, 1, 100);
|
|
29495
|
+
}
|
|
29496
|
+
let index = 0;
|
|
29497
|
+
if (criterion.isBottom && !criterion.isPercent) {
|
|
29498
|
+
index = value - 1;
|
|
29499
|
+
}
|
|
29500
|
+
else if (criterion.isBottom && criterion.isPercent) {
|
|
29501
|
+
index = Math.floor((sortedValues.length * value) / 100) - 1;
|
|
29502
|
+
}
|
|
29503
|
+
else if (!criterion.isBottom && criterion.isPercent) {
|
|
29504
|
+
index = sortedValues.length - Math.floor((sortedValues.length * value) / 100);
|
|
29505
|
+
}
|
|
29506
|
+
else {
|
|
29507
|
+
index = sortedValues.length - value;
|
|
29508
|
+
}
|
|
29509
|
+
index = clip(index, 0, sortedValues.length - 1);
|
|
29510
|
+
return sortedValues[index];
|
|
29511
|
+
},
|
|
29512
|
+
isValueValid: (value, criterion, threshold) => {
|
|
29513
|
+
if (typeof value !== "number" || threshold === undefined) {
|
|
29514
|
+
return false;
|
|
29515
|
+
}
|
|
29516
|
+
return criterion.isBottom ? value <= threshold : value >= threshold;
|
|
29517
|
+
},
|
|
29518
|
+
getErrorString: (criterion) => {
|
|
29519
|
+
const args = {
|
|
29520
|
+
value: String(criterion.values[0]),
|
|
29521
|
+
percentSymbol: criterion.isPercent ? "%" : "",
|
|
29522
|
+
};
|
|
29523
|
+
return criterion.isBottom
|
|
29524
|
+
? _t("The value must be in bottom %(value)s%(percentSymbol)s", args)
|
|
29525
|
+
: _t("The value must be in top %(value)s%(percentSymbol)s", args);
|
|
29526
|
+
},
|
|
29527
|
+
isCriterionValueValid: (value) => checkValueIsPositiveNumber(value),
|
|
29528
|
+
criterionValueErrorString: DVTerms.CriterionError.positiveNumber,
|
|
29529
|
+
numberOfValues: () => 1,
|
|
29530
|
+
name: _t("Is in Top/Bottom ranking"),
|
|
29531
|
+
getPreview: (criterion) => {
|
|
29532
|
+
const args = { value: criterion.values[0], percentSymbol: criterion.isPercent ? "%" : "" };
|
|
29533
|
+
return criterion.isBottom
|
|
29534
|
+
? _t("Value is in bottom %(value)s%(percentSymbol)s", args)
|
|
29535
|
+
: _t("Value is in top %(value)s%(percentSymbol)s", args);
|
|
29536
|
+
},
|
|
29537
|
+
});
|
|
29455
29538
|
function getNumberCriterionlocalizedValues(criterion, locale) {
|
|
29456
29539
|
return criterion.values.map((value) => {
|
|
29457
29540
|
return value !== undefined
|
|
@@ -29473,6 +29556,10 @@
|
|
|
29473
29556
|
const valueAsNumber = tryToNumber(value, DEFAULT_LOCALE);
|
|
29474
29557
|
return valueAsNumber !== undefined;
|
|
29475
29558
|
}
|
|
29559
|
+
function checkValueIsPositiveNumber(value) {
|
|
29560
|
+
const valueAsNumber = tryToNumber(value, DEFAULT_LOCALE);
|
|
29561
|
+
return valueAsNumber !== undefined && valueAsNumber > 0;
|
|
29562
|
+
}
|
|
29476
29563
|
|
|
29477
29564
|
// -----------------------------------------------------------------------------
|
|
29478
29565
|
// Constants
|
|
@@ -39803,6 +39890,10 @@
|
|
|
39803
39890
|
break;
|
|
39804
39891
|
case "CellIsRule":
|
|
39805
39892
|
const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
|
|
39893
|
+
const evaluator = criterionEvaluatorRegistry.get(cf.rule.operator);
|
|
39894
|
+
const criterion = { ...cf.rule, type: cf.rule.operator };
|
|
39895
|
+
const ranges = cf.ranges.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));
|
|
39896
|
+
const preComputedCriterion = evaluator.preComputeCriterion?.(criterion, ranges, this.getters);
|
|
39806
39897
|
for (const ref of cf.ranges) {
|
|
39807
39898
|
const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
|
|
39808
39899
|
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
@@ -39815,7 +39906,7 @@
|
|
|
39815
39906
|
}
|
|
39816
39907
|
return value;
|
|
39817
39908
|
});
|
|
39818
|
-
if (this.getRuleResultForTarget(target, { ...cf.rule, values })) {
|
|
39909
|
+
if (this.getRuleResultForTarget(target, { ...cf.rule, values }, preComputedCriterion)) {
|
|
39819
39910
|
if (!computedStyle[col])
|
|
39820
39911
|
computedStyle[col] = [];
|
|
39821
39912
|
// we must combine all the properties of all the CF rules applied to the given cell
|
|
@@ -39978,7 +40069,7 @@
|
|
|
39978
40069
|
}
|
|
39979
40070
|
}
|
|
39980
40071
|
}
|
|
39981
|
-
getRuleResultForTarget(target, rule) {
|
|
40072
|
+
getRuleResultForTarget(target, rule, preComputedCriterion) {
|
|
39982
40073
|
const cell = this.getters.getEvaluatedCell(target);
|
|
39983
40074
|
if (cell.type === CellValueType.error) {
|
|
39984
40075
|
return false;
|
|
@@ -39995,11 +40086,12 @@
|
|
|
39995
40086
|
return false;
|
|
39996
40087
|
}
|
|
39997
40088
|
const evaluatedCriterion = {
|
|
40089
|
+
...rule,
|
|
39998
40090
|
type: rule.operator,
|
|
39999
40091
|
values: evaluatedCriterionValues.map(toScalar),
|
|
40000
40092
|
dateValue: rule.dateValue || "exactDate",
|
|
40001
40093
|
};
|
|
40002
|
-
return evaluator.isValueValid(cell.value ?? "", evaluatedCriterion,
|
|
40094
|
+
return evaluator.isValueValid(cell.value ?? "", evaluatedCriterion, preComputedCriterion);
|
|
40003
40095
|
}
|
|
40004
40096
|
}
|
|
40005
40097
|
|
|
@@ -40016,17 +40108,20 @@
|
|
|
40016
40108
|
"isDataValidationInvalid",
|
|
40017
40109
|
];
|
|
40018
40110
|
validationResults = {};
|
|
40111
|
+
criterionPreComputeResult = {};
|
|
40019
40112
|
handle(cmd) {
|
|
40020
40113
|
if (invalidateEvaluationCommands.has(cmd.type) ||
|
|
40021
40114
|
cmd.type === "EVALUATE_CELLS" ||
|
|
40022
40115
|
(cmd.type === "UPDATE_CELL" && ("content" in cmd || "format" in cmd))) {
|
|
40023
40116
|
this.validationResults = {};
|
|
40117
|
+
this.criterionPreComputeResult = {};
|
|
40024
40118
|
return;
|
|
40025
40119
|
}
|
|
40026
40120
|
switch (cmd.type) {
|
|
40027
40121
|
case "ADD_DATA_VALIDATION_RULE":
|
|
40028
40122
|
case "REMOVE_DATA_VALIDATION_RULE":
|
|
40029
40123
|
delete this.validationResults[cmd.sheetId];
|
|
40124
|
+
delete this.criterionPreComputeResult[cmd.sheetId];
|
|
40030
40125
|
break;
|
|
40031
40126
|
}
|
|
40032
40127
|
}
|
|
@@ -40169,7 +40264,15 @@
|
|
|
40169
40264
|
return undefined;
|
|
40170
40265
|
}
|
|
40171
40266
|
const evaluatedCriterion = { ...criterion, values: evaluatedCriterionValues.map(toScalar) };
|
|
40172
|
-
if (
|
|
40267
|
+
if (!this.criterionPreComputeResult[sheetId]) {
|
|
40268
|
+
this.criterionPreComputeResult[sheetId] = {};
|
|
40269
|
+
}
|
|
40270
|
+
let preComputedCriterion = this.criterionPreComputeResult[sheetId][rule.id];
|
|
40271
|
+
if (preComputedCriterion === undefined) {
|
|
40272
|
+
preComputedCriterion = evaluator.preComputeCriterion?.(rule.criterion, rule.ranges, this.getters);
|
|
40273
|
+
this.criterionPreComputeResult[sheetId][rule.id] = preComputedCriterion;
|
|
40274
|
+
}
|
|
40275
|
+
if (evaluator.isValueValid(cellValue, evaluatedCriterion, preComputedCriterion)) {
|
|
40173
40276
|
return undefined;
|
|
40174
40277
|
}
|
|
40175
40278
|
return evaluator.getErrorString(evaluatedCriterion, this.getters, sheetId);
|
|
@@ -45501,6 +45604,7 @@
|
|
|
45501
45604
|
if (filterValue.type === "none")
|
|
45502
45605
|
continue;
|
|
45503
45606
|
const evaluator = criterionEvaluatorRegistry.get(filterValue.type);
|
|
45607
|
+
const preComputedCriterion = evaluator.preComputeCriterion?.(filterValue, [filter.filteredRange], this.getters);
|
|
45504
45608
|
const evaluatedCriterionValues = filterValue.values.map((value) => {
|
|
45505
45609
|
if (!value.startsWith("=")) {
|
|
45506
45610
|
return parseLiteral(value, DEFAULT_LOCALE);
|
|
@@ -45518,7 +45622,7 @@
|
|
|
45518
45622
|
for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
|
|
45519
45623
|
const position = { sheetId, col: filter.col, row };
|
|
45520
45624
|
const value = this.getters.getEvaluatedCell(position).value ?? "";
|
|
45521
|
-
if (!evaluator.isValueValid(value, evaluatedCriterion,
|
|
45625
|
+
if (!evaluator.isValueValid(value, evaluatedCriterion, preComputedCriterion)) {
|
|
45522
45626
|
hiddenRows.add(row);
|
|
45523
45627
|
}
|
|
45524
45628
|
}
|
|
@@ -49659,6 +49763,8 @@
|
|
|
49659
49763
|
case undefined:
|
|
49660
49764
|
throw new Error("dateValue should be defined");
|
|
49661
49765
|
}
|
|
49766
|
+
case "top10":
|
|
49767
|
+
return [];
|
|
49662
49768
|
}
|
|
49663
49769
|
}
|
|
49664
49770
|
function cellRuleTypeAttributes(rule) {
|
|
@@ -49691,6 +49797,14 @@
|
|
|
49691
49797
|
case "dateIs":
|
|
49692
49798
|
case "customFormula":
|
|
49693
49799
|
return [["type", "expression"]];
|
|
49800
|
+
case "top10": {
|
|
49801
|
+
return [
|
|
49802
|
+
["type", "top10"],
|
|
49803
|
+
["rank", rule.values[0]],
|
|
49804
|
+
["percent", rule.isPercent ? "1" : "0"],
|
|
49805
|
+
["bottom", rule.isBottom ? "1" : "0"],
|
|
49806
|
+
];
|
|
49807
|
+
}
|
|
49694
49808
|
}
|
|
49695
49809
|
}
|
|
49696
49810
|
function addDataBarRule(cf, rule) {
|
|
@@ -51672,8 +51786,8 @@
|
|
|
51672
51786
|
|
|
51673
51787
|
|
|
51674
51788
|
__info__.version = "19.1.0-alpha.3";
|
|
51675
|
-
__info__.date = "2026-01-07T16:20:
|
|
51676
|
-
__info__.hash = "
|
|
51789
|
+
__info__.date = "2026-01-07T16:20:57.293Z";
|
|
51790
|
+
__info__.hash = "ac2fa3e";
|
|
51677
51791
|
|
|
51678
51792
|
|
|
51679
51793
|
})(this.o_spreadsheet_engine = this.o_spreadsheet_engine || {});
|