@odoo/o-spreadsheet 16.3.17 → 16.3.22

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.
@@ -302,15 +302,80 @@ const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 18, 24, 36];
302
302
  // -----------------------------------------------------------------------------
303
303
  // Date Type
304
304
  // -----------------------------------------------------------------------------
305
+ /**
306
+ * A DateTime object that can be used to manipulate spreadsheet dates.
307
+ * Conceptually, a spreadsheet date is simply a number with a date format,
308
+ * and it is timezone-agnostic.
309
+ * This DateTime object consistently uses UTC time to represent a naive date and time.
310
+ */
311
+ class DateTime {
312
+ jsDate;
313
+ constructor(year, month, day, hours = 0, minutes = 0, seconds = 0) {
314
+ this.jsDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds, 0));
315
+ }
316
+ static fromTimestamp(timestamp) {
317
+ const date = new Date(timestamp);
318
+ return new DateTime(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
319
+ }
320
+ static now() {
321
+ const now = new Date();
322
+ return new DateTime(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds());
323
+ }
324
+ toString() {
325
+ return this.jsDate.toString();
326
+ }
327
+ toLocaleDateString() {
328
+ return this.jsDate.toLocaleDateString();
329
+ }
330
+ getTime() {
331
+ return this.jsDate.getTime();
332
+ }
333
+ getFullYear() {
334
+ return this.jsDate.getUTCFullYear();
335
+ }
336
+ getMonth() {
337
+ return this.jsDate.getUTCMonth();
338
+ }
339
+ getDate() {
340
+ return this.jsDate.getUTCDate();
341
+ }
342
+ getDay() {
343
+ return this.jsDate.getUTCDay();
344
+ }
345
+ getHours() {
346
+ return this.jsDate.getUTCHours();
347
+ }
348
+ getMinutes() {
349
+ return this.jsDate.getUTCMinutes();
350
+ }
351
+ getSeconds() {
352
+ return this.jsDate.getUTCSeconds();
353
+ }
354
+ setFullYear(year) {
355
+ this.jsDate.setFullYear(year);
356
+ }
357
+ setDate(date) {
358
+ this.jsDate.setUTCDate(date);
359
+ }
360
+ setHours(hours) {
361
+ this.jsDate.setUTCHours(hours);
362
+ }
363
+ setMinutes(minutes) {
364
+ this.jsDate.setUTCMinutes(minutes);
365
+ }
366
+ setSeconds(seconds) {
367
+ this.jsDate.setUTCSeconds(seconds);
368
+ }
369
+ }
305
370
  // -----------------------------------------------------------------------------
306
371
  // Parsing
307
372
  // -----------------------------------------------------------------------------
308
- const INITIAL_1900_DAY = new Date(1899, 11, 30);
373
+ const INITIAL_1900_DAY = new DateTime(1899, 11, 30);
309
374
  const MS_PER_DAY = 24 * 60 * 60 * 1000;
310
375
  const CURRENT_MILLENIAL = 2000; // note: don't forget to update this in 2999
311
- const CURRENT_YEAR = new Date().getFullYear();
312
- const INITIAL_JS_DAY = new Date(0);
313
- const DATE_JS_1900_OFFSET = INITIAL_JS_DAY - INITIAL_1900_DAY;
376
+ const CURRENT_YEAR = DateTime.now().getFullYear();
377
+ const INITIAL_JS_DAY = DateTime.fromTimestamp(0);
378
+ const DATE_JS_1900_OFFSET = INITIAL_JS_DAY.getTime() - INITIAL_1900_DAY.getTime();
314
379
  const mdyDateRegexp = /^\d{1,2}(\/|-|\s)\d{1,2}((\/|-|\s)\d{1,4})?$/;
315
380
  const ymdDateRegexp = /^\d{3,4}(\/|-|\s)\d{1,2}(\/|-|\s)\d{1,2}$/;
316
381
  const timeRegexp = /((\d+(:\d+)?(:\d+)?\s*(AM|PM))|(\d+:\d+(:\d+)?))$/;
@@ -359,7 +424,7 @@ function _parseDateTime(str) {
359
424
  return {
360
425
  value: date.value + time.value,
361
426
  format: date.format + " " + (time.format === "hhhh:mm:ss" ? "hh:mm:ss" : time.format),
362
- jsDate: new Date(date.jsDate.getFullYear() + time.jsDate.getFullYear() - 1899, date.jsDate.getMonth() + time.jsDate.getMonth() - 11, date.jsDate.getDate() + time.jsDate.getDate() - 30, date.jsDate.getHours() + time.jsDate.getHours(), date.jsDate.getMinutes() + time.jsDate.getMinutes(), date.jsDate.getSeconds() + time.jsDate.getSeconds()),
427
+ jsDate: new DateTime(date.jsDate.getFullYear() + time.jsDate.getFullYear() - 1899, date.jsDate.getMonth() + time.jsDate.getMonth() - 11, date.jsDate.getDate() + time.jsDate.getDate() - 30, date.jsDate.getHours() + time.jsDate.getHours(), date.jsDate.getMinutes() + time.jsDate.getMinutes(), date.jsDate.getSeconds() + time.jsDate.getSeconds()),
363
428
  };
364
429
  }
365
430
  return date || time;
@@ -376,13 +441,13 @@ function parseDate(str, dateFormat) {
376
441
  const day = Number(parts[dayIndex]);
377
442
  const leadingZero = (parts[monthIndex].length === 2 && month < 10) || (parts[dayIndex].length === 2 && day < 10);
378
443
  const year = parts[yearIndex] ? inferYear(parts[yearIndex]) : CURRENT_YEAR;
379
- const jsDate = new Date(year, month - 1, day);
444
+ const jsDate = new DateTime(year, month - 1, day);
380
445
  const sep = str.match(/\/|-|\s/)[0];
381
446
  if (jsDate.getMonth() !== month - 1 || jsDate.getDate() !== day) {
382
447
  // invalid date
383
448
  return null;
384
449
  }
385
- const delta = jsDate - INITIAL_1900_DAY;
450
+ const delta = jsDate.getTime() - INITIAL_1900_DAY.getTime();
386
451
  let format = leadingZero ? `mm${sep}dd` : `m${sep}d`;
387
452
  if (parts[yearIndex]) {
388
453
  format = isMDY ? format + sep + "yyyy" : "yyyy" + sep + format;
@@ -442,7 +507,7 @@ function parseTime(str) {
442
507
  if (hours >= 24) {
443
508
  format = "hhhh:mm:ss";
444
509
  }
445
- const jsDate = new Date(1899, 11, 30, hours, minutes, seconds);
510
+ const jsDate = new DateTime(1899, 11, 30, hours, minutes, seconds);
446
511
  return {
447
512
  value: hours / 24 + minutes / 1440 + seconds / 86400,
448
513
  format: format,
@@ -456,7 +521,7 @@ function parseTime(str) {
456
521
  // -----------------------------------------------------------------------------
457
522
  function numberToJsDate(value) {
458
523
  const truncValue = Math.trunc(value);
459
- let date = new Date(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
524
+ let date = DateTime.fromTimestamp(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
460
525
  let time = value - truncValue;
461
526
  time = time < 0 ? 1 + time : time;
462
527
  const hours = Math.round(time * 24);
@@ -473,7 +538,7 @@ function jsDateToRoundNumber(date) {
473
538
  }
474
539
  /** Return the number of days in the current month of the given date */
475
540
  function getDaysInMonth(date) {
476
- return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
541
+ return new DateTime(date.getFullYear(), date.getMonth() + 1, 0).getDate();
477
542
  }
478
543
  function isLastDayOfMonth(date) {
479
544
  return getDaysInMonth(date) === date.getDate();
@@ -491,7 +556,7 @@ function addMonthsToDate(date, months, keepEndOfMonth) {
491
556
  const yStart = date.getFullYear();
492
557
  const mStart = date.getMonth();
493
558
  const dStart = date.getDate();
494
- const jsDate = new Date(yStart, mStart + months);
559
+ const jsDate = new DateTime(yStart, mStart + months, 1);
495
560
  if (keepEndOfMonth && dStart === getDaysInMonth(date)) {
496
561
  jsDate.setDate(getDaysInMonth(jsDate));
497
562
  }
@@ -651,13 +716,6 @@ function areTwoDatesWithinOneYear(startDate, endDate) {
651
716
  }
652
717
 
653
718
  //------------------------------------------------------------------------------
654
- /**
655
- * Stringify an object, like JSON.stringify, except that the first level of keys
656
- * is ordered.
657
- */
658
- function stringify(obj) {
659
- return JSON.stringify(obj, Object.keys(obj).sort());
660
- }
661
719
  /**
662
720
  * Remove quotes from a quoted string
663
721
  * ```js
@@ -887,7 +945,7 @@ function isBoolean(str) {
887
945
  function isDateTime(str) {
888
946
  return parseDateTime(str) !== null;
889
947
  }
890
- const MARKDOWN_LINK_REGEX = /^\[([^\[]+)\]\((.+)\)$/;
948
+ const MARKDOWN_LINK_REGEX = /^\[(.+)\]\((.+)\)$/;
891
949
  //link must start with http or https
892
950
  //https://stackoverflow.com/a/3809435/4760614
893
951
  const WEB_LINK_REGEX = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/;
@@ -956,7 +1014,7 @@ function isObjectEmptyRecursive(argument) {
956
1014
  */
957
1015
  function getItemId(item, itemsDic) {
958
1016
  for (let [key, value] of Object.entries(itemsDic)) {
959
- if (stringify(value) === stringify(item)) {
1017
+ if (deepEquals(value, item)) {
960
1018
  return parseInt(key, 10);
961
1019
  }
962
1020
  }
@@ -1022,6 +1080,22 @@ function lazy(fn) {
1022
1080
  lazyValue.map = (callback) => lazy(() => callback(lazyValue()));
1023
1081
  return lazyValue;
1024
1082
  }
1083
+ /**
1084
+ * Creates a version of the function that's memoized on the value of its first
1085
+ * argument, if any.
1086
+ */
1087
+ function memoize(func) {
1088
+ const cache = new Map();
1089
+ const funcName = func.name ? func.name + " (memoized)" : "memoized";
1090
+ return {
1091
+ [funcName](...args) {
1092
+ if (!cache.has(args[0])) {
1093
+ cache.set(args[0], func(...args));
1094
+ }
1095
+ return cache.get(args[0]);
1096
+ },
1097
+ }[funcName];
1098
+ }
1025
1099
  /**
1026
1100
  * Find the next defined value after the given index in an array of strings. If there is no defined value
1027
1101
  * after the index, return the closest defined value before the index. Return an empty string if no
@@ -1940,7 +2014,7 @@ function formatJSTime(jsDate, format) {
1940
2014
  .map((p) => {
1941
2015
  switch (p) {
1942
2016
  case "hhhh":
1943
- const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY) / (60 * 60 * 1000));
2017
+ const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY.getTime()) / (60 * 60 * 1000));
1944
2018
  return helapsedHours.toString();
1945
2019
  case "hh":
1946
2020
  return hours.toString().padStart(2, "0");
@@ -2187,8 +2261,10 @@ function convertInternalFormatToFormat(internalFormat) {
2187
2261
  const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
2188
2262
  // Same as above, but matches the exact string (nothing before or after)
2189
2263
  const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
2190
- /** Reference of a column header (eg. A, AB) */
2191
- const colHeader = new RegExp(/^([A-Z]{1,3})+$/, "i");
2264
+ /** Reference of a column header (eg. A, AB, $A) */
2265
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
2266
+ /** Reference of a row header (eg. 1, $1) */
2267
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
2192
2268
  /** Reference of a column (eg. A, $CA, Sheet1!B) */
2193
2269
  const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
2194
2270
  /** Reference of a row (eg. 1, 59, Sheet1!9) */
@@ -2218,6 +2294,9 @@ function isRowReference(xc) {
2218
2294
  function isColHeader(str) {
2219
2295
  return colHeader.test(str);
2220
2296
  }
2297
+ function isRowHeader(str) {
2298
+ return rowHeader.test(str);
2299
+ }
2221
2300
  /**
2222
2301
  * Return true if the given xc is the reference of a single cell,
2223
2302
  * without any specified sheet (e.g. A1)
@@ -2284,18 +2363,10 @@ class RangeImpl {
2284
2363
  if (isFullCol) {
2285
2364
  parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2286
2365
  parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2287
- if (zone.left === zone.right) {
2288
- parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2289
- parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2290
- }
2291
2366
  }
2292
2367
  if (isFullRow) {
2293
2368
  parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2294
2369
  parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2295
- if (zone.top === zone.bottom) {
2296
- parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2297
- parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2298
- }
2299
2370
  }
2300
2371
  return parts;
2301
2372
  }
@@ -3694,12 +3765,12 @@ function toString(value) {
3694
3765
  }
3695
3766
  }
3696
3767
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
3697
- function normalizeString(str) {
3768
+ const normalizeString = memoize(function normalizeString(str) {
3698
3769
  return str
3699
3770
  .toLowerCase()
3700
3771
  .normalize("NFD")
3701
3772
  .replace(/[\u0300-\u036f]/g, "");
3702
- }
3773
+ });
3703
3774
  /**
3704
3775
  * Normalize a value.
3705
3776
  * If the cell value is a string, this will set it to lowercase and replacing accent letters with plain letters
@@ -4430,6 +4501,7 @@ exports.CommandResult = void 0;
4430
4501
  CommandResult[CommandResult["SplitWillOverwriteContent"] = 90] = "SplitWillOverwriteContent";
4431
4502
  CommandResult[CommandResult["NoSplitSeparatorInSelection"] = 91] = "NoSplitSeparatorInSelection";
4432
4503
  CommandResult[CommandResult["NoActiveSheet"] = 92] = "NoActiveSheet";
4504
+ CommandResult[CommandResult["NoChanges"] = 93] = "NoChanges";
4433
4505
  })(exports.CommandResult || (exports.CommandResult = {}));
4434
4506
 
4435
4507
  const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
@@ -5154,6 +5226,7 @@ const FilterMenuPopoverBuilder = {
5154
5226
  },
5155
5227
  };
5156
5228
 
5229
+ const macRegex = /Mac/i;
5157
5230
  /**
5158
5231
  * Return true if the event was triggered from
5159
5232
  * a child element.
@@ -5199,6 +5272,17 @@ function setElementScrollTop(el, scroll) {
5199
5272
  }
5200
5273
  function getOpenedMenus() {
5201
5274
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
5275
+ }
5276
+ function isMacOS() {
5277
+ return Boolean(macRegex.test(navigator.userAgent));
5278
+ }
5279
+ /**
5280
+ * @param {KeyboardEvent | MouseEvent} ev
5281
+ * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
5282
+ * On Mac, this is the "meta" or "command" key.
5283
+ */
5284
+ function isCtrlKey(ev) {
5285
+ return isMacOS() ? ev.metaKey : ev.ctrlKey;
5202
5286
  }
5203
5287
 
5204
5288
  /**
@@ -9585,6 +9669,9 @@ function arg(definition, description = "") {
9585
9669
  function makeArg(str, description) {
9586
9670
  let parts = str.match(ARG_REGEXP);
9587
9671
  let name = parts[1].trim();
9672
+ if (!name) {
9673
+ throw new Error(`Function argument definition is missing a name: '${str}'.`);
9674
+ }
9588
9675
  let types = [];
9589
9676
  let isOptional = false;
9590
9677
  let isRepeating = false;
@@ -12038,7 +12125,7 @@ const DATE = {
12038
12125
  if (_year < 1900) {
12039
12126
  _year += 1900;
12040
12127
  }
12041
- const jsDate = new Date(_year, _month - 1, _day);
12128
+ const jsDate = new DateTime(_year, _month - 1, _day);
12042
12129
  const result = jsDateToRoundNumber(jsDate);
12043
12130
  assert(() => result >= 0, _lt(`The function [[FUNCTION_NAME]] result must be greater than or equal 01/01/1900.`));
12044
12131
  return result;
@@ -12081,7 +12168,7 @@ const DATEDIF = {
12081
12168
  // See: https://support.microsoft.com/en-us/office/datedif-function-25dba1a4-2812-480b-84dd-8b32a451b35c
12082
12169
  let days = jsEndDate.getDate() - jsStartDate.getDate();
12083
12170
  if (days < 0) {
12084
- const monthBeforeEndMonth = new Date(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
12171
+ const monthBeforeEndMonth = new DateTime(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
12085
12172
  const daysInMonthBeforeEndMonth = getDaysInMonth(monthBeforeEndMonth);
12086
12173
  days = daysInMonthBeforeEndMonth - Math.abs(days);
12087
12174
  }
@@ -12090,7 +12177,7 @@ const DATEDIF = {
12090
12177
  if (areTwoDatesWithinOneYear(_startDate, _endDate)) {
12091
12178
  return getTimeDifferenceInWholeDays(jsStartDate, jsEndDate);
12092
12179
  }
12093
- const endDateWithinOneYear = new Date(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
12180
+ const endDateWithinOneYear = new DateTime(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
12094
12181
  let days = getTimeDifferenceInWholeDays(jsStartDate, endDateWithinOneYear);
12095
12182
  if (days < 0) {
12096
12183
  endDateWithinOneYear.setFullYear(jsStartDate.getFullYear() + 1);
@@ -12203,7 +12290,7 @@ const EOMONTH = {
12203
12290
  const _months = Math.trunc(toNumber(months));
12204
12291
  const yStart = _startDate.getFullYear();
12205
12292
  const mStart = _startDate.getMonth();
12206
- const jsDate = new Date(yStart, mStart + _months + 1, 0);
12293
+ const jsDate = new DateTime(yStart, mStart + _months + 1, 0);
12207
12294
  return jsDateToRoundNumber(jsDate);
12208
12295
  },
12209
12296
  isExported: true,
@@ -12240,17 +12327,17 @@ const ISOWEEKNUM = {
12240
12327
  // The first week of the year is the week that contains the first
12241
12328
  // Thursday of the year.
12242
12329
  let firstThursday = 1;
12243
- while (new Date(y, 0, firstThursday).getDay() !== 4) {
12330
+ while (new DateTime(y, 0, firstThursday).getDay() !== 4) {
12244
12331
  firstThursday += 1;
12245
12332
  }
12246
- const firstDayOfFirstWeek = new Date(y, 0, firstThursday - 3);
12333
+ const firstDayOfFirstWeek = new DateTime(y, 0, firstThursday - 3);
12247
12334
  // The last week of the year is the week that contains the last Thursday of
12248
12335
  // the year.
12249
12336
  let lastThursday = 31;
12250
- while (new Date(y, 11, lastThursday).getDay() !== 4) {
12337
+ while (new DateTime(y, 11, lastThursday).getDay() !== 4) {
12251
12338
  lastThursday -= 1;
12252
12339
  }
12253
- const lastDayOfLastWeek = new Date(y, 11, lastThursday + 3);
12340
+ const lastDayOfLastWeek = new DateTime(y, 11, lastThursday + 3);
12254
12341
  // B - If our date > lastDayOfLastWeek then it's in the weeks of the year after
12255
12342
  // If our date < firstDayOfFirstWeek then it's in the weeks of the year before
12256
12343
  let offsetYear;
@@ -12276,17 +12363,17 @@ const ISOWEEKNUM = {
12276
12363
  case 1:
12277
12364
  // firstDay is the 1st day of the 1st week of the year after
12278
12365
  // firstDay = lastDayOfLastWeek + 1 Day
12279
- firstDay = new Date(y, 11, lastThursday + 3 + 1);
12366
+ firstDay = new DateTime(y, 11, lastThursday + 3 + 1);
12280
12367
  break;
12281
12368
  case -1:
12282
12369
  // firstDay is the 1st day of the 1st week of the previous year.
12283
12370
  // The first week of the previous year is the week that contains the
12284
12371
  // first Thursday of the previous year.
12285
12372
  let firstThursdayPreviousYear = 1;
12286
- while (new Date(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
12373
+ while (new DateTime(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
12287
12374
  firstThursdayPreviousYear += 1;
12288
12375
  }
12289
- firstDay = new Date(y - 1, 0, firstThursdayPreviousYear - 3);
12376
+ firstDay = new DateTime(y - 1, 0, firstThursdayPreviousYear - 3);
12290
12377
  break;
12291
12378
  }
12292
12379
  const diff = (_date.getTime() - firstDay.getTime()) / MS_PER_DAY;
@@ -12421,8 +12508,8 @@ const NETWORKDAYS_INTL = {
12421
12508
  });
12422
12509
  }
12423
12510
  const invertDate = _startDate.getTime() > _endDate.getTime();
12424
- const stopDate = new Date((invertDate ? _startDate : _endDate).getTime());
12425
- let stepDate = new Date((invertDate ? _endDate : _startDate).getTime());
12511
+ const stopDate = DateTime.fromTimestamp((invertDate ? _startDate : _endDate).getTime());
12512
+ let stepDate = DateTime.fromTimestamp((invertDate ? _endDate : _startDate).getTime());
12426
12513
  const timeStopDate = stopDate.getTime();
12427
12514
  let timeStepDate = stepDate.getTime();
12428
12515
  let netWorkingDay = 0;
@@ -12446,8 +12533,7 @@ const NOW = {
12446
12533
  returns: ["DATE"],
12447
12534
  computeFormat: () => "m/d/yyyy hh:mm:ss",
12448
12535
  compute: function () {
12449
- let today = new Date();
12450
- today.setMilliseconds(0);
12536
+ let today = DateTime.now();
12451
12537
  const delta = today.getTime() - INITIAL_1900_DAY.getTime();
12452
12538
  const time = today.getHours() / 24 + today.getMinutes() / 1440 + today.getSeconds() / 86400;
12453
12539
  return Math.floor(delta / MS_PER_DAY) + time;
@@ -12517,8 +12603,8 @@ const TODAY = {
12517
12603
  returns: ["DATE"],
12518
12604
  computeFormat: () => "m/d/yyyy",
12519
12605
  compute: function () {
12520
- const today = new Date();
12521
- const jsDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());
12606
+ const today = DateTime.now();
12607
+ const jsDate = new DateTime(today.getFullYear(), today.getMonth(), today.getDate());
12522
12608
  return jsDateToRoundNumber(jsDate);
12523
12609
  },
12524
12610
  isExported: true,
@@ -12573,10 +12659,10 @@ const WEEKNUM = {
12573
12659
  }
12574
12660
  const y = _date.getFullYear();
12575
12661
  let dayStart = 1;
12576
- let startDayOfFirstWeek = new Date(y, 0, dayStart);
12662
+ let startDayOfFirstWeek = new DateTime(y, 0, dayStart);
12577
12663
  while (startDayOfFirstWeek.getDay() !== startDayOfWeek) {
12578
12664
  dayStart += 1;
12579
- startDayOfFirstWeek = new Date(y, 0, dayStart);
12665
+ startDayOfFirstWeek = new DateTime(y, 0, dayStart);
12580
12666
  }
12581
12667
  const dif = (_date.getTime() - startDayOfFirstWeek.getTime()) / MS_PER_DAY;
12582
12668
  if (dif < 0) {
@@ -12630,7 +12716,7 @@ const WORKDAY_INTL = {
12630
12716
  timesHoliday.add(holiday.getTime());
12631
12717
  });
12632
12718
  }
12633
- let stepDate = new Date(_startDate.getTime());
12719
+ let stepDate = DateTime.fromTimestamp(_startDate.getTime());
12634
12720
  let timeStepDate = stepDate.getTime();
12635
12721
  const unitDay = Math.sign(_numDays);
12636
12722
  let stepDay = Math.abs(_numDays);
@@ -12692,7 +12778,7 @@ const MONTH_START = {
12692
12778
  const _startDate = toJsDate(date);
12693
12779
  const yStart = _startDate.getFullYear();
12694
12780
  const mStart = _startDate.getMonth();
12695
- const jsDate = new Date(yStart, mStart, 1);
12781
+ const jsDate = new DateTime(yStart, mStart, 1);
12696
12782
  return jsDateToRoundNumber(jsDate);
12697
12783
  },
12698
12784
  };
@@ -12730,7 +12816,7 @@ const QUARTER_START = {
12730
12816
  compute: function (date) {
12731
12817
  const quarter = QUARTER.compute(date);
12732
12818
  const year = YEAR.compute(date);
12733
- const jsDate = new Date(year, (quarter - 1) * 3, 1);
12819
+ const jsDate = new DateTime(year, (quarter - 1) * 3, 1);
12734
12820
  return jsDateToRoundNumber(jsDate);
12735
12821
  },
12736
12822
  };
@@ -12745,7 +12831,7 @@ const QUARTER_END = {
12745
12831
  compute: function (date) {
12746
12832
  const quarter = QUARTER.compute(date);
12747
12833
  const year = YEAR.compute(date);
12748
- const jsDate = new Date(year, quarter * 3, 0);
12834
+ const jsDate = new DateTime(year, quarter * 3, 0);
12749
12835
  return jsDateToRoundNumber(jsDate);
12750
12836
  },
12751
12837
  };
@@ -12759,7 +12845,7 @@ const YEAR_START = {
12759
12845
  computeFormat: () => "m/d/yyyy",
12760
12846
  compute: function (date) {
12761
12847
  const year = YEAR.compute(date);
12762
- const jsDate = new Date(year, 0, 1);
12848
+ const jsDate = new DateTime(year, 0, 1);
12763
12849
  return jsDateToRoundNumber(jsDate);
12764
12850
  },
12765
12851
  };
@@ -12773,7 +12859,7 @@ const YEAR_END = {
12773
12859
  computeFormat: () => "m/d/yyyy",
12774
12860
  compute: function (date) {
12775
12861
  const year = YEAR.compute(date);
12776
- const jsDate = new Date(year + 1, 0, 0);
12862
+ const jsDate = new DateTime(year + 1, 0, 0);
12777
12863
  return jsDateToRoundNumber(jsDate);
12778
12864
  },
12779
12865
  };
@@ -12821,8 +12907,8 @@ const DEFAULT_DELTA_ARG = 0;
12821
12907
  const DELTA = {
12822
12908
  description: _lt("Compare two numeric values, returning 1 if they're equal."),
12823
12909
  args: [
12824
- arg(" (number)", _lt("The first number to compare.")),
12825
- arg(` (number, default=${DEFAULT_DELTA_ARG})`, _lt("The second number to compare.")),
12910
+ arg("number1 (number)", _lt("The first number to compare.")),
12911
+ arg(`number2 (number, default=${DEFAULT_DELTA_ARG})`, _lt("The second number to compare.")),
12826
12912
  ],
12827
12913
  returns: ["NUMBER"],
12828
12914
  compute: function (number1, number2 = DEFAULT_DELTA_ARG) {
@@ -12908,7 +12994,7 @@ function assertDeprecationFactorStrictlyPositive(factor) {
12908
12994
  function assertSettlementLessThanOneYearBeforeMaturity(settlement, maturity) {
12909
12995
  const startDate = toJsDate(settlement);
12910
12996
  const endDate = toJsDate(maturity);
12911
- const startDatePlusOneYear = new Date(startDate);
12997
+ const startDatePlusOneYear = toJsDate(settlement);
12912
12998
  startDatePlusOneYear.setFullYear(startDate.getFullYear() + 1);
12913
12999
  assert(() => endDate.getTime() <= startDatePlusOneYear.getTime(), _lt("The settlement date (%s) must at most one year after the maturity date (%s).", settlement.toString(), maturity.toString()));
12914
13000
  }
@@ -13043,7 +13129,7 @@ const AMORLINC = {
13043
13129
  arg("salvage (number)", _lt("The value of the asset at the end of depreciation.")),
13044
13130
  arg("period (number)", _lt("The single period within life for which to calculate depreciation.")),
13045
13131
  arg("rate (number)", _lt("The deprecation rate.")),
13046
- arg(" (number, optional)", _lt("An indicator of what day count method to use.")),
13132
+ arg("day_count_convention (number, optional)", _lt("An indicator of what day count method to use.")),
13047
13133
  ],
13048
13134
  returns: ["NUMBER"],
13049
13135
  compute: function (cost, purchaseDate, firstPeriodEnd, salvage, period, rate, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {
@@ -16026,8 +16112,8 @@ const MID = {
16026
16112
  description: _lt("A segment of a string."),
16027
16113
  args: [
16028
16114
  arg("text (string)", _lt("The string to extract a segment from.")),
16029
- arg(" (number)", _lt("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
16030
- arg(" (number)", _lt("The length of the segment to extract.")),
16115
+ arg("starting_at (number)", _lt("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
16116
+ arg("extract_length (number)", _lt("The length of the segment to extract.")),
16031
16117
  ],
16032
16118
  returns: ["STRING"],
16033
16119
  compute: function (text, starting_at, extract_length) {
@@ -17696,10 +17782,10 @@ const arrowMap = {
17696
17782
  function updateSelectionWithArrowKeys(ev, selection) {
17697
17783
  const direction = arrowMap[ev.key];
17698
17784
  if (ev.shiftKey) {
17699
- selection.resizeAnchorZone(direction, ev.ctrlKey ? "end" : 1);
17785
+ selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
17700
17786
  }
17701
17787
  else {
17702
- selection.moveAnchorCell(direction, ev.ctrlKey ? "end" : 1);
17788
+ selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
17703
17789
  }
17704
17790
  }
17705
17791
 
@@ -22396,7 +22482,10 @@ class GridOverlay extends owl.Component {
22396
22482
  return;
22397
22483
  }
22398
22484
  const [col, row] = this.getCartesianCoordinates(ev);
22399
- this.props.onCellClicked(col, row, { shiftKey: ev.shiftKey, ctrlKey: ev.ctrlKey });
22485
+ this.props.onCellClicked(col, row, {
22486
+ expandZone: ev.shiftKey,
22487
+ addZone: isCtrlKey(ev),
22488
+ });
22400
22489
  }
22401
22490
  onDoubleClick(ev) {
22402
22491
  const [col, row] = this.getCartesianCoordinates(ev);
@@ -22469,6 +22558,7 @@ class AbstractResizer extends owl.Component {
22469
22558
  draggerShadowThickness: 0,
22470
22559
  delta: 0,
22471
22560
  base: 0,
22561
+ position: "before",
22472
22562
  });
22473
22563
  _computeHandleDisplay(ev) {
22474
22564
  const position = this._getEvOffset(ev);
@@ -22593,11 +22683,13 @@ class AbstractResizer extends owl.Component {
22593
22683
  this.state.draggerLinePosition = dimensions.start;
22594
22684
  this.state.draggerShadowPosition = dimensions.start;
22595
22685
  this.state.base = elementIndex;
22686
+ this.state.position = "before";
22596
22687
  }
22597
22688
  else if (this._getSelectedZoneEnd() < elementIndex) {
22598
22689
  this.state.draggerLinePosition = dimensions.end;
22599
22690
  this.state.draggerShadowPosition = dimensions.end - this.state.draggerShadowThickness;
22600
- this.state.base = elementIndex + 1;
22691
+ this.state.base = elementIndex;
22692
+ this.state.position = "after";
22601
22693
  }
22602
22694
  else {
22603
22695
  this.state.draggerLinePosition = startDimensions.start;
@@ -22621,7 +22713,7 @@ class AbstractResizer extends owl.Component {
22621
22713
  this._increaseSelection(index);
22622
22714
  }
22623
22715
  else {
22624
- this._selectElement(index, ev.ctrlKey);
22716
+ this._selectElement(index, isCtrlKey(ev));
22625
22717
  }
22626
22718
  this.lastSelectedElementIndex = index;
22627
22719
  const mouseMoveSelect = (col, row) => {
@@ -22634,7 +22726,7 @@ class AbstractResizer extends owl.Component {
22634
22726
  const mouseUpSelect = () => {
22635
22727
  this.state.isSelecting = false;
22636
22728
  this.lastSelectedElementIndex = null;
22637
- this.env.model.dispatch(ev.ctrlKey ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
22729
+ this.env.model.dispatch(isCtrlKey(ev) ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
22638
22730
  this._computeGrabDisplay(ev);
22639
22731
  };
22640
22732
  dragAndDropBeyondTheViewport(this.env, mouseMoveSelect, mouseUpSelect);
@@ -22780,13 +22872,14 @@ class ColResizer extends AbstractResizer {
22780
22872
  dimension: "COL",
22781
22873
  base: this.state.base,
22782
22874
  elements,
22875
+ position: this.state.position,
22783
22876
  });
22784
22877
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
22785
22878
  this.env.raiseError(MergeErrorMessage);
22786
22879
  }
22787
22880
  }
22788
- _selectElement(index, ctrlKey) {
22789
- this.env.model.selection.selectColumn(index, ctrlKey ? "newAnchor" : "overrideSelection");
22881
+ _selectElement(index, addDistinctHeader) {
22882
+ this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
22790
22883
  }
22791
22884
  _increaseSelection(index) {
22792
22885
  this.env.model.selection.selectColumn(index, "updateAnchor");
@@ -22953,13 +23046,14 @@ class RowResizer extends AbstractResizer {
22953
23046
  dimension: "ROW",
22954
23047
  base: this.state.base,
22955
23048
  elements,
23049
+ position: this.state.position,
22956
23050
  });
22957
23051
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
22958
23052
  this.env.raiseError(MergeErrorMessage);
22959
23053
  }
22960
23054
  }
22961
- _selectElement(index, ctrlKey) {
22962
- this.env.model.selection.selectRow(index, ctrlKey ? "newAnchor" : "overrideSelection");
23055
+ _selectElement(index, addDistinctHeader) {
23056
+ this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
22963
23057
  }
22964
23058
  _increaseSelection(index) {
22965
23059
  this.env.model.selection.selectRow(index, "updateAnchor");
@@ -23060,8 +23154,8 @@ function useWheelHandler(handler) {
23060
23154
  return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT);
23061
23155
  }
23062
23156
  const onMouseWheel = (ev) => {
23063
- const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode);
23064
- const deltaY = normalize(ev.shiftKey ? ev.deltaX : ev.deltaY, ev.deltaMode);
23157
+ const deltaX = normalize(ev.shiftKey && !isMacOS() ? ev.deltaY : ev.deltaX, ev.deltaMode);
23158
+ const deltaY = normalize(ev.shiftKey && !isMacOS() ? ev.deltaX : ev.deltaY, ev.deltaMode);
23065
23159
  handler(deltaX, deltaY);
23066
23160
  };
23067
23161
  return onMouseWheel;
@@ -23763,8 +23857,8 @@ class Grid extends owl.Component {
23763
23857
  // ---------------------------------------------------------------------------
23764
23858
  // Zone selection with mouse
23765
23859
  // ---------------------------------------------------------------------------
23766
- onCellClicked(col, row, { ctrlKey, shiftKey }) {
23767
- if (ctrlKey) {
23860
+ onCellClicked(col, row, { addZone, expandZone }) {
23861
+ if (addZone) {
23768
23862
  this.env.model.dispatch("PREPARE_SELECTION_INPUT_EXPANSION");
23769
23863
  }
23770
23864
  if (this.env.model.getters.hasOpenedPopover()) {
@@ -23773,10 +23867,10 @@ class Grid extends owl.Component {
23773
23867
  if (this.env.model.getters.getEditionMode() === "editing") {
23774
23868
  this.env.model.dispatch("STOP_EDITION");
23775
23869
  }
23776
- if (shiftKey) {
23870
+ if (expandZone) {
23777
23871
  this.env.model.selection.setAnchorCorner(col, row);
23778
23872
  }
23779
- else if (ctrlKey) {
23873
+ else if (addZone) {
23780
23874
  this.env.model.selection.addCellToSelection(col, row);
23781
23875
  }
23782
23876
  else {
@@ -23838,9 +23932,7 @@ class Grid extends owl.Component {
23838
23932
  return;
23839
23933
  }
23840
23934
  let keyDownString = "";
23841
- if (ev.ctrlKey)
23842
- keyDownString += "CTRL+";
23843
- if (ev.metaKey)
23935
+ if (isCtrlKey(ev))
23844
23936
  keyDownString += "CTRL+";
23845
23937
  if (ev.altKey)
23846
23938
  keyDownString += "ALT+";
@@ -25748,7 +25840,7 @@ const machine = {
25748
25840
  SPACE: goTo(State.RightRef),
25749
25841
  NUMBER: goTo(State.Found),
25750
25842
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
25751
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
25843
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
25752
25844
  },
25753
25845
  [State.RightColumnRef]: {
25754
25846
  SPACE: goTo(State.RightColumnRef),
@@ -25759,6 +25851,7 @@ const machine = {
25759
25851
  SPACE: goTo(State.RightRowRef),
25760
25852
  NUMBER: goTo(State.Found),
25761
25853
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
25854
+ SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
25762
25855
  },
25763
25856
  [State.Found]: {},
25764
25857
  };
@@ -28995,6 +29088,14 @@ class BordersPlugin extends CorePlugin {
28995
29088
  // ---------------------------------------------------------------------------
28996
29089
  // Command Handling
28997
29090
  // ---------------------------------------------------------------------------
29091
+ allowDispatch(cmd) {
29092
+ switch (cmd.type) {
29093
+ case "SET_BORDER":
29094
+ return this.checkBordersUnchanged(cmd);
29095
+ default:
29096
+ return 0 /* CommandResult.Success */;
29097
+ }
29098
+ }
28998
29099
  handle(cmd) {
28999
29100
  switch (cmd.type) {
29000
29101
  case "ADD_MERGE":
@@ -29414,6 +29515,14 @@ class BordersPlugin extends CorePlugin {
29414
29515
  this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersTopLeft.right);
29415
29516
  }
29416
29517
  }
29518
+ checkBordersUnchanged(cmd) {
29519
+ const currentBorder = this.getCellBorder(cmd);
29520
+ const areAllNewBordersUndefined = !cmd.border?.bottom && !cmd.border?.left && !cmd.border?.right && !cmd.border?.top;
29521
+ if ((!currentBorder && areAllNewBordersUndefined) || deepEquals(currentBorder, cmd.border)) {
29522
+ return 93 /* CommandResult.NoChanges */;
29523
+ }
29524
+ return 0 /* CommandResult.Success */;
29525
+ }
29417
29526
  // ---------------------------------------------------------------------------
29418
29527
  // Import/Export
29419
29528
  // ---------------------------------------------------------------------------
@@ -29450,7 +29559,7 @@ class BordersPlugin extends CorePlugin {
29450
29559
  */
29451
29560
  function getBorderId(border) {
29452
29561
  for (let [key, value] of Object.entries(borders)) {
29453
- if (stringify(value) === stringify(border)) {
29562
+ if (deepEquals(value, border)) {
29454
29563
  return parseInt(key, 10);
29455
29564
  }
29456
29565
  }
@@ -29521,8 +29630,9 @@ class CellPlugin extends CorePlugin {
29521
29630
  allowDispatch(cmd) {
29522
29631
  switch (cmd.type) {
29523
29632
  case "UPDATE_CELL":
29633
+ return this.checkCellOutOfSheet(cmd);
29524
29634
  case "CLEAR_CELL":
29525
- return this.checkCellOutOfSheet(cmd.sheetId, cmd.col, cmd.row);
29635
+ return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
29526
29636
  default:
29527
29637
  return 0 /* CommandResult.Success */;
29528
29638
  }
@@ -29681,14 +29791,9 @@ class CellPlugin extends CorePlugin {
29681
29791
  */
29682
29792
  getCellById(cellId) {
29683
29793
  // this must be as fast as possible
29684
- for (const sheetId in this.cells) {
29685
- const sheet = this.cells[sheetId];
29686
- const cell = sheet[cellId];
29687
- if (cell) {
29688
- return cell;
29689
- }
29690
- }
29691
- return undefined;
29794
+ const position = this.getters.getCellPosition(cellId);
29795
+ const sheet = this.cells[position.sheetId];
29796
+ return sheet[cellId];
29692
29797
  }
29693
29798
  /*
29694
29799
  * Reconstructs the original formula string based on a normalized form and its dependencies
@@ -29925,13 +30030,23 @@ class CellPlugin extends CorePlugin {
29925
30030
  dependencies: [],
29926
30031
  };
29927
30032
  }
29928
- checkCellOutOfSheet(sheetId, col, row) {
30033
+ checkCellOutOfSheet(cmd) {
30034
+ const { sheetId, col, row } = cmd;
29929
30035
  const sheet = this.getters.tryGetSheet(sheetId);
29930
30036
  if (!sheet)
29931
30037
  return 27 /* CommandResult.InvalidSheetId */;
29932
30038
  const sheetZone = this.getters.getSheetZone(sheetId);
29933
30039
  return isInside(col, row, sheetZone) ? 0 /* CommandResult.Success */ : 18 /* CommandResult.TargetOutOfSheet */;
29934
30040
  }
30041
+ checkUselessClearCell(cmd) {
30042
+ const cell = this.getters.getCell(cmd);
30043
+ if (!cell)
30044
+ return 93 /* CommandResult.NoChanges */;
30045
+ if (!cell.content && !cell.style && !cell.format) {
30046
+ return 93 /* CommandResult.NoChanges */;
30047
+ }
30048
+ return 0 /* CommandResult.Success */;
30049
+ }
29935
30050
  }
29936
30051
 
29937
30052
  class ChartPlugin extends CorePlugin {
@@ -32412,7 +32527,7 @@ class SheetPlugin extends CorePlugin {
32412
32527
  const elements = cmd.dimension === "COL"
32413
32528
  ? this.getNumberCols(cmd.sheetId)
32414
32529
  : this.getNumberRows(cmd.sheetId);
32415
- if (cmd.base < 0 || cmd.base > elements) {
32530
+ if (cmd.base < 0 || cmd.base >= elements) {
32416
32531
  return 86 /* CommandResult.InvalidHeaderIndex */;
32417
32532
  }
32418
32533
  else if (cmd.quantity <= 0) {
@@ -33034,10 +33149,13 @@ class SheetPlugin extends CorePlugin {
33034
33149
  // begin with the end.
33035
33150
  rows.sort((a, b) => b - a);
33036
33151
  for (let group of groupConsecutive(rows)) {
33152
+ // indexes are sorted in the descending order
33153
+ const from = group[group.length - 1];
33154
+ const to = group[0];
33037
33155
  // Move the cells.
33038
- this.moveCellOnRowsDeletion(sheet, group[group.length - 1], group[0]);
33039
- // Effectively delete the element and recompute the left-right/top-bottom.
33040
- group.map((row) => this.updateRowsStructureOnDeletion(row, sheet));
33156
+ this.moveCellOnRowsDeletion(sheet, from, to);
33157
+ // Effectively delete the rows
33158
+ this.updateRowsStructureOnDeletion(sheet, from, to);
33041
33159
  }
33042
33160
  const count = rows.filter((row) => row < sheet.panes.ySplit).length;
33043
33161
  if (count) {
@@ -33059,8 +33177,6 @@ class SheetPlugin extends CorePlugin {
33059
33177
  this.addEmptyRows(sheet, quantity);
33060
33178
  // Move the cells.
33061
33179
  this.moveCellsOnAddition(sheet, index, quantity, "rows");
33062
- // Recompute the left-right/top-bottom.
33063
- this.updateRowsStructureOnAddition(sheet, row, quantity);
33064
33180
  if (index < sheet.panes.ySplit) {
33065
33181
  this.setPaneDivisions(sheet.id, sheet.panes.ySplit + quantity, "ROW");
33066
33182
  }
@@ -33161,35 +33277,20 @@ class SheetPlugin extends CorePlugin {
33161
33277
  }
33162
33278
  }
33163
33279
  }
33164
- updateRowsStructureOnDeletion(index, sheet) {
33280
+ updateRowsStructureOnDeletion(sheet, deleteFromRow, deleteToRow) {
33165
33281
  const rows = [];
33166
- const cellsQueue = sheet.rows.map((row) => row.cells);
33282
+ const cellsQueue = sheet.rows.map((row) => row.cells).reverse();
33167
33283
  for (let i in sheet.rows) {
33168
- if (Number(i) === index) {
33284
+ const row = Number(i);
33285
+ if (row >= deleteFromRow && row <= deleteToRow) {
33169
33286
  continue;
33170
33287
  }
33171
33288
  rows.push({
33172
- cells: cellsQueue.shift(),
33289
+ cells: cellsQueue.pop(),
33173
33290
  });
33174
33291
  }
33175
33292
  this.history.update("sheets", sheet.id, "rows", rows);
33176
33293
  }
33177
- /**
33178
- * Update the rows of the sheet after an addition:
33179
- * - Rename the rows
33180
- *
33181
- * @param sheet Sheet on which the deletion occurs
33182
- * @param addedRow Index of the added row
33183
- * @param rowsToAdd Number of the rows to add
33184
- */
33185
- updateRowsStructureOnAddition(sheet, addedRow, rowsToAdd) {
33186
- const rows = [];
33187
- const cellsQueue = sheet.rows.map((row) => row.cells);
33188
- sheet.rows.forEach(() => rows.push({
33189
- cells: cellsQueue.shift(),
33190
- }));
33191
- this.history.update("sheets", sheet.id, "rows", rows);
33192
- }
33193
33294
  /**
33194
33295
  * Add empty rows at the end of the rows
33195
33296
  *
@@ -33459,13 +33560,18 @@ class EvaluationPlugin extends UIPlugin {
33459
33560
  // Getters
33460
33561
  // ---------------------------------------------------------------------------
33461
33562
  evaluateFormula(formulaString, sheetId = this.getters.getActiveSheetId()) {
33462
- const compiledFormula = compile(formulaString);
33463
- const params = this.getCompilationParameters((cell) => this.getEvaluatedCell(this.getters.getCellPosition(cell.id)));
33464
- const ranges = [];
33465
- for (let xc of compiledFormula.dependencies) {
33466
- ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
33563
+ try {
33564
+ const compiledFormula = compile(formulaString);
33565
+ const params = this.getCompilationParameters((cell) => this.getEvaluatedCell(this.getters.getCellPosition(cell.id)));
33566
+ const ranges = [];
33567
+ for (let xc of compiledFormula.dependencies) {
33568
+ ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
33569
+ }
33570
+ return compiledFormula.execute(ranges, ...params).value;
33571
+ }
33572
+ catch (error) {
33573
+ return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
33467
33574
  }
33468
- return compiledFormula.execute(ranges, ...params).value;
33469
33575
  }
33470
33576
  /**
33471
33577
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -33640,6 +33746,7 @@ class EvaluationPlugin extends UIPlugin {
33640
33746
  }
33641
33747
  return evaluatedCell;
33642
33748
  };
33749
+ const rangeCache = {};
33643
33750
  /**
33644
33751
  * Return the values of the cell(s) used in reference, but always in the format of a range even
33645
33752
  * if a single cell is referenced. It is a list of col values. This is useful for the formulas that describe parameters as
@@ -33656,21 +33763,26 @@ class EvaluationPlugin extends UIPlugin {
33656
33763
  // Performance issue: Avoid fetching data on positions that are out of the spreadsheet
33657
33764
  // e.g. A1:ZZZ9999 in a sheet with 10 cols and 10 rows should ignore everything past J10 and return a 10x10 array
33658
33765
  const sheetZone = getters.getSheetZone(sheetId);
33659
- const result = [];
33660
33766
  const zone = intersection(range.zone, sheetZone);
33661
33767
  if (!zone) {
33662
- result.push([]);
33663
- return result;
33768
+ return [[]];
33769
+ }
33770
+ const { top, left, bottom, right } = zone;
33771
+ const cacheKey = `${sheetId}-${top}-${left}-${bottom}-${right}`;
33772
+ if (cacheKey in rangeCache) {
33773
+ return rangeCache[cacheKey];
33664
33774
  }
33775
+ const result = new Array(right - left + 1);
33665
33776
  // Performance issue: nested loop is faster than a map here
33666
- for (let col = zone.left; col <= zone.right; col++) {
33667
- const rowValues = [];
33668
- for (let row = zone.top; row <= zone.bottom; row++) {
33777
+ for (let col = left; col <= right; col++) {
33778
+ const rowValues = new Array(bottom - top + 1);
33779
+ for (let row = top; row <= bottom; row++) {
33669
33780
  const cell = evalContext.getters.getCell({ sheetId: range.sheetId, col, row });
33670
- rowValues.push(cell ? getEvaluatedCell(cell) : undefined);
33781
+ rowValues[row - top] = cell ? getEvaluatedCell(cell) : undefined;
33671
33782
  }
33672
- result.push(rowValues);
33783
+ result[col - left] = rowValues;
33673
33784
  }
33785
+ rangeCache[cacheKey] = result;
33674
33786
  return result;
33675
33787
  }
33676
33788
  /**
@@ -33884,39 +33996,34 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
33884
33996
  this.computedIcons[sheetId] = {};
33885
33997
  const computedStyle = this.computedStyles[sheetId];
33886
33998
  for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
33887
- try {
33888
- switch (cf.rule.type) {
33889
- case "ColorScaleRule":
33890
- for (let range of cf.ranges) {
33891
- this.applyColorScale(range, cf.rule);
33892
- }
33893
- break;
33894
- case "IconSetRule":
33895
- for (let range of cf.ranges) {
33896
- this.applyIcon(range, cf.rule);
33897
- }
33898
- break;
33899
- default:
33900
- for (let ref of cf.ranges) {
33901
- const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
33902
- for (let row = zone.top; row <= zone.bottom; row++) {
33903
- for (let col = zone.left; col <= zone.right; col++) {
33904
- const pr = this.rulePredicate[cf.rule.type];
33905
- let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
33906
- if (pr && pr(cell, cf.rule)) {
33907
- if (!computedStyle[col])
33908
- computedStyle[col] = [];
33909
- // we must combine all the properties of all the CF rules applied to the given cell
33910
- computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
33911
- }
33999
+ switch (cf.rule.type) {
34000
+ case "ColorScaleRule":
34001
+ for (let range of cf.ranges) {
34002
+ this.applyColorScale(range, cf.rule);
34003
+ }
34004
+ break;
34005
+ case "IconSetRule":
34006
+ for (let range of cf.ranges) {
34007
+ this.applyIcon(range, cf.rule);
34008
+ }
34009
+ break;
34010
+ default:
34011
+ for (let ref of cf.ranges) {
34012
+ const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
34013
+ for (let row = zone.top; row <= zone.bottom; row++) {
34014
+ for (let col = zone.left; col <= zone.right; col++) {
34015
+ const pr = this.rulePredicate[cf.rule.type];
34016
+ let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
34017
+ if (pr && pr(cell, cf.rule)) {
34018
+ if (!computedStyle[col])
34019
+ computedStyle[col] = [];
34020
+ // we must combine all the properties of all the CF rules applied to the given cell
34021
+ computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
33912
34022
  }
33913
34023
  }
33914
34024
  }
33915
- break;
33916
- }
33917
- }
33918
- catch (_) {
33919
- // we don't care about the errors within the evaluation of a rule
34025
+ }
34026
+ break;
33920
34027
  }
33921
34028
  }
33922
34029
  }
@@ -33941,7 +34048,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
33941
34048
  return percentile(rangeValues, Number(threshold.value) / 100, true);
33942
34049
  case "formula":
33943
34050
  const value = threshold.value && this.getters.evaluateFormula(threshold.value);
33944
- return !(value instanceof Promise) ? value : null;
34051
+ return typeof value === "number" ? value : null;
33945
34052
  default:
33946
34053
  return null;
33947
34054
  }
@@ -35590,19 +35697,21 @@ class AutofillPlugin extends UIPlugin {
35590
35697
  let col = zone.left;
35591
35698
  let row = zone.bottom;
35592
35699
  if (col > 0) {
35593
- let left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
35594
- while (left.type !== CellValueType.empty) {
35700
+ let leftPosition = { sheetId, col: col - 1, row };
35701
+ while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
35702
+ this.getters.getCell(leftPosition)?.content) {
35595
35703
  row += 1;
35596
- left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
35704
+ leftPosition = { sheetId, col: col - 1, row };
35597
35705
  }
35598
35706
  }
35599
35707
  if (row === zone.bottom) {
35600
35708
  col = zone.right;
35601
35709
  if (col <= this.getters.getNumberCols(sheetId)) {
35602
- let right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
35603
- while (right.type !== CellValueType.empty) {
35710
+ let rightPosition = { sheetId, col: col + 1, row };
35711
+ while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
35712
+ this.getters.getCell(rightPosition)?.content) {
35604
35713
  row += 1;
35605
- right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
35714
+ rightPosition = { sheetId, col: col + 1, row };
35606
35715
  }
35607
35716
  }
35608
35717
  }
@@ -40550,6 +40659,10 @@ function setXcToReferenceType(xc, referenceType) {
40550
40659
  return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
40551
40660
  case "colrow":
40552
40661
  indexOfNumber = xc.search(/[0-9]/);
40662
+ if (indexOfNumber === -1 || indexOfNumber === 0) {
40663
+ // no row number (eg. A) or no column (eg. 1)
40664
+ return "$" + xc;
40665
+ }
40553
40666
  xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
40554
40667
  return "$" + xc;
40555
40668
  case "none":
@@ -41731,7 +41844,7 @@ class GridSelectionPlugin extends UIPlugin {
41731
41844
  sheetId: cmd.sheetId,
41732
41845
  base: cmd.base,
41733
41846
  quantity: thickness,
41734
- position: "before",
41847
+ position: cmd.position,
41735
41848
  });
41736
41849
  const isCol = cmd.dimension === "COL";
41737
41850
  const start = cmd.elements[0];
@@ -41748,12 +41861,13 @@ class GridSelectionPlugin extends UIPlugin {
41748
41861
  },
41749
41862
  ];
41750
41863
  const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
41864
+ const base = isBasedBefore ? cmd.base : cmd.base + 1;
41751
41865
  const pasteTarget = [
41752
41866
  {
41753
- left: isCol ? cmd.base : 0,
41754
- right: isCol ? cmd.base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
41755
- top: !isCol ? cmd.base : 0,
41756
- bottom: !isCol ? cmd.base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
41867
+ left: isCol ? base : 0,
41868
+ right: isCol ? base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
41869
+ top: !isCol ? base : 0,
41870
+ bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
41757
41871
  },
41758
41872
  ];
41759
41873
  state.paste(pasteTarget, { selectTarget: true });
@@ -41790,6 +41904,11 @@ class GridSelectionPlugin extends UIPlugin {
41790
41904
  doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) {
41791
41905
  return 2 /* CommandResult.WillRemoveExistingMerge */;
41792
41906
  }
41907
+ const headers = [cmd.base, ...cmd.elements];
41908
+ const maxHeaderValue = isCol ? this.getters.getNumberCols(id) : this.getters.getNumberRows(id);
41909
+ if (headers.some((h) => h < 0 || h >= maxHeaderValue)) {
41910
+ return 86 /* CommandResult.InvalidHeaderIndex */;
41911
+ }
41793
41912
  return 0 /* CommandResult.Success */;
41794
41913
  }
41795
41914
  //-------------------------------------------
@@ -44071,7 +44190,7 @@ class Spreadsheet extends owl.Component {
44071
44190
  }
44072
44191
  onKeydown(ev) {
44073
44192
  let keyDownString = "";
44074
- if (ev.ctrlKey || ev.metaKey) {
44193
+ if (isCtrlKey(ev)) {
44075
44194
  keyDownString += "CTRL+";
44076
44195
  }
44077
44196
  keyDownString += ev.key.toUpperCase();
@@ -44942,9 +45061,9 @@ function revertChanges(revisions) {
44942
45061
  * Apply the changes of the given HistoryChange to the state
44943
45062
  */
44944
45063
  function applyChange(change, target) {
44945
- let val = change.root;
44946
- let key = change.path[change.path.length - 1];
44947
- for (let pathIndex = 0; pathIndex < change.path.slice(0, -1).length; pathIndex++) {
45064
+ let val = change.path[0];
45065
+ const key = change.path[change.path.length - 1];
45066
+ for (let pathIndex = 1; pathIndex < change.path.slice(0, -1).length; pathIndex++) {
44948
45067
  const p = change.path[pathIndex];
44949
45068
  if (val[p] === undefined) {
44950
45069
  const nextPath = change.path[pathIndex + 1];
@@ -45611,13 +45730,14 @@ class StateObserver {
45611
45730
  }
45612
45731
  addChange(...args) {
45613
45732
  const val = args.pop();
45614
- const [root, ...path] = args;
45733
+ const root = args[0];
45615
45734
  let value = root;
45616
- let key = path[path.length - 1];
45617
- for (let pathIndex = 0; pathIndex <= path.length - 2; pathIndex++) {
45618
- const p = path[pathIndex];
45735
+ let key = args[args.length - 1];
45736
+ const pathLength = args.length - 2;
45737
+ for (let pathIndex = 1; pathIndex <= pathLength; pathIndex++) {
45738
+ const p = args[pathIndex];
45619
45739
  if (value[p] === undefined) {
45620
- const nextPath = path[pathIndex + 1];
45740
+ const nextPath = args[pathIndex + 1];
45621
45741
  value[p] = createEmptyStructure(nextPath);
45622
45742
  }
45623
45743
  value = value[p];
@@ -45626,8 +45746,7 @@ class StateObserver {
45626
45746
  return;
45627
45747
  }
45628
45748
  this.changes.push({
45629
- root,
45630
- path,
45749
+ path: args,
45631
45750
  before: value[key],
45632
45751
  after: val,
45633
45752
  });
@@ -47477,19 +47596,22 @@ class Model extends EventBus {
47477
47596
  * Check if the given command is allowed by all the plugins and the history.
47478
47597
  */
47479
47598
  checkDispatchAllowed(command) {
47480
- if (isCoreCommand(command)) {
47481
- return this.checkDispatchAllowedCoreCommand(command);
47599
+ const results = isCoreCommand(command)
47600
+ ? this.checkDispatchAllowedCoreCommand(command)
47601
+ : this.checkDispatchAllowedLocalCommand(command);
47602
+ if (results.some((r) => r !== 0 /* CommandResult.Success */)) {
47603
+ return new DispatchResult(results.flat());
47482
47604
  }
47483
- return this.checkDispatchAllowedLocalCommand(command);
47605
+ return DispatchResult.Success;
47484
47606
  }
47485
47607
  checkDispatchAllowedCoreCommand(command) {
47486
47608
  const results = this.corePlugins.map((handler) => handler.allowDispatch(command));
47487
47609
  results.push(this.range.allowDispatch(command));
47488
- return new DispatchResult(results.flat());
47610
+ return results;
47489
47611
  }
47490
47612
  checkDispatchAllowedLocalCommand(command) {
47491
47613
  const results = this.uiHandlers.map((handler) => handler.allowDispatch(command));
47492
- return new DispatchResult(results.flat());
47614
+ return results;
47493
47615
  }
47494
47616
  finalize() {
47495
47617
  this.status = 3 /* Status.Finalizing */;
@@ -47805,6 +47927,6 @@ exports.setTranslationMethod = setTranslationMethod;
47805
47927
  exports.tokenize = tokenize;
47806
47928
 
47807
47929
 
47808
- __info__.version = '16.3.17';
47809
- __info__.date = '2023-11-16T11:59:29.130Z';
47810
- __info__.hash = '4049460';
47930
+ __info__.version = '16.3.22';
47931
+ __info__.date = '2024-01-17T10:24:07.652Z';
47932
+ __info__.hash = '2c1b136';