@odoo/o-spreadsheet 19.2.0-alpha.1 → 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.
@@ -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 {
@@ -2535,6 +2536,7 @@ declare class Session extends EventBus<CollaborativeEvent> {
2535
2536
  private onClientJoined;
2536
2537
  private onClientLeft;
2537
2538
  private sendUpdateMessage;
2539
+ private sendToTransport;
2538
2540
  /**
2539
2541
  * Send the next pending message
2540
2542
  */
@@ -2755,6 +2757,9 @@ type SheetValidationResult = {
2755
2757
  declare class EvaluationDataValidationPlugin extends CoreViewPlugin {
2756
2758
  static getters: readonly ["getDataValidationInvalidCriterionValueMessage", "getInvalidDataValidationMessage", "getValidationResultForCellValue", "getDataValidationRangeValues", "isCellValidCheckbox", "getDataValidationCellStyle", "getDataValidationChipStyle", "isDataValidationInvalid"];
2757
2759
  validationResults: Record<UID, SheetValidationResult>;
2760
+ criterionPreComputeResult: Record<UID, {
2761
+ [dvRuleId: UID]: unknown;
2762
+ }>;
2758
2763
  handle(cmd: CoreViewCommand): void;
2759
2764
  isDataValidationInvalid(cellPosition: CellPosition): boolean;
2760
2765
  getDataValidationCellStyle(position: CellPosition): Style | undefined;
@@ -2766,7 +2771,7 @@ declare class EvaluationDataValidationPlugin extends CoreViewPlugin {
2766
2771
  * The value must be canonicalized.
2767
2772
  */
2768
2773
  getDataValidationInvalidCriterionValueMessage(criterionType: DataValidationCriterionType, value: string): string | undefined;
2769
- getDataValidationRangeValues(sheetId: UID, criterion: EvaluatedCriterion): {
2774
+ getDataValidationRangeValues(sheetId: UID, criterion: GenericCriterion): {
2770
2775
  value: string;
2771
2776
  label: string;
2772
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 2025-12-26T10:19:48.559Z
7
- * @hash 3296c7e
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."),
@@ -18112,7 +18113,7 @@ class ReadonlyTransportFilter {
18112
18113
  if (message.type === "CLIENT_JOINED" ||
18113
18114
  message.type === "CLIENT_LEFT" ||
18114
18115
  message.type === "CLIENT_MOVED") {
18115
- this.transportService.sendMessage(message);
18116
+ await this.transportService.sendMessage(message);
18116
18117
  }
18117
18118
  // ignore all other messages
18118
18119
  }
@@ -20731,7 +20732,7 @@ class Session extends EventBus {
20731
20732
  }
20732
20733
  delete this.clients[this.clientId];
20733
20734
  this.transportService.leave(this.clientId);
20734
- this.transportService.sendMessage({
20735
+ this.sendToTransport({
20735
20736
  type: "CLIENT_LEFT",
20736
20737
  clientId: this.clientId,
20737
20738
  version: MESSAGE_VERSION,
@@ -20745,7 +20746,7 @@ class Session extends EventBus {
20745
20746
  return;
20746
20747
  }
20747
20748
  const snapshotId = this.uuidGenerator.uuidv4();
20748
- await this.transportService.sendMessage({
20749
+ await this.sendToTransport({
20749
20750
  type: "SNAPSHOT",
20750
20751
  nextRevisionId: snapshotId,
20751
20752
  serverRevisionId: this.serverRevisionId,
@@ -20793,10 +20794,14 @@ class Session extends EventBus {
20793
20794
  const type = currentPosition ? "CLIENT_MOVED" : "CLIENT_JOINED";
20794
20795
  const client = this.getCurrentClient();
20795
20796
  this.clients[this.clientId] = { ...client, position };
20796
- this.transportService.sendMessage({
20797
+ this.sendToTransport({
20797
20798
  type,
20798
20799
  version: MESSAGE_VERSION,
20799
20800
  client: { ...client, position },
20801
+ }).then(() => {
20802
+ if (this.pendingMessages.length > 0 && !this.waitingAck) {
20803
+ this.sendPendingMessage();
20804
+ }
20800
20805
  });
20801
20806
  }
20802
20807
  /**
@@ -20877,7 +20882,7 @@ class Session extends EventBus {
20877
20882
  if (client) {
20878
20883
  const { position } = client;
20879
20884
  if (position) {
20880
- this.transportService.sendMessage({
20885
+ this.sendToTransport({
20881
20886
  type: "CLIENT_MOVED",
20882
20887
  version: MESSAGE_VERSION,
20883
20888
  client: { ...client, position },
@@ -20898,6 +20903,10 @@ class Session extends EventBus {
20898
20903
  }
20899
20904
  this.sendPendingMessage();
20900
20905
  }
20906
+ async sendToTransport(message) {
20907
+ // wrap in an async function to ensure it returns a promise
20908
+ return this.transportService.sendMessage(message);
20909
+ }
20901
20910
  /**
20902
20911
  * Send the next pending message
20903
20912
  */
@@ -20926,9 +20935,14 @@ class Session extends EventBus {
20926
20935
  ${JSON.stringify(message)}`);
20927
20936
  }
20928
20937
  this.waitingAck = true;
20929
- this.transportService.sendMessage({
20938
+ this.sendToTransport({
20930
20939
  ...message,
20931
20940
  serverRevisionId: this.serverRevisionId,
20941
+ }).catch((e) => {
20942
+ if (!(e instanceof ClientDisconnectedError)) {
20943
+ throw e.cause || e;
20944
+ }
20945
+ this.waitingAck = false;
20932
20946
  });
20933
20947
  }
20934
20948
  acknowledge(message) {
@@ -22389,6 +22403,7 @@ const CF_OPERATOR_TYPE_CONVERSION_MAP = {
22389
22403
  greaterThanOrEqual: "isGreaterOrEqualTo",
22390
22404
  lessThan: "isLessThan",
22391
22405
  lessThanOrEqual: "isLessOrEqualTo",
22406
+ top10: "top10",
22392
22407
  };
22393
22408
  /** Conversion map CF types in XLSX <=> Cf types in o_spreadsheet */
22394
22409
  const CF_TYPE_CONVERSION_MAP = {
@@ -22929,6 +22944,7 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
22929
22944
  const rule = cf.cfRules[0];
22930
22945
  let operator;
22931
22946
  const values = [];
22947
+ const cfAdditionalProperties = {};
22932
22948
  if (rule.dxfId === undefined &&
22933
22949
  !(rule.type === "colorScale" || rule.type === "iconSet" || rule.type === "dataBar"))
22934
22950
  continue;
@@ -22937,7 +22953,6 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
22937
22953
  case "containsErrors":
22938
22954
  case "notContainsErrors":
22939
22955
  case "duplicateValues":
22940
- case "top10":
22941
22956
  case "uniqueValues":
22942
22957
  case "timePeriod":
22943
22958
  // Not supported
@@ -22988,6 +23003,18 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
22988
23003
  values.push(prefixFormulaWithEqual(rule.formula[1]));
22989
23004
  }
22990
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;
22991
23018
  }
22992
23019
  if (operator && rule.dxfId !== undefined) {
22993
23020
  cfs.push({
@@ -22998,6 +23025,7 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
22998
23025
  type: "CellIsRule",
22999
23026
  operator: operator,
23000
23027
  values: values,
23028
+ ...cfAdditionalProperties,
23001
23029
  style: convertStyle({ fontStyle: dxfs[rule.dxfId].font, fillStyle: dxfs[rule.dxfId].fill }, warningManager),
23002
23030
  },
23003
23031
  });
@@ -23291,6 +23319,8 @@ function convertOperator(operator) {
23291
23319
  return "greaterThanOrEqual";
23292
23320
  case "dateIsOnOrBefore":
23293
23321
  return "lessThanOrEqual";
23322
+ case "top10":
23323
+ return "top10";
23294
23324
  }
23295
23325
  }
23296
23326
  // -------------------------------------
@@ -23910,17 +23940,41 @@ function toCriterionDateNumber(dateValue) {
23910
23940
  const today = DateTime.now();
23911
23941
  switch (dateValue) {
23912
23942
  case "today":
23913
- return jsDateToNumber(today);
23914
- case "yesterday":
23915
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 1)));
23916
- case "tomorrow":
23917
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() + 1)));
23943
+ return Math.floor(jsDateToNumber(today));
23944
+ case "yesterday": {
23945
+ today.setDate(today.getDate() - 1);
23946
+ return Math.floor(jsDateToNumber(today));
23947
+ }
23948
+ case "tomorrow": {
23949
+ today.setDate(today.getDate() + 1);
23950
+ return Math.floor(jsDateToNumber(today));
23951
+ }
23918
23952
  case "lastWeek":
23919
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 7)));
23920
- case "lastMonth":
23921
- return jsDateToNumber(DateTime.fromTimestamp(today.setMonth(today.getMonth() - 1)));
23953
+ today.setDate(today.getDate() - 6);
23954
+ return Math.floor(jsDateToNumber(today));
23955
+ case "lastMonth": {
23956
+ const lastMonth = today.getMonth() === 0 ? 11 : today.getMonth() - 1;
23957
+ const dateInLastMonth = new DateTime(today.getFullYear(), lastMonth, 1);
23958
+ if (today.getDate() > getDaysInMonth(dateInLastMonth)) {
23959
+ today.setDate(1);
23960
+ }
23961
+ else {
23962
+ today.setDate(today.getDate() + 1);
23963
+ today.setMonth(today.getMonth() - 1);
23964
+ }
23965
+ return Math.floor(jsDateToNumber(today));
23966
+ }
23922
23967
  case "lastYear":
23923
- return jsDateToNumber(DateTime.fromTimestamp(today.setFullYear(today.getFullYear() - 1)));
23968
+ // Handle leap year case
23969
+ if (today.getMonth() === 1 && today.getDate() === 29) {
23970
+ today.setDate(28);
23971
+ today.setFullYear(today.getFullYear() - 1);
23972
+ }
23973
+ else {
23974
+ today.setDate(today.getDate() + 1);
23975
+ today.setFullYear(today.getFullYear() - 1);
23976
+ }
23977
+ return Math.floor(jsDateToNumber(today));
23924
23978
  }
23925
23979
  }
23926
23980
  /** Get all the dates values of a criterion converted to numbers, converting date values such as "today" to actual dates */
@@ -28945,7 +28999,7 @@ criterionEvaluatorRegistry.add("dateIs", {
28945
28999
  return false;
28946
29000
  }
28947
29001
  if (["lastWeek", "lastMonth", "lastYear"].includes(criterion.dateValue)) {
28948
- const today = jsDateToRoundNumber(DateTime.now());
29002
+ const today = Math.floor(jsDateToNumber(DateTime.now()));
28949
29003
  return isDateBetween(dateValue, today, criterionValue);
28950
29004
  }
28951
29005
  return areDatesSameDay(dateValue, criterionValue);
@@ -29327,15 +29381,20 @@ criterionEvaluatorRegistry.add("isValueInList", {
29327
29381
  getPreview: (criterion) => _t("Value one of: %s", criterion.values.join(", ")),
29328
29382
  });
29329
29383
  criterionEvaluatorRegistry.add("isValueInRange", {
29330
- type: "isValueInList",
29331
- isValueValid: (value, criterion, getters, sheetId) => {
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) => {
29332
29394
  if (!value) {
29333
29395
  return false;
29334
29396
  }
29335
- const criterionValues = getters.getDataValidationRangeValues(sheetId, criterion);
29336
- return criterionValues
29337
- .map((value) => value.value.toLowerCase())
29338
- .includes(value.toString().toLowerCase());
29397
+ return valuesSet.has(value.toString().toLowerCase());
29339
29398
  },
29340
29399
  getErrorString: (criterion) => _t("The value must be a value in the range %s", String(criterion.values[0])),
29341
29400
  isCriterionValueValid: (value) => rangeReference.test(value),
@@ -29412,6 +29471,67 @@ criterionEvaluatorRegistry.add("isNotEmpty", {
29412
29471
  name: _t("Is not empty"),
29413
29472
  getPreview: () => _t("Is not empty"),
29414
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
+ });
29415
29535
  function getNumberCriterionlocalizedValues(criterion, locale) {
29416
29536
  return criterion.values.map((value) => {
29417
29537
  return value !== undefined
@@ -29433,6 +29553,10 @@ function checkValueIsNumber(value) {
29433
29553
  const valueAsNumber = tryToNumber(value, DEFAULT_LOCALE);
29434
29554
  return valueAsNumber !== undefined;
29435
29555
  }
29556
+ function checkValueIsPositiveNumber(value) {
29557
+ const valueAsNumber = tryToNumber(value, DEFAULT_LOCALE);
29558
+ return valueAsNumber !== undefined && valueAsNumber > 0;
29559
+ }
29436
29560
 
29437
29561
  // -----------------------------------------------------------------------------
29438
29562
  // Constants
@@ -39763,6 +39887,10 @@ class EvaluationConditionalFormatPlugin extends CoreViewPlugin {
39763
39887
  break;
39764
39888
  case "CellIsRule":
39765
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);
39766
39894
  for (const ref of cf.ranges) {
39767
39895
  const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
39768
39896
  for (let row = zone.top; row <= zone.bottom; row++) {
@@ -39775,7 +39903,7 @@ class EvaluationConditionalFormatPlugin extends CoreViewPlugin {
39775
39903
  }
39776
39904
  return value;
39777
39905
  });
39778
- if (this.getRuleResultForTarget(target, { ...cf.rule, values })) {
39906
+ if (this.getRuleResultForTarget(target, { ...cf.rule, values }, preComputedCriterion)) {
39779
39907
  if (!computedStyle[col])
39780
39908
  computedStyle[col] = [];
39781
39909
  // we must combine all the properties of all the CF rules applied to the given cell
@@ -39938,7 +40066,7 @@ class EvaluationConditionalFormatPlugin extends CoreViewPlugin {
39938
40066
  }
39939
40067
  }
39940
40068
  }
39941
- getRuleResultForTarget(target, rule) {
40069
+ getRuleResultForTarget(target, rule, preComputedCriterion) {
39942
40070
  const cell = this.getters.getEvaluatedCell(target);
39943
40071
  if (cell.type === CellValueType.error) {
39944
40072
  return false;
@@ -39955,11 +40083,12 @@ class EvaluationConditionalFormatPlugin extends CoreViewPlugin {
39955
40083
  return false;
39956
40084
  }
39957
40085
  const evaluatedCriterion = {
40086
+ ...rule,
39958
40087
  type: rule.operator,
39959
40088
  values: evaluatedCriterionValues.map(toScalar),
39960
40089
  dateValue: rule.dateValue || "exactDate",
39961
40090
  };
39962
- return evaluator.isValueValid(cell.value ?? "", evaluatedCriterion, this.getters, sheetId);
40091
+ return evaluator.isValueValid(cell.value ?? "", evaluatedCriterion, preComputedCriterion);
39963
40092
  }
39964
40093
  }
39965
40094
 
@@ -39976,17 +40105,20 @@ class EvaluationDataValidationPlugin extends CoreViewPlugin {
39976
40105
  "isDataValidationInvalid",
39977
40106
  ];
39978
40107
  validationResults = {};
40108
+ criterionPreComputeResult = {};
39979
40109
  handle(cmd) {
39980
40110
  if (invalidateEvaluationCommands.has(cmd.type) ||
39981
40111
  cmd.type === "EVALUATE_CELLS" ||
39982
40112
  (cmd.type === "UPDATE_CELL" && ("content" in cmd || "format" in cmd))) {
39983
40113
  this.validationResults = {};
40114
+ this.criterionPreComputeResult = {};
39984
40115
  return;
39985
40116
  }
39986
40117
  switch (cmd.type) {
39987
40118
  case "ADD_DATA_VALIDATION_RULE":
39988
40119
  case "REMOVE_DATA_VALIDATION_RULE":
39989
40120
  delete this.validationResults[cmd.sheetId];
40121
+ delete this.criterionPreComputeResult[cmd.sheetId];
39990
40122
  break;
39991
40123
  }
39992
40124
  }
@@ -40129,7 +40261,15 @@ class EvaluationDataValidationPlugin extends CoreViewPlugin {
40129
40261
  return undefined;
40130
40262
  }
40131
40263
  const evaluatedCriterion = { ...criterion, values: evaluatedCriterionValues.map(toScalar) };
40132
- if (evaluator.isValueValid(cellValue, evaluatedCriterion, this.getters, sheetId)) {
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)) {
40133
40273
  return undefined;
40134
40274
  }
40135
40275
  return evaluator.getErrorString(evaluatedCriterion, this.getters, sheetId);
@@ -45461,6 +45601,7 @@ class FilterEvaluationPlugin extends UIPlugin {
45461
45601
  if (filterValue.type === "none")
45462
45602
  continue;
45463
45603
  const evaluator = criterionEvaluatorRegistry.get(filterValue.type);
45604
+ const preComputedCriterion = evaluator.preComputeCriterion?.(filterValue, [filter.filteredRange], this.getters);
45464
45605
  const evaluatedCriterionValues = filterValue.values.map((value) => {
45465
45606
  if (!value.startsWith("=")) {
45466
45607
  return parseLiteral(value, DEFAULT_LOCALE);
@@ -45478,7 +45619,7 @@ class FilterEvaluationPlugin extends UIPlugin {
45478
45619
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
45479
45620
  const position = { sheetId, col: filter.col, row };
45480
45621
  const value = this.getters.getEvaluatedCell(position).value ?? "";
45481
- if (!evaluator.isValueValid(value, evaluatedCriterion, this.getters, sheetId)) {
45622
+ if (!evaluator.isValueValid(value, evaluatedCriterion, preComputedCriterion)) {
45482
45623
  hiddenRows.add(row);
45483
45624
  }
45484
45625
  }
@@ -49619,6 +49760,8 @@ function cellRuleFormula(ranges, rule) {
49619
49760
  case undefined:
49620
49761
  throw new Error("dateValue should be defined");
49621
49762
  }
49763
+ case "top10":
49764
+ return [];
49622
49765
  }
49623
49766
  }
49624
49767
  function cellRuleTypeAttributes(rule) {
@@ -49651,6 +49794,14 @@ function cellRuleTypeAttributes(rule) {
49651
49794
  case "dateIs":
49652
49795
  case "customFormula":
49653
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
+ }
49654
49805
  }
49655
49806
  }
49656
49807
  function addDataBarRule(cf, rule) {
@@ -51523,5 +51674,5 @@ export { BadExpressionError, BasePlugin, CellErrorType, CircularDependencyError,
51523
51674
 
51524
51675
 
51525
51676
  __info__.version = "19.1.0-alpha.3";
51526
- __info__.date = "2025-12-26T10:19:48.559Z";
51527
- __info__.hash = "3296c7e";
51677
+ __info__.date = "2026-01-07T16:20:57.293Z";
51678
+ __info__.hash = "ac2fa3e";