@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.
@@ -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
  (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."),
@@ -18115,7 +18116,7 @@
18115
18116
  if (message.type === "CLIENT_JOINED" ||
18116
18117
  message.type === "CLIENT_LEFT" ||
18117
18118
  message.type === "CLIENT_MOVED") {
18118
- this.transportService.sendMessage(message);
18119
+ await this.transportService.sendMessage(message);
18119
18120
  }
18120
18121
  // ignore all other messages
18121
18122
  }
@@ -20734,7 +20735,7 @@
20734
20735
  }
20735
20736
  delete this.clients[this.clientId];
20736
20737
  this.transportService.leave(this.clientId);
20737
- this.transportService.sendMessage({
20738
+ this.sendToTransport({
20738
20739
  type: "CLIENT_LEFT",
20739
20740
  clientId: this.clientId,
20740
20741
  version: MESSAGE_VERSION,
@@ -20748,7 +20749,7 @@
20748
20749
  return;
20749
20750
  }
20750
20751
  const snapshotId = this.uuidGenerator.uuidv4();
20751
- await this.transportService.sendMessage({
20752
+ await this.sendToTransport({
20752
20753
  type: "SNAPSHOT",
20753
20754
  nextRevisionId: snapshotId,
20754
20755
  serverRevisionId: this.serverRevisionId,
@@ -20796,10 +20797,14 @@
20796
20797
  const type = currentPosition ? "CLIENT_MOVED" : "CLIENT_JOINED";
20797
20798
  const client = this.getCurrentClient();
20798
20799
  this.clients[this.clientId] = { ...client, position };
20799
- this.transportService.sendMessage({
20800
+ this.sendToTransport({
20800
20801
  type,
20801
20802
  version: MESSAGE_VERSION,
20802
20803
  client: { ...client, position },
20804
+ }).then(() => {
20805
+ if (this.pendingMessages.length > 0 && !this.waitingAck) {
20806
+ this.sendPendingMessage();
20807
+ }
20803
20808
  });
20804
20809
  }
20805
20810
  /**
@@ -20880,7 +20885,7 @@
20880
20885
  if (client) {
20881
20886
  const { position } = client;
20882
20887
  if (position) {
20883
- this.transportService.sendMessage({
20888
+ this.sendToTransport({
20884
20889
  type: "CLIENT_MOVED",
20885
20890
  version: MESSAGE_VERSION,
20886
20891
  client: { ...client, position },
@@ -20901,6 +20906,10 @@
20901
20906
  }
20902
20907
  this.sendPendingMessage();
20903
20908
  }
20909
+ async sendToTransport(message) {
20910
+ // wrap in an async function to ensure it returns a promise
20911
+ return this.transportService.sendMessage(message);
20912
+ }
20904
20913
  /**
20905
20914
  * Send the next pending message
20906
20915
  */
@@ -20929,9 +20938,14 @@
20929
20938
  ${JSON.stringify(message)}`);
20930
20939
  }
20931
20940
  this.waitingAck = true;
20932
- this.transportService.sendMessage({
20941
+ this.sendToTransport({
20933
20942
  ...message,
20934
20943
  serverRevisionId: this.serverRevisionId,
20944
+ }).catch((e) => {
20945
+ if (!(e instanceof ClientDisconnectedError)) {
20946
+ throw e.cause || e;
20947
+ }
20948
+ this.waitingAck = false;
20935
20949
  });
20936
20950
  }
20937
20951
  acknowledge(message) {
@@ -22392,6 +22406,7 @@
22392
22406
  greaterThanOrEqual: "isGreaterOrEqualTo",
22393
22407
  lessThan: "isLessThan",
22394
22408
  lessThanOrEqual: "isLessOrEqualTo",
22409
+ top10: "top10",
22395
22410
  };
22396
22411
  /** Conversion map CF types in XLSX <=> Cf types in o_spreadsheet */
22397
22412
  const CF_TYPE_CONVERSION_MAP = {
@@ -22932,6 +22947,7 @@
22932
22947
  const rule = cf.cfRules[0];
22933
22948
  let operator;
22934
22949
  const values = [];
22950
+ const cfAdditionalProperties = {};
22935
22951
  if (rule.dxfId === undefined &&
22936
22952
  !(rule.type === "colorScale" || rule.type === "iconSet" || rule.type === "dataBar"))
22937
22953
  continue;
@@ -22940,7 +22956,6 @@
22940
22956
  case "containsErrors":
22941
22957
  case "notContainsErrors":
22942
22958
  case "duplicateValues":
22943
- case "top10":
22944
22959
  case "uniqueValues":
22945
22960
  case "timePeriod":
22946
22961
  // Not supported
@@ -22991,6 +23006,18 @@
22991
23006
  values.push(prefixFormulaWithEqual(rule.formula[1]));
22992
23007
  }
22993
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;
22994
23021
  }
22995
23022
  if (operator && rule.dxfId !== undefined) {
22996
23023
  cfs.push({
@@ -23001,6 +23028,7 @@
23001
23028
  type: "CellIsRule",
23002
23029
  operator: operator,
23003
23030
  values: values,
23031
+ ...cfAdditionalProperties,
23004
23032
  style: convertStyle({ fontStyle: dxfs[rule.dxfId].font, fillStyle: dxfs[rule.dxfId].fill }, warningManager),
23005
23033
  },
23006
23034
  });
@@ -23294,6 +23322,8 @@
23294
23322
  return "greaterThanOrEqual";
23295
23323
  case "dateIsOnOrBefore":
23296
23324
  return "lessThanOrEqual";
23325
+ case "top10":
23326
+ return "top10";
23297
23327
  }
23298
23328
  }
23299
23329
  // -------------------------------------
@@ -23913,17 +23943,41 @@
23913
23943
  const today = DateTime.now();
23914
23944
  switch (dateValue) {
23915
23945
  case "today":
23916
- return jsDateToNumber(today);
23917
- case "yesterday":
23918
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 1)));
23919
- case "tomorrow":
23920
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() + 1)));
23946
+ return Math.floor(jsDateToNumber(today));
23947
+ case "yesterday": {
23948
+ today.setDate(today.getDate() - 1);
23949
+ return Math.floor(jsDateToNumber(today));
23950
+ }
23951
+ case "tomorrow": {
23952
+ today.setDate(today.getDate() + 1);
23953
+ return Math.floor(jsDateToNumber(today));
23954
+ }
23921
23955
  case "lastWeek":
23922
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 7)));
23923
- case "lastMonth":
23924
- return jsDateToNumber(DateTime.fromTimestamp(today.setMonth(today.getMonth() - 1)));
23956
+ today.setDate(today.getDate() - 6);
23957
+ return Math.floor(jsDateToNumber(today));
23958
+ case "lastMonth": {
23959
+ const lastMonth = today.getMonth() === 0 ? 11 : today.getMonth() - 1;
23960
+ const dateInLastMonth = new DateTime(today.getFullYear(), lastMonth, 1);
23961
+ if (today.getDate() > getDaysInMonth(dateInLastMonth)) {
23962
+ today.setDate(1);
23963
+ }
23964
+ else {
23965
+ today.setDate(today.getDate() + 1);
23966
+ today.setMonth(today.getMonth() - 1);
23967
+ }
23968
+ return Math.floor(jsDateToNumber(today));
23969
+ }
23925
23970
  case "lastYear":
23926
- return jsDateToNumber(DateTime.fromTimestamp(today.setFullYear(today.getFullYear() - 1)));
23971
+ // Handle leap year case
23972
+ if (today.getMonth() === 1 && today.getDate() === 29) {
23973
+ today.setDate(28);
23974
+ today.setFullYear(today.getFullYear() - 1);
23975
+ }
23976
+ else {
23977
+ today.setDate(today.getDate() + 1);
23978
+ today.setFullYear(today.getFullYear() - 1);
23979
+ }
23980
+ return Math.floor(jsDateToNumber(today));
23927
23981
  }
23928
23982
  }
23929
23983
  /** Get all the dates values of a criterion converted to numbers, converting date values such as "today" to actual dates */
@@ -28948,7 +29002,7 @@
28948
29002
  return false;
28949
29003
  }
28950
29004
  if (["lastWeek", "lastMonth", "lastYear"].includes(criterion.dateValue)) {
28951
- const today = jsDateToRoundNumber(DateTime.now());
29005
+ const today = Math.floor(jsDateToNumber(DateTime.now()));
28952
29006
  return isDateBetween(dateValue, today, criterionValue);
28953
29007
  }
28954
29008
  return areDatesSameDay(dateValue, criterionValue);
@@ -29330,15 +29384,20 @@
29330
29384
  getPreview: (criterion) => _t("Value one of: %s", criterion.values.join(", ")),
29331
29385
  });
29332
29386
  criterionEvaluatorRegistry.add("isValueInRange", {
29333
- type: "isValueInList",
29334
- isValueValid: (value, criterion, getters, sheetId) => {
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) => {
29335
29397
  if (!value) {
29336
29398
  return false;
29337
29399
  }
29338
- const criterionValues = getters.getDataValidationRangeValues(sheetId, criterion);
29339
- return criterionValues
29340
- .map((value) => value.value.toLowerCase())
29341
- .includes(value.toString().toLowerCase());
29400
+ return valuesSet.has(value.toString().toLowerCase());
29342
29401
  },
29343
29402
  getErrorString: (criterion) => _t("The value must be a value in the range %s", String(criterion.values[0])),
29344
29403
  isCriterionValueValid: (value) => rangeReference.test(value),
@@ -29415,6 +29474,67 @@
29415
29474
  name: _t("Is not empty"),
29416
29475
  getPreview: () => _t("Is not empty"),
29417
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
+ });
29418
29538
  function getNumberCriterionlocalizedValues(criterion, locale) {
29419
29539
  return criterion.values.map((value) => {
29420
29540
  return value !== undefined
@@ -29436,6 +29556,10 @@
29436
29556
  const valueAsNumber = tryToNumber(value, DEFAULT_LOCALE);
29437
29557
  return valueAsNumber !== undefined;
29438
29558
  }
29559
+ function checkValueIsPositiveNumber(value) {
29560
+ const valueAsNumber = tryToNumber(value, DEFAULT_LOCALE);
29561
+ return valueAsNumber !== undefined && valueAsNumber > 0;
29562
+ }
29439
29563
 
29440
29564
  // -----------------------------------------------------------------------------
29441
29565
  // Constants
@@ -39766,6 +39890,10 @@
39766
39890
  break;
39767
39891
  case "CellIsRule":
39768
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);
39769
39897
  for (const ref of cf.ranges) {
39770
39898
  const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
39771
39899
  for (let row = zone.top; row <= zone.bottom; row++) {
@@ -39778,7 +39906,7 @@
39778
39906
  }
39779
39907
  return value;
39780
39908
  });
39781
- if (this.getRuleResultForTarget(target, { ...cf.rule, values })) {
39909
+ if (this.getRuleResultForTarget(target, { ...cf.rule, values }, preComputedCriterion)) {
39782
39910
  if (!computedStyle[col])
39783
39911
  computedStyle[col] = [];
39784
39912
  // we must combine all the properties of all the CF rules applied to the given cell
@@ -39941,7 +40069,7 @@
39941
40069
  }
39942
40070
  }
39943
40071
  }
39944
- getRuleResultForTarget(target, rule) {
40072
+ getRuleResultForTarget(target, rule, preComputedCriterion) {
39945
40073
  const cell = this.getters.getEvaluatedCell(target);
39946
40074
  if (cell.type === CellValueType.error) {
39947
40075
  return false;
@@ -39958,11 +40086,12 @@
39958
40086
  return false;
39959
40087
  }
39960
40088
  const evaluatedCriterion = {
40089
+ ...rule,
39961
40090
  type: rule.operator,
39962
40091
  values: evaluatedCriterionValues.map(toScalar),
39963
40092
  dateValue: rule.dateValue || "exactDate",
39964
40093
  };
39965
- return evaluator.isValueValid(cell.value ?? "", evaluatedCriterion, this.getters, sheetId);
40094
+ return evaluator.isValueValid(cell.value ?? "", evaluatedCriterion, preComputedCriterion);
39966
40095
  }
39967
40096
  }
39968
40097
 
@@ -39979,17 +40108,20 @@
39979
40108
  "isDataValidationInvalid",
39980
40109
  ];
39981
40110
  validationResults = {};
40111
+ criterionPreComputeResult = {};
39982
40112
  handle(cmd) {
39983
40113
  if (invalidateEvaluationCommands.has(cmd.type) ||
39984
40114
  cmd.type === "EVALUATE_CELLS" ||
39985
40115
  (cmd.type === "UPDATE_CELL" && ("content" in cmd || "format" in cmd))) {
39986
40116
  this.validationResults = {};
40117
+ this.criterionPreComputeResult = {};
39987
40118
  return;
39988
40119
  }
39989
40120
  switch (cmd.type) {
39990
40121
  case "ADD_DATA_VALIDATION_RULE":
39991
40122
  case "REMOVE_DATA_VALIDATION_RULE":
39992
40123
  delete this.validationResults[cmd.sheetId];
40124
+ delete this.criterionPreComputeResult[cmd.sheetId];
39993
40125
  break;
39994
40126
  }
39995
40127
  }
@@ -40132,7 +40264,15 @@
40132
40264
  return undefined;
40133
40265
  }
40134
40266
  const evaluatedCriterion = { ...criterion, values: evaluatedCriterionValues.map(toScalar) };
40135
- if (evaluator.isValueValid(cellValue, evaluatedCriterion, this.getters, sheetId)) {
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)) {
40136
40276
  return undefined;
40137
40277
  }
40138
40278
  return evaluator.getErrorString(evaluatedCriterion, this.getters, sheetId);
@@ -45464,6 +45604,7 @@
45464
45604
  if (filterValue.type === "none")
45465
45605
  continue;
45466
45606
  const evaluator = criterionEvaluatorRegistry.get(filterValue.type);
45607
+ const preComputedCriterion = evaluator.preComputeCriterion?.(filterValue, [filter.filteredRange], this.getters);
45467
45608
  const evaluatedCriterionValues = filterValue.values.map((value) => {
45468
45609
  if (!value.startsWith("=")) {
45469
45610
  return parseLiteral(value, DEFAULT_LOCALE);
@@ -45481,7 +45622,7 @@
45481
45622
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
45482
45623
  const position = { sheetId, col: filter.col, row };
45483
45624
  const value = this.getters.getEvaluatedCell(position).value ?? "";
45484
- if (!evaluator.isValueValid(value, evaluatedCriterion, this.getters, sheetId)) {
45625
+ if (!evaluator.isValueValid(value, evaluatedCriterion, preComputedCriterion)) {
45485
45626
  hiddenRows.add(row);
45486
45627
  }
45487
45628
  }
@@ -49622,6 +49763,8 @@
49622
49763
  case undefined:
49623
49764
  throw new Error("dateValue should be defined");
49624
49765
  }
49766
+ case "top10":
49767
+ return [];
49625
49768
  }
49626
49769
  }
49627
49770
  function cellRuleTypeAttributes(rule) {
@@ -49654,6 +49797,14 @@
49654
49797
  case "dateIs":
49655
49798
  case "customFormula":
49656
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
+ }
49657
49808
  }
49658
49809
  }
49659
49810
  function addDataBarRule(cf, rule) {
@@ -51635,8 +51786,8 @@
51635
51786
 
51636
51787
 
51637
51788
  __info__.version = "19.1.0-alpha.3";
51638
- __info__.date = "2025-12-26T10:19:48.559Z";
51639
- __info__.hash = "3296c7e";
51789
+ __info__.date = "2026-01-07T16:20:57.293Z";
51790
+ __info__.hash = "ac2fa3e";
51640
51791
 
51641
51792
 
51642
51793
  })(this.o_spreadsheet_engine = this.o_spreadsheet_engine || {});