@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.
@@ -298,15 +298,80 @@ const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 18, 24, 36];
298
298
  // -----------------------------------------------------------------------------
299
299
  // Date Type
300
300
  // -----------------------------------------------------------------------------
301
+ /**
302
+ * A DateTime object that can be used to manipulate spreadsheet dates.
303
+ * Conceptually, a spreadsheet date is simply a number with a date format,
304
+ * and it is timezone-agnostic.
305
+ * This DateTime object consistently uses UTC time to represent a naive date and time.
306
+ */
307
+ class DateTime {
308
+ jsDate;
309
+ constructor(year, month, day, hours = 0, minutes = 0, seconds = 0) {
310
+ this.jsDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds, 0));
311
+ }
312
+ static fromTimestamp(timestamp) {
313
+ const date = new Date(timestamp);
314
+ return new DateTime(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
315
+ }
316
+ static now() {
317
+ const now = new Date();
318
+ return new DateTime(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds());
319
+ }
320
+ toString() {
321
+ return this.jsDate.toString();
322
+ }
323
+ toLocaleDateString() {
324
+ return this.jsDate.toLocaleDateString();
325
+ }
326
+ getTime() {
327
+ return this.jsDate.getTime();
328
+ }
329
+ getFullYear() {
330
+ return this.jsDate.getUTCFullYear();
331
+ }
332
+ getMonth() {
333
+ return this.jsDate.getUTCMonth();
334
+ }
335
+ getDate() {
336
+ return this.jsDate.getUTCDate();
337
+ }
338
+ getDay() {
339
+ return this.jsDate.getUTCDay();
340
+ }
341
+ getHours() {
342
+ return this.jsDate.getUTCHours();
343
+ }
344
+ getMinutes() {
345
+ return this.jsDate.getUTCMinutes();
346
+ }
347
+ getSeconds() {
348
+ return this.jsDate.getUTCSeconds();
349
+ }
350
+ setFullYear(year) {
351
+ this.jsDate.setFullYear(year);
352
+ }
353
+ setDate(date) {
354
+ this.jsDate.setUTCDate(date);
355
+ }
356
+ setHours(hours) {
357
+ this.jsDate.setUTCHours(hours);
358
+ }
359
+ setMinutes(minutes) {
360
+ this.jsDate.setUTCMinutes(minutes);
361
+ }
362
+ setSeconds(seconds) {
363
+ this.jsDate.setUTCSeconds(seconds);
364
+ }
365
+ }
301
366
  // -----------------------------------------------------------------------------
302
367
  // Parsing
303
368
  // -----------------------------------------------------------------------------
304
- const INITIAL_1900_DAY = new Date(1899, 11, 30);
369
+ const INITIAL_1900_DAY = new DateTime(1899, 11, 30);
305
370
  const MS_PER_DAY = 24 * 60 * 60 * 1000;
306
371
  const CURRENT_MILLENIAL = 2000; // note: don't forget to update this in 2999
307
- const CURRENT_YEAR = new Date().getFullYear();
308
- const INITIAL_JS_DAY = new Date(0);
309
- const DATE_JS_1900_OFFSET = INITIAL_JS_DAY - INITIAL_1900_DAY;
372
+ const CURRENT_YEAR = DateTime.now().getFullYear();
373
+ const INITIAL_JS_DAY = DateTime.fromTimestamp(0);
374
+ const DATE_JS_1900_OFFSET = INITIAL_JS_DAY.getTime() - INITIAL_1900_DAY.getTime();
310
375
  const mdyDateRegexp = /^\d{1,2}(\/|-|\s)\d{1,2}((\/|-|\s)\d{1,4})?$/;
311
376
  const ymdDateRegexp = /^\d{3,4}(\/|-|\s)\d{1,2}(\/|-|\s)\d{1,2}$/;
312
377
  const timeRegexp = /((\d+(:\d+)?(:\d+)?\s*(AM|PM))|(\d+:\d+(:\d+)?))$/;
@@ -355,7 +420,7 @@ function _parseDateTime(str) {
355
420
  return {
356
421
  value: date.value + time.value,
357
422
  format: date.format + " " + (time.format === "hhhh:mm:ss" ? "hh:mm:ss" : time.format),
358
- 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()),
423
+ 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()),
359
424
  };
360
425
  }
361
426
  return date || time;
@@ -372,13 +437,13 @@ function parseDate(str, dateFormat) {
372
437
  const day = Number(parts[dayIndex]);
373
438
  const leadingZero = (parts[monthIndex].length === 2 && month < 10) || (parts[dayIndex].length === 2 && day < 10);
374
439
  const year = parts[yearIndex] ? inferYear(parts[yearIndex]) : CURRENT_YEAR;
375
- const jsDate = new Date(year, month - 1, day);
440
+ const jsDate = new DateTime(year, month - 1, day);
376
441
  const sep = str.match(/\/|-|\s/)[0];
377
442
  if (jsDate.getMonth() !== month - 1 || jsDate.getDate() !== day) {
378
443
  // invalid date
379
444
  return null;
380
445
  }
381
- const delta = jsDate - INITIAL_1900_DAY;
446
+ const delta = jsDate.getTime() - INITIAL_1900_DAY.getTime();
382
447
  let format = leadingZero ? `mm${sep}dd` : `m${sep}d`;
383
448
  if (parts[yearIndex]) {
384
449
  format = isMDY ? format + sep + "yyyy" : "yyyy" + sep + format;
@@ -438,7 +503,7 @@ function parseTime(str) {
438
503
  if (hours >= 24) {
439
504
  format = "hhhh:mm:ss";
440
505
  }
441
- const jsDate = new Date(1899, 11, 30, hours, minutes, seconds);
506
+ const jsDate = new DateTime(1899, 11, 30, hours, minutes, seconds);
442
507
  return {
443
508
  value: hours / 24 + minutes / 1440 + seconds / 86400,
444
509
  format: format,
@@ -452,7 +517,7 @@ function parseTime(str) {
452
517
  // -----------------------------------------------------------------------------
453
518
  function numberToJsDate(value) {
454
519
  const truncValue = Math.trunc(value);
455
- let date = new Date(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
520
+ let date = DateTime.fromTimestamp(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
456
521
  let time = value - truncValue;
457
522
  time = time < 0 ? 1 + time : time;
458
523
  const hours = Math.round(time * 24);
@@ -469,7 +534,7 @@ function jsDateToRoundNumber(date) {
469
534
  }
470
535
  /** Return the number of days in the current month of the given date */
471
536
  function getDaysInMonth(date) {
472
- return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
537
+ return new DateTime(date.getFullYear(), date.getMonth() + 1, 0).getDate();
473
538
  }
474
539
  function isLastDayOfMonth(date) {
475
540
  return getDaysInMonth(date) === date.getDate();
@@ -487,7 +552,7 @@ function addMonthsToDate(date, months, keepEndOfMonth) {
487
552
  const yStart = date.getFullYear();
488
553
  const mStart = date.getMonth();
489
554
  const dStart = date.getDate();
490
- const jsDate = new Date(yStart, mStart + months);
555
+ const jsDate = new DateTime(yStart, mStart + months, 1);
491
556
  if (keepEndOfMonth && dStart === getDaysInMonth(date)) {
492
557
  jsDate.setDate(getDaysInMonth(jsDate));
493
558
  }
@@ -647,13 +712,6 @@ function areTwoDatesWithinOneYear(startDate, endDate) {
647
712
  }
648
713
 
649
714
  //------------------------------------------------------------------------------
650
- /**
651
- * Stringify an object, like JSON.stringify, except that the first level of keys
652
- * is ordered.
653
- */
654
- function stringify(obj) {
655
- return JSON.stringify(obj, Object.keys(obj).sort());
656
- }
657
715
  /**
658
716
  * Remove quotes from a quoted string
659
717
  * ```js
@@ -883,7 +941,7 @@ function isBoolean(str) {
883
941
  function isDateTime(str) {
884
942
  return parseDateTime(str) !== null;
885
943
  }
886
- const MARKDOWN_LINK_REGEX = /^\[([^\[]+)\]\((.+)\)$/;
944
+ const MARKDOWN_LINK_REGEX = /^\[(.+)\]\((.+)\)$/;
887
945
  //link must start with http or https
888
946
  //https://stackoverflow.com/a/3809435/4760614
889
947
  const WEB_LINK_REGEX = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/;
@@ -952,7 +1010,7 @@ function isObjectEmptyRecursive(argument) {
952
1010
  */
953
1011
  function getItemId(item, itemsDic) {
954
1012
  for (let [key, value] of Object.entries(itemsDic)) {
955
- if (stringify(value) === stringify(item)) {
1013
+ if (deepEquals(value, item)) {
956
1014
  return parseInt(key, 10);
957
1015
  }
958
1016
  }
@@ -1018,6 +1076,22 @@ function lazy(fn) {
1018
1076
  lazyValue.map = (callback) => lazy(() => callback(lazyValue()));
1019
1077
  return lazyValue;
1020
1078
  }
1079
+ /**
1080
+ * Creates a version of the function that's memoized on the value of its first
1081
+ * argument, if any.
1082
+ */
1083
+ function memoize(func) {
1084
+ const cache = new Map();
1085
+ const funcName = func.name ? func.name + " (memoized)" : "memoized";
1086
+ return {
1087
+ [funcName](...args) {
1088
+ if (!cache.has(args[0])) {
1089
+ cache.set(args[0], func(...args));
1090
+ }
1091
+ return cache.get(args[0]);
1092
+ },
1093
+ }[funcName];
1094
+ }
1021
1095
  /**
1022
1096
  * Find the next defined value after the given index in an array of strings. If there is no defined value
1023
1097
  * after the index, return the closest defined value before the index. Return an empty string if no
@@ -1936,7 +2010,7 @@ function formatJSTime(jsDate, format) {
1936
2010
  .map((p) => {
1937
2011
  switch (p) {
1938
2012
  case "hhhh":
1939
- const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY) / (60 * 60 * 1000));
2013
+ const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY.getTime()) / (60 * 60 * 1000));
1940
2014
  return helapsedHours.toString();
1941
2015
  case "hh":
1942
2016
  return hours.toString().padStart(2, "0");
@@ -2183,8 +2257,10 @@ function convertInternalFormatToFormat(internalFormat) {
2183
2257
  const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
2184
2258
  // Same as above, but matches the exact string (nothing before or after)
2185
2259
  const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
2186
- /** Reference of a column header (eg. A, AB) */
2187
- const colHeader = new RegExp(/^([A-Z]{1,3})+$/, "i");
2260
+ /** Reference of a column header (eg. A, AB, $A) */
2261
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
2262
+ /** Reference of a row header (eg. 1, $1) */
2263
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
2188
2264
  /** Reference of a column (eg. A, $CA, Sheet1!B) */
2189
2265
  const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
2190
2266
  /** Reference of a row (eg. 1, 59, Sheet1!9) */
@@ -2214,6 +2290,9 @@ function isRowReference(xc) {
2214
2290
  function isColHeader(str) {
2215
2291
  return colHeader.test(str);
2216
2292
  }
2293
+ function isRowHeader(str) {
2294
+ return rowHeader.test(str);
2295
+ }
2217
2296
  /**
2218
2297
  * Return true if the given xc is the reference of a single cell,
2219
2298
  * without any specified sheet (e.g. A1)
@@ -2280,18 +2359,10 @@ class RangeImpl {
2280
2359
  if (isFullCol) {
2281
2360
  parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2282
2361
  parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2283
- if (zone.left === zone.right) {
2284
- parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2285
- parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2286
- }
2287
2362
  }
2288
2363
  if (isFullRow) {
2289
2364
  parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2290
2365
  parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2291
- if (zone.top === zone.bottom) {
2292
- parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2293
- parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2294
- }
2295
2366
  }
2296
2367
  return parts;
2297
2368
  }
@@ -3690,12 +3761,12 @@ function toString(value) {
3690
3761
  }
3691
3762
  }
3692
3763
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
3693
- function normalizeString(str) {
3764
+ const normalizeString = memoize(function normalizeString(str) {
3694
3765
  return str
3695
3766
  .toLowerCase()
3696
3767
  .normalize("NFD")
3697
3768
  .replace(/[\u0300-\u036f]/g, "");
3698
- }
3769
+ });
3699
3770
  /**
3700
3771
  * Normalize a value.
3701
3772
  * If the cell value is a string, this will set it to lowercase and replacing accent letters with plain letters
@@ -4426,6 +4497,7 @@ var CommandResult;
4426
4497
  CommandResult[CommandResult["SplitWillOverwriteContent"] = 90] = "SplitWillOverwriteContent";
4427
4498
  CommandResult[CommandResult["NoSplitSeparatorInSelection"] = 91] = "NoSplitSeparatorInSelection";
4428
4499
  CommandResult[CommandResult["NoActiveSheet"] = 92] = "NoActiveSheet";
4500
+ CommandResult[CommandResult["NoChanges"] = 93] = "NoChanges";
4429
4501
  })(CommandResult || (CommandResult = {}));
4430
4502
 
4431
4503
  const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
@@ -5150,6 +5222,7 @@ const FilterMenuPopoverBuilder = {
5150
5222
  },
5151
5223
  };
5152
5224
 
5225
+ const macRegex = /Mac/i;
5153
5226
  /**
5154
5227
  * Return true if the event was triggered from
5155
5228
  * a child element.
@@ -5195,6 +5268,17 @@ function setElementScrollTop(el, scroll) {
5195
5268
  }
5196
5269
  function getOpenedMenus() {
5197
5270
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
5271
+ }
5272
+ function isMacOS() {
5273
+ return Boolean(macRegex.test(navigator.userAgent));
5274
+ }
5275
+ /**
5276
+ * @param {KeyboardEvent | MouseEvent} ev
5277
+ * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
5278
+ * On Mac, this is the "meta" or "command" key.
5279
+ */
5280
+ function isCtrlKey(ev) {
5281
+ return isMacOS() ? ev.metaKey : ev.ctrlKey;
5198
5282
  }
5199
5283
 
5200
5284
  /**
@@ -9581,6 +9665,9 @@ function arg(definition, description = "") {
9581
9665
  function makeArg(str, description) {
9582
9666
  let parts = str.match(ARG_REGEXP);
9583
9667
  let name = parts[1].trim();
9668
+ if (!name) {
9669
+ throw new Error(`Function argument definition is missing a name: '${str}'.`);
9670
+ }
9584
9671
  let types = [];
9585
9672
  let isOptional = false;
9586
9673
  let isRepeating = false;
@@ -12034,7 +12121,7 @@ const DATE = {
12034
12121
  if (_year < 1900) {
12035
12122
  _year += 1900;
12036
12123
  }
12037
- const jsDate = new Date(_year, _month - 1, _day);
12124
+ const jsDate = new DateTime(_year, _month - 1, _day);
12038
12125
  const result = jsDateToRoundNumber(jsDate);
12039
12126
  assert(() => result >= 0, _lt(`The function [[FUNCTION_NAME]] result must be greater than or equal 01/01/1900.`));
12040
12127
  return result;
@@ -12077,7 +12164,7 @@ const DATEDIF = {
12077
12164
  // See: https://support.microsoft.com/en-us/office/datedif-function-25dba1a4-2812-480b-84dd-8b32a451b35c
12078
12165
  let days = jsEndDate.getDate() - jsStartDate.getDate();
12079
12166
  if (days < 0) {
12080
- const monthBeforeEndMonth = new Date(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
12167
+ const monthBeforeEndMonth = new DateTime(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
12081
12168
  const daysInMonthBeforeEndMonth = getDaysInMonth(monthBeforeEndMonth);
12082
12169
  days = daysInMonthBeforeEndMonth - Math.abs(days);
12083
12170
  }
@@ -12086,7 +12173,7 @@ const DATEDIF = {
12086
12173
  if (areTwoDatesWithinOneYear(_startDate, _endDate)) {
12087
12174
  return getTimeDifferenceInWholeDays(jsStartDate, jsEndDate);
12088
12175
  }
12089
- const endDateWithinOneYear = new Date(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
12176
+ const endDateWithinOneYear = new DateTime(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
12090
12177
  let days = getTimeDifferenceInWholeDays(jsStartDate, endDateWithinOneYear);
12091
12178
  if (days < 0) {
12092
12179
  endDateWithinOneYear.setFullYear(jsStartDate.getFullYear() + 1);
@@ -12199,7 +12286,7 @@ const EOMONTH = {
12199
12286
  const _months = Math.trunc(toNumber(months));
12200
12287
  const yStart = _startDate.getFullYear();
12201
12288
  const mStart = _startDate.getMonth();
12202
- const jsDate = new Date(yStart, mStart + _months + 1, 0);
12289
+ const jsDate = new DateTime(yStart, mStart + _months + 1, 0);
12203
12290
  return jsDateToRoundNumber(jsDate);
12204
12291
  },
12205
12292
  isExported: true,
@@ -12236,17 +12323,17 @@ const ISOWEEKNUM = {
12236
12323
  // The first week of the year is the week that contains the first
12237
12324
  // Thursday of the year.
12238
12325
  let firstThursday = 1;
12239
- while (new Date(y, 0, firstThursday).getDay() !== 4) {
12326
+ while (new DateTime(y, 0, firstThursday).getDay() !== 4) {
12240
12327
  firstThursday += 1;
12241
12328
  }
12242
- const firstDayOfFirstWeek = new Date(y, 0, firstThursday - 3);
12329
+ const firstDayOfFirstWeek = new DateTime(y, 0, firstThursday - 3);
12243
12330
  // The last week of the year is the week that contains the last Thursday of
12244
12331
  // the year.
12245
12332
  let lastThursday = 31;
12246
- while (new Date(y, 11, lastThursday).getDay() !== 4) {
12333
+ while (new DateTime(y, 11, lastThursday).getDay() !== 4) {
12247
12334
  lastThursday -= 1;
12248
12335
  }
12249
- const lastDayOfLastWeek = new Date(y, 11, lastThursday + 3);
12336
+ const lastDayOfLastWeek = new DateTime(y, 11, lastThursday + 3);
12250
12337
  // B - If our date > lastDayOfLastWeek then it's in the weeks of the year after
12251
12338
  // If our date < firstDayOfFirstWeek then it's in the weeks of the year before
12252
12339
  let offsetYear;
@@ -12272,17 +12359,17 @@ const ISOWEEKNUM = {
12272
12359
  case 1:
12273
12360
  // firstDay is the 1st day of the 1st week of the year after
12274
12361
  // firstDay = lastDayOfLastWeek + 1 Day
12275
- firstDay = new Date(y, 11, lastThursday + 3 + 1);
12362
+ firstDay = new DateTime(y, 11, lastThursday + 3 + 1);
12276
12363
  break;
12277
12364
  case -1:
12278
12365
  // firstDay is the 1st day of the 1st week of the previous year.
12279
12366
  // The first week of the previous year is the week that contains the
12280
12367
  // first Thursday of the previous year.
12281
12368
  let firstThursdayPreviousYear = 1;
12282
- while (new Date(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
12369
+ while (new DateTime(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
12283
12370
  firstThursdayPreviousYear += 1;
12284
12371
  }
12285
- firstDay = new Date(y - 1, 0, firstThursdayPreviousYear - 3);
12372
+ firstDay = new DateTime(y - 1, 0, firstThursdayPreviousYear - 3);
12286
12373
  break;
12287
12374
  }
12288
12375
  const diff = (_date.getTime() - firstDay.getTime()) / MS_PER_DAY;
@@ -12417,8 +12504,8 @@ const NETWORKDAYS_INTL = {
12417
12504
  });
12418
12505
  }
12419
12506
  const invertDate = _startDate.getTime() > _endDate.getTime();
12420
- const stopDate = new Date((invertDate ? _startDate : _endDate).getTime());
12421
- let stepDate = new Date((invertDate ? _endDate : _startDate).getTime());
12507
+ const stopDate = DateTime.fromTimestamp((invertDate ? _startDate : _endDate).getTime());
12508
+ let stepDate = DateTime.fromTimestamp((invertDate ? _endDate : _startDate).getTime());
12422
12509
  const timeStopDate = stopDate.getTime();
12423
12510
  let timeStepDate = stepDate.getTime();
12424
12511
  let netWorkingDay = 0;
@@ -12442,8 +12529,7 @@ const NOW = {
12442
12529
  returns: ["DATE"],
12443
12530
  computeFormat: () => "m/d/yyyy hh:mm:ss",
12444
12531
  compute: function () {
12445
- let today = new Date();
12446
- today.setMilliseconds(0);
12532
+ let today = DateTime.now();
12447
12533
  const delta = today.getTime() - INITIAL_1900_DAY.getTime();
12448
12534
  const time = today.getHours() / 24 + today.getMinutes() / 1440 + today.getSeconds() / 86400;
12449
12535
  return Math.floor(delta / MS_PER_DAY) + time;
@@ -12513,8 +12599,8 @@ const TODAY = {
12513
12599
  returns: ["DATE"],
12514
12600
  computeFormat: () => "m/d/yyyy",
12515
12601
  compute: function () {
12516
- const today = new Date();
12517
- const jsDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());
12602
+ const today = DateTime.now();
12603
+ const jsDate = new DateTime(today.getFullYear(), today.getMonth(), today.getDate());
12518
12604
  return jsDateToRoundNumber(jsDate);
12519
12605
  },
12520
12606
  isExported: true,
@@ -12569,10 +12655,10 @@ const WEEKNUM = {
12569
12655
  }
12570
12656
  const y = _date.getFullYear();
12571
12657
  let dayStart = 1;
12572
- let startDayOfFirstWeek = new Date(y, 0, dayStart);
12658
+ let startDayOfFirstWeek = new DateTime(y, 0, dayStart);
12573
12659
  while (startDayOfFirstWeek.getDay() !== startDayOfWeek) {
12574
12660
  dayStart += 1;
12575
- startDayOfFirstWeek = new Date(y, 0, dayStart);
12661
+ startDayOfFirstWeek = new DateTime(y, 0, dayStart);
12576
12662
  }
12577
12663
  const dif = (_date.getTime() - startDayOfFirstWeek.getTime()) / MS_PER_DAY;
12578
12664
  if (dif < 0) {
@@ -12626,7 +12712,7 @@ const WORKDAY_INTL = {
12626
12712
  timesHoliday.add(holiday.getTime());
12627
12713
  });
12628
12714
  }
12629
- let stepDate = new Date(_startDate.getTime());
12715
+ let stepDate = DateTime.fromTimestamp(_startDate.getTime());
12630
12716
  let timeStepDate = stepDate.getTime();
12631
12717
  const unitDay = Math.sign(_numDays);
12632
12718
  let stepDay = Math.abs(_numDays);
@@ -12688,7 +12774,7 @@ const MONTH_START = {
12688
12774
  const _startDate = toJsDate(date);
12689
12775
  const yStart = _startDate.getFullYear();
12690
12776
  const mStart = _startDate.getMonth();
12691
- const jsDate = new Date(yStart, mStart, 1);
12777
+ const jsDate = new DateTime(yStart, mStart, 1);
12692
12778
  return jsDateToRoundNumber(jsDate);
12693
12779
  },
12694
12780
  };
@@ -12726,7 +12812,7 @@ const QUARTER_START = {
12726
12812
  compute: function (date) {
12727
12813
  const quarter = QUARTER.compute(date);
12728
12814
  const year = YEAR.compute(date);
12729
- const jsDate = new Date(year, (quarter - 1) * 3, 1);
12815
+ const jsDate = new DateTime(year, (quarter - 1) * 3, 1);
12730
12816
  return jsDateToRoundNumber(jsDate);
12731
12817
  },
12732
12818
  };
@@ -12741,7 +12827,7 @@ const QUARTER_END = {
12741
12827
  compute: function (date) {
12742
12828
  const quarter = QUARTER.compute(date);
12743
12829
  const year = YEAR.compute(date);
12744
- const jsDate = new Date(year, quarter * 3, 0);
12830
+ const jsDate = new DateTime(year, quarter * 3, 0);
12745
12831
  return jsDateToRoundNumber(jsDate);
12746
12832
  },
12747
12833
  };
@@ -12755,7 +12841,7 @@ const YEAR_START = {
12755
12841
  computeFormat: () => "m/d/yyyy",
12756
12842
  compute: function (date) {
12757
12843
  const year = YEAR.compute(date);
12758
- const jsDate = new Date(year, 0, 1);
12844
+ const jsDate = new DateTime(year, 0, 1);
12759
12845
  return jsDateToRoundNumber(jsDate);
12760
12846
  },
12761
12847
  };
@@ -12769,7 +12855,7 @@ const YEAR_END = {
12769
12855
  computeFormat: () => "m/d/yyyy",
12770
12856
  compute: function (date) {
12771
12857
  const year = YEAR.compute(date);
12772
- const jsDate = new Date(year + 1, 0, 0);
12858
+ const jsDate = new DateTime(year + 1, 0, 0);
12773
12859
  return jsDateToRoundNumber(jsDate);
12774
12860
  },
12775
12861
  };
@@ -12817,8 +12903,8 @@ const DEFAULT_DELTA_ARG = 0;
12817
12903
  const DELTA = {
12818
12904
  description: _lt("Compare two numeric values, returning 1 if they're equal."),
12819
12905
  args: [
12820
- arg(" (number)", _lt("The first number to compare.")),
12821
- arg(` (number, default=${DEFAULT_DELTA_ARG})`, _lt("The second number to compare.")),
12906
+ arg("number1 (number)", _lt("The first number to compare.")),
12907
+ arg(`number2 (number, default=${DEFAULT_DELTA_ARG})`, _lt("The second number to compare.")),
12822
12908
  ],
12823
12909
  returns: ["NUMBER"],
12824
12910
  compute: function (number1, number2 = DEFAULT_DELTA_ARG) {
@@ -12904,7 +12990,7 @@ function assertDeprecationFactorStrictlyPositive(factor) {
12904
12990
  function assertSettlementLessThanOneYearBeforeMaturity(settlement, maturity) {
12905
12991
  const startDate = toJsDate(settlement);
12906
12992
  const endDate = toJsDate(maturity);
12907
- const startDatePlusOneYear = new Date(startDate);
12993
+ const startDatePlusOneYear = toJsDate(settlement);
12908
12994
  startDatePlusOneYear.setFullYear(startDate.getFullYear() + 1);
12909
12995
  assert(() => endDate.getTime() <= startDatePlusOneYear.getTime(), _lt("The settlement date (%s) must at most one year after the maturity date (%s).", settlement.toString(), maturity.toString()));
12910
12996
  }
@@ -13039,7 +13125,7 @@ const AMORLINC = {
13039
13125
  arg("salvage (number)", _lt("The value of the asset at the end of depreciation.")),
13040
13126
  arg("period (number)", _lt("The single period within life for which to calculate depreciation.")),
13041
13127
  arg("rate (number)", _lt("The deprecation rate.")),
13042
- arg(" (number, optional)", _lt("An indicator of what day count method to use.")),
13128
+ arg("day_count_convention (number, optional)", _lt("An indicator of what day count method to use.")),
13043
13129
  ],
13044
13130
  returns: ["NUMBER"],
13045
13131
  compute: function (cost, purchaseDate, firstPeriodEnd, salvage, period, rate, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {
@@ -16022,8 +16108,8 @@ const MID = {
16022
16108
  description: _lt("A segment of a string."),
16023
16109
  args: [
16024
16110
  arg("text (string)", _lt("The string to extract a segment from.")),
16025
- arg(" (number)", _lt("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
16026
- arg(" (number)", _lt("The length of the segment to extract.")),
16111
+ 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.")),
16112
+ arg("extract_length (number)", _lt("The length of the segment to extract.")),
16027
16113
  ],
16028
16114
  returns: ["STRING"],
16029
16115
  compute: function (text, starting_at, extract_length) {
@@ -17692,10 +17778,10 @@ const arrowMap = {
17692
17778
  function updateSelectionWithArrowKeys(ev, selection) {
17693
17779
  const direction = arrowMap[ev.key];
17694
17780
  if (ev.shiftKey) {
17695
- selection.resizeAnchorZone(direction, ev.ctrlKey ? "end" : 1);
17781
+ selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
17696
17782
  }
17697
17783
  else {
17698
- selection.moveAnchorCell(direction, ev.ctrlKey ? "end" : 1);
17784
+ selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
17699
17785
  }
17700
17786
  }
17701
17787
 
@@ -22392,7 +22478,10 @@ class GridOverlay extends Component {
22392
22478
  return;
22393
22479
  }
22394
22480
  const [col, row] = this.getCartesianCoordinates(ev);
22395
- this.props.onCellClicked(col, row, { shiftKey: ev.shiftKey, ctrlKey: ev.ctrlKey });
22481
+ this.props.onCellClicked(col, row, {
22482
+ expandZone: ev.shiftKey,
22483
+ addZone: isCtrlKey(ev),
22484
+ });
22396
22485
  }
22397
22486
  onDoubleClick(ev) {
22398
22487
  const [col, row] = this.getCartesianCoordinates(ev);
@@ -22465,6 +22554,7 @@ class AbstractResizer extends Component {
22465
22554
  draggerShadowThickness: 0,
22466
22555
  delta: 0,
22467
22556
  base: 0,
22557
+ position: "before",
22468
22558
  });
22469
22559
  _computeHandleDisplay(ev) {
22470
22560
  const position = this._getEvOffset(ev);
@@ -22589,11 +22679,13 @@ class AbstractResizer extends Component {
22589
22679
  this.state.draggerLinePosition = dimensions.start;
22590
22680
  this.state.draggerShadowPosition = dimensions.start;
22591
22681
  this.state.base = elementIndex;
22682
+ this.state.position = "before";
22592
22683
  }
22593
22684
  else if (this._getSelectedZoneEnd() < elementIndex) {
22594
22685
  this.state.draggerLinePosition = dimensions.end;
22595
22686
  this.state.draggerShadowPosition = dimensions.end - this.state.draggerShadowThickness;
22596
- this.state.base = elementIndex + 1;
22687
+ this.state.base = elementIndex;
22688
+ this.state.position = "after";
22597
22689
  }
22598
22690
  else {
22599
22691
  this.state.draggerLinePosition = startDimensions.start;
@@ -22617,7 +22709,7 @@ class AbstractResizer extends Component {
22617
22709
  this._increaseSelection(index);
22618
22710
  }
22619
22711
  else {
22620
- this._selectElement(index, ev.ctrlKey);
22712
+ this._selectElement(index, isCtrlKey(ev));
22621
22713
  }
22622
22714
  this.lastSelectedElementIndex = index;
22623
22715
  const mouseMoveSelect = (col, row) => {
@@ -22630,7 +22722,7 @@ class AbstractResizer extends Component {
22630
22722
  const mouseUpSelect = () => {
22631
22723
  this.state.isSelecting = false;
22632
22724
  this.lastSelectedElementIndex = null;
22633
- this.env.model.dispatch(ev.ctrlKey ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
22725
+ this.env.model.dispatch(isCtrlKey(ev) ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
22634
22726
  this._computeGrabDisplay(ev);
22635
22727
  };
22636
22728
  dragAndDropBeyondTheViewport(this.env, mouseMoveSelect, mouseUpSelect);
@@ -22776,13 +22868,14 @@ class ColResizer extends AbstractResizer {
22776
22868
  dimension: "COL",
22777
22869
  base: this.state.base,
22778
22870
  elements,
22871
+ position: this.state.position,
22779
22872
  });
22780
22873
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
22781
22874
  this.env.raiseError(MergeErrorMessage);
22782
22875
  }
22783
22876
  }
22784
- _selectElement(index, ctrlKey) {
22785
- this.env.model.selection.selectColumn(index, ctrlKey ? "newAnchor" : "overrideSelection");
22877
+ _selectElement(index, addDistinctHeader) {
22878
+ this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
22786
22879
  }
22787
22880
  _increaseSelection(index) {
22788
22881
  this.env.model.selection.selectColumn(index, "updateAnchor");
@@ -22949,13 +23042,14 @@ class RowResizer extends AbstractResizer {
22949
23042
  dimension: "ROW",
22950
23043
  base: this.state.base,
22951
23044
  elements,
23045
+ position: this.state.position,
22952
23046
  });
22953
23047
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
22954
23048
  this.env.raiseError(MergeErrorMessage);
22955
23049
  }
22956
23050
  }
22957
- _selectElement(index, ctrlKey) {
22958
- this.env.model.selection.selectRow(index, ctrlKey ? "newAnchor" : "overrideSelection");
23051
+ _selectElement(index, addDistinctHeader) {
23052
+ this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
22959
23053
  }
22960
23054
  _increaseSelection(index) {
22961
23055
  this.env.model.selection.selectRow(index, "updateAnchor");
@@ -23056,8 +23150,8 @@ function useWheelHandler(handler) {
23056
23150
  return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT);
23057
23151
  }
23058
23152
  const onMouseWheel = (ev) => {
23059
- const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode);
23060
- const deltaY = normalize(ev.shiftKey ? ev.deltaX : ev.deltaY, ev.deltaMode);
23153
+ const deltaX = normalize(ev.shiftKey && !isMacOS() ? ev.deltaY : ev.deltaX, ev.deltaMode);
23154
+ const deltaY = normalize(ev.shiftKey && !isMacOS() ? ev.deltaX : ev.deltaY, ev.deltaMode);
23061
23155
  handler(deltaX, deltaY);
23062
23156
  };
23063
23157
  return onMouseWheel;
@@ -23759,8 +23853,8 @@ class Grid extends Component {
23759
23853
  // ---------------------------------------------------------------------------
23760
23854
  // Zone selection with mouse
23761
23855
  // ---------------------------------------------------------------------------
23762
- onCellClicked(col, row, { ctrlKey, shiftKey }) {
23763
- if (ctrlKey) {
23856
+ onCellClicked(col, row, { addZone, expandZone }) {
23857
+ if (addZone) {
23764
23858
  this.env.model.dispatch("PREPARE_SELECTION_INPUT_EXPANSION");
23765
23859
  }
23766
23860
  if (this.env.model.getters.hasOpenedPopover()) {
@@ -23769,10 +23863,10 @@ class Grid extends Component {
23769
23863
  if (this.env.model.getters.getEditionMode() === "editing") {
23770
23864
  this.env.model.dispatch("STOP_EDITION");
23771
23865
  }
23772
- if (shiftKey) {
23866
+ if (expandZone) {
23773
23867
  this.env.model.selection.setAnchorCorner(col, row);
23774
23868
  }
23775
- else if (ctrlKey) {
23869
+ else if (addZone) {
23776
23870
  this.env.model.selection.addCellToSelection(col, row);
23777
23871
  }
23778
23872
  else {
@@ -23834,9 +23928,7 @@ class Grid extends Component {
23834
23928
  return;
23835
23929
  }
23836
23930
  let keyDownString = "";
23837
- if (ev.ctrlKey)
23838
- keyDownString += "CTRL+";
23839
- if (ev.metaKey)
23931
+ if (isCtrlKey(ev))
23840
23932
  keyDownString += "CTRL+";
23841
23933
  if (ev.altKey)
23842
23934
  keyDownString += "ALT+";
@@ -25744,7 +25836,7 @@ const machine = {
25744
25836
  SPACE: goTo(State.RightRef),
25745
25837
  NUMBER: goTo(State.Found),
25746
25838
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
25747
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
25839
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
25748
25840
  },
25749
25841
  [State.RightColumnRef]: {
25750
25842
  SPACE: goTo(State.RightColumnRef),
@@ -25755,6 +25847,7 @@ const machine = {
25755
25847
  SPACE: goTo(State.RightRowRef),
25756
25848
  NUMBER: goTo(State.Found),
25757
25849
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
25850
+ SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
25758
25851
  },
25759
25852
  [State.Found]: {},
25760
25853
  };
@@ -28991,6 +29084,14 @@ class BordersPlugin extends CorePlugin {
28991
29084
  // ---------------------------------------------------------------------------
28992
29085
  // Command Handling
28993
29086
  // ---------------------------------------------------------------------------
29087
+ allowDispatch(cmd) {
29088
+ switch (cmd.type) {
29089
+ case "SET_BORDER":
29090
+ return this.checkBordersUnchanged(cmd);
29091
+ default:
29092
+ return 0 /* CommandResult.Success */;
29093
+ }
29094
+ }
28994
29095
  handle(cmd) {
28995
29096
  switch (cmd.type) {
28996
29097
  case "ADD_MERGE":
@@ -29410,6 +29511,14 @@ class BordersPlugin extends CorePlugin {
29410
29511
  this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersTopLeft.right);
29411
29512
  }
29412
29513
  }
29514
+ checkBordersUnchanged(cmd) {
29515
+ const currentBorder = this.getCellBorder(cmd);
29516
+ const areAllNewBordersUndefined = !cmd.border?.bottom && !cmd.border?.left && !cmd.border?.right && !cmd.border?.top;
29517
+ if ((!currentBorder && areAllNewBordersUndefined) || deepEquals(currentBorder, cmd.border)) {
29518
+ return 93 /* CommandResult.NoChanges */;
29519
+ }
29520
+ return 0 /* CommandResult.Success */;
29521
+ }
29413
29522
  // ---------------------------------------------------------------------------
29414
29523
  // Import/Export
29415
29524
  // ---------------------------------------------------------------------------
@@ -29446,7 +29555,7 @@ class BordersPlugin extends CorePlugin {
29446
29555
  */
29447
29556
  function getBorderId(border) {
29448
29557
  for (let [key, value] of Object.entries(borders)) {
29449
- if (stringify(value) === stringify(border)) {
29558
+ if (deepEquals(value, border)) {
29450
29559
  return parseInt(key, 10);
29451
29560
  }
29452
29561
  }
@@ -29517,8 +29626,9 @@ class CellPlugin extends CorePlugin {
29517
29626
  allowDispatch(cmd) {
29518
29627
  switch (cmd.type) {
29519
29628
  case "UPDATE_CELL":
29629
+ return this.checkCellOutOfSheet(cmd);
29520
29630
  case "CLEAR_CELL":
29521
- return this.checkCellOutOfSheet(cmd.sheetId, cmd.col, cmd.row);
29631
+ return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
29522
29632
  default:
29523
29633
  return 0 /* CommandResult.Success */;
29524
29634
  }
@@ -29677,14 +29787,9 @@ class CellPlugin extends CorePlugin {
29677
29787
  */
29678
29788
  getCellById(cellId) {
29679
29789
  // this must be as fast as possible
29680
- for (const sheetId in this.cells) {
29681
- const sheet = this.cells[sheetId];
29682
- const cell = sheet[cellId];
29683
- if (cell) {
29684
- return cell;
29685
- }
29686
- }
29687
- return undefined;
29790
+ const position = this.getters.getCellPosition(cellId);
29791
+ const sheet = this.cells[position.sheetId];
29792
+ return sheet[cellId];
29688
29793
  }
29689
29794
  /*
29690
29795
  * Reconstructs the original formula string based on a normalized form and its dependencies
@@ -29921,13 +30026,23 @@ class CellPlugin extends CorePlugin {
29921
30026
  dependencies: [],
29922
30027
  };
29923
30028
  }
29924
- checkCellOutOfSheet(sheetId, col, row) {
30029
+ checkCellOutOfSheet(cmd) {
30030
+ const { sheetId, col, row } = cmd;
29925
30031
  const sheet = this.getters.tryGetSheet(sheetId);
29926
30032
  if (!sheet)
29927
30033
  return 27 /* CommandResult.InvalidSheetId */;
29928
30034
  const sheetZone = this.getters.getSheetZone(sheetId);
29929
30035
  return isInside(col, row, sheetZone) ? 0 /* CommandResult.Success */ : 18 /* CommandResult.TargetOutOfSheet */;
29930
30036
  }
30037
+ checkUselessClearCell(cmd) {
30038
+ const cell = this.getters.getCell(cmd);
30039
+ if (!cell)
30040
+ return 93 /* CommandResult.NoChanges */;
30041
+ if (!cell.content && !cell.style && !cell.format) {
30042
+ return 93 /* CommandResult.NoChanges */;
30043
+ }
30044
+ return 0 /* CommandResult.Success */;
30045
+ }
29931
30046
  }
29932
30047
 
29933
30048
  class ChartPlugin extends CorePlugin {
@@ -32408,7 +32523,7 @@ class SheetPlugin extends CorePlugin {
32408
32523
  const elements = cmd.dimension === "COL"
32409
32524
  ? this.getNumberCols(cmd.sheetId)
32410
32525
  : this.getNumberRows(cmd.sheetId);
32411
- if (cmd.base < 0 || cmd.base > elements) {
32526
+ if (cmd.base < 0 || cmd.base >= elements) {
32412
32527
  return 86 /* CommandResult.InvalidHeaderIndex */;
32413
32528
  }
32414
32529
  else if (cmd.quantity <= 0) {
@@ -33030,10 +33145,13 @@ class SheetPlugin extends CorePlugin {
33030
33145
  // begin with the end.
33031
33146
  rows.sort((a, b) => b - a);
33032
33147
  for (let group of groupConsecutive(rows)) {
33148
+ // indexes are sorted in the descending order
33149
+ const from = group[group.length - 1];
33150
+ const to = group[0];
33033
33151
  // Move the cells.
33034
- this.moveCellOnRowsDeletion(sheet, group[group.length - 1], group[0]);
33035
- // Effectively delete the element and recompute the left-right/top-bottom.
33036
- group.map((row) => this.updateRowsStructureOnDeletion(row, sheet));
33152
+ this.moveCellOnRowsDeletion(sheet, from, to);
33153
+ // Effectively delete the rows
33154
+ this.updateRowsStructureOnDeletion(sheet, from, to);
33037
33155
  }
33038
33156
  const count = rows.filter((row) => row < sheet.panes.ySplit).length;
33039
33157
  if (count) {
@@ -33055,8 +33173,6 @@ class SheetPlugin extends CorePlugin {
33055
33173
  this.addEmptyRows(sheet, quantity);
33056
33174
  // Move the cells.
33057
33175
  this.moveCellsOnAddition(sheet, index, quantity, "rows");
33058
- // Recompute the left-right/top-bottom.
33059
- this.updateRowsStructureOnAddition(sheet, row, quantity);
33060
33176
  if (index < sheet.panes.ySplit) {
33061
33177
  this.setPaneDivisions(sheet.id, sheet.panes.ySplit + quantity, "ROW");
33062
33178
  }
@@ -33157,35 +33273,20 @@ class SheetPlugin extends CorePlugin {
33157
33273
  }
33158
33274
  }
33159
33275
  }
33160
- updateRowsStructureOnDeletion(index, sheet) {
33276
+ updateRowsStructureOnDeletion(sheet, deleteFromRow, deleteToRow) {
33161
33277
  const rows = [];
33162
- const cellsQueue = sheet.rows.map((row) => row.cells);
33278
+ const cellsQueue = sheet.rows.map((row) => row.cells).reverse();
33163
33279
  for (let i in sheet.rows) {
33164
- if (Number(i) === index) {
33280
+ const row = Number(i);
33281
+ if (row >= deleteFromRow && row <= deleteToRow) {
33165
33282
  continue;
33166
33283
  }
33167
33284
  rows.push({
33168
- cells: cellsQueue.shift(),
33285
+ cells: cellsQueue.pop(),
33169
33286
  });
33170
33287
  }
33171
33288
  this.history.update("sheets", sheet.id, "rows", rows);
33172
33289
  }
33173
- /**
33174
- * Update the rows of the sheet after an addition:
33175
- * - Rename the rows
33176
- *
33177
- * @param sheet Sheet on which the deletion occurs
33178
- * @param addedRow Index of the added row
33179
- * @param rowsToAdd Number of the rows to add
33180
- */
33181
- updateRowsStructureOnAddition(sheet, addedRow, rowsToAdd) {
33182
- const rows = [];
33183
- const cellsQueue = sheet.rows.map((row) => row.cells);
33184
- sheet.rows.forEach(() => rows.push({
33185
- cells: cellsQueue.shift(),
33186
- }));
33187
- this.history.update("sheets", sheet.id, "rows", rows);
33188
- }
33189
33290
  /**
33190
33291
  * Add empty rows at the end of the rows
33191
33292
  *
@@ -33455,13 +33556,18 @@ class EvaluationPlugin extends UIPlugin {
33455
33556
  // Getters
33456
33557
  // ---------------------------------------------------------------------------
33457
33558
  evaluateFormula(formulaString, sheetId = this.getters.getActiveSheetId()) {
33458
- const compiledFormula = compile(formulaString);
33459
- const params = this.getCompilationParameters((cell) => this.getEvaluatedCell(this.getters.getCellPosition(cell.id)));
33460
- const ranges = [];
33461
- for (let xc of compiledFormula.dependencies) {
33462
- ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
33559
+ try {
33560
+ const compiledFormula = compile(formulaString);
33561
+ const params = this.getCompilationParameters((cell) => this.getEvaluatedCell(this.getters.getCellPosition(cell.id)));
33562
+ const ranges = [];
33563
+ for (let xc of compiledFormula.dependencies) {
33564
+ ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
33565
+ }
33566
+ return compiledFormula.execute(ranges, ...params).value;
33567
+ }
33568
+ catch (error) {
33569
+ return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
33463
33570
  }
33464
- return compiledFormula.execute(ranges, ...params).value;
33465
33571
  }
33466
33572
  /**
33467
33573
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -33636,6 +33742,7 @@ class EvaluationPlugin extends UIPlugin {
33636
33742
  }
33637
33743
  return evaluatedCell;
33638
33744
  };
33745
+ const rangeCache = {};
33639
33746
  /**
33640
33747
  * Return the values of the cell(s) used in reference, but always in the format of a range even
33641
33748
  * if a single cell is referenced. It is a list of col values. This is useful for the formulas that describe parameters as
@@ -33652,21 +33759,26 @@ class EvaluationPlugin extends UIPlugin {
33652
33759
  // Performance issue: Avoid fetching data on positions that are out of the spreadsheet
33653
33760
  // e.g. A1:ZZZ9999 in a sheet with 10 cols and 10 rows should ignore everything past J10 and return a 10x10 array
33654
33761
  const sheetZone = getters.getSheetZone(sheetId);
33655
- const result = [];
33656
33762
  const zone = intersection(range.zone, sheetZone);
33657
33763
  if (!zone) {
33658
- result.push([]);
33659
- return result;
33764
+ return [[]];
33765
+ }
33766
+ const { top, left, bottom, right } = zone;
33767
+ const cacheKey = `${sheetId}-${top}-${left}-${bottom}-${right}`;
33768
+ if (cacheKey in rangeCache) {
33769
+ return rangeCache[cacheKey];
33660
33770
  }
33771
+ const result = new Array(right - left + 1);
33661
33772
  // Performance issue: nested loop is faster than a map here
33662
- for (let col = zone.left; col <= zone.right; col++) {
33663
- const rowValues = [];
33664
- for (let row = zone.top; row <= zone.bottom; row++) {
33773
+ for (let col = left; col <= right; col++) {
33774
+ const rowValues = new Array(bottom - top + 1);
33775
+ for (let row = top; row <= bottom; row++) {
33665
33776
  const cell = evalContext.getters.getCell({ sheetId: range.sheetId, col, row });
33666
- rowValues.push(cell ? getEvaluatedCell(cell) : undefined);
33777
+ rowValues[row - top] = cell ? getEvaluatedCell(cell) : undefined;
33667
33778
  }
33668
- result.push(rowValues);
33779
+ result[col - left] = rowValues;
33669
33780
  }
33781
+ rangeCache[cacheKey] = result;
33670
33782
  return result;
33671
33783
  }
33672
33784
  /**
@@ -33880,39 +33992,34 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
33880
33992
  this.computedIcons[sheetId] = {};
33881
33993
  const computedStyle = this.computedStyles[sheetId];
33882
33994
  for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
33883
- try {
33884
- switch (cf.rule.type) {
33885
- case "ColorScaleRule":
33886
- for (let range of cf.ranges) {
33887
- this.applyColorScale(range, cf.rule);
33888
- }
33889
- break;
33890
- case "IconSetRule":
33891
- for (let range of cf.ranges) {
33892
- this.applyIcon(range, cf.rule);
33893
- }
33894
- break;
33895
- default:
33896
- for (let ref of cf.ranges) {
33897
- const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
33898
- for (let row = zone.top; row <= zone.bottom; row++) {
33899
- for (let col = zone.left; col <= zone.right; col++) {
33900
- const pr = this.rulePredicate[cf.rule.type];
33901
- let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
33902
- if (pr && pr(cell, cf.rule)) {
33903
- if (!computedStyle[col])
33904
- computedStyle[col] = [];
33905
- // we must combine all the properties of all the CF rules applied to the given cell
33906
- computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
33907
- }
33995
+ switch (cf.rule.type) {
33996
+ case "ColorScaleRule":
33997
+ for (let range of cf.ranges) {
33998
+ this.applyColorScale(range, cf.rule);
33999
+ }
34000
+ break;
34001
+ case "IconSetRule":
34002
+ for (let range of cf.ranges) {
34003
+ this.applyIcon(range, cf.rule);
34004
+ }
34005
+ break;
34006
+ default:
34007
+ for (let ref of cf.ranges) {
34008
+ const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
34009
+ for (let row = zone.top; row <= zone.bottom; row++) {
34010
+ for (let col = zone.left; col <= zone.right; col++) {
34011
+ const pr = this.rulePredicate[cf.rule.type];
34012
+ let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
34013
+ if (pr && pr(cell, cf.rule)) {
34014
+ if (!computedStyle[col])
34015
+ computedStyle[col] = [];
34016
+ // we must combine all the properties of all the CF rules applied to the given cell
34017
+ computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
33908
34018
  }
33909
34019
  }
33910
34020
  }
33911
- break;
33912
- }
33913
- }
33914
- catch (_) {
33915
- // we don't care about the errors within the evaluation of a rule
34021
+ }
34022
+ break;
33916
34023
  }
33917
34024
  }
33918
34025
  }
@@ -33937,7 +34044,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
33937
34044
  return percentile(rangeValues, Number(threshold.value) / 100, true);
33938
34045
  case "formula":
33939
34046
  const value = threshold.value && this.getters.evaluateFormula(threshold.value);
33940
- return !(value instanceof Promise) ? value : null;
34047
+ return typeof value === "number" ? value : null;
33941
34048
  default:
33942
34049
  return null;
33943
34050
  }
@@ -35586,19 +35693,21 @@ class AutofillPlugin extends UIPlugin {
35586
35693
  let col = zone.left;
35587
35694
  let row = zone.bottom;
35588
35695
  if (col > 0) {
35589
- let left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
35590
- while (left.type !== CellValueType.empty) {
35696
+ let leftPosition = { sheetId, col: col - 1, row };
35697
+ while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
35698
+ this.getters.getCell(leftPosition)?.content) {
35591
35699
  row += 1;
35592
- left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
35700
+ leftPosition = { sheetId, col: col - 1, row };
35593
35701
  }
35594
35702
  }
35595
35703
  if (row === zone.bottom) {
35596
35704
  col = zone.right;
35597
35705
  if (col <= this.getters.getNumberCols(sheetId)) {
35598
- let right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
35599
- while (right.type !== CellValueType.empty) {
35706
+ let rightPosition = { sheetId, col: col + 1, row };
35707
+ while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
35708
+ this.getters.getCell(rightPosition)?.content) {
35600
35709
  row += 1;
35601
- right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
35710
+ rightPosition = { sheetId, col: col + 1, row };
35602
35711
  }
35603
35712
  }
35604
35713
  }
@@ -40546,6 +40655,10 @@ function setXcToReferenceType(xc, referenceType) {
40546
40655
  return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
40547
40656
  case "colrow":
40548
40657
  indexOfNumber = xc.search(/[0-9]/);
40658
+ if (indexOfNumber === -1 || indexOfNumber === 0) {
40659
+ // no row number (eg. A) or no column (eg. 1)
40660
+ return "$" + xc;
40661
+ }
40549
40662
  xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
40550
40663
  return "$" + xc;
40551
40664
  case "none":
@@ -41727,7 +41840,7 @@ class GridSelectionPlugin extends UIPlugin {
41727
41840
  sheetId: cmd.sheetId,
41728
41841
  base: cmd.base,
41729
41842
  quantity: thickness,
41730
- position: "before",
41843
+ position: cmd.position,
41731
41844
  });
41732
41845
  const isCol = cmd.dimension === "COL";
41733
41846
  const start = cmd.elements[0];
@@ -41744,12 +41857,13 @@ class GridSelectionPlugin extends UIPlugin {
41744
41857
  },
41745
41858
  ];
41746
41859
  const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
41860
+ const base = isBasedBefore ? cmd.base : cmd.base + 1;
41747
41861
  const pasteTarget = [
41748
41862
  {
41749
- left: isCol ? cmd.base : 0,
41750
- right: isCol ? cmd.base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
41751
- top: !isCol ? cmd.base : 0,
41752
- bottom: !isCol ? cmd.base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
41863
+ left: isCol ? base : 0,
41864
+ right: isCol ? base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
41865
+ top: !isCol ? base : 0,
41866
+ bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
41753
41867
  },
41754
41868
  ];
41755
41869
  state.paste(pasteTarget, { selectTarget: true });
@@ -41786,6 +41900,11 @@ class GridSelectionPlugin extends UIPlugin {
41786
41900
  doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) {
41787
41901
  return 2 /* CommandResult.WillRemoveExistingMerge */;
41788
41902
  }
41903
+ const headers = [cmd.base, ...cmd.elements];
41904
+ const maxHeaderValue = isCol ? this.getters.getNumberCols(id) : this.getters.getNumberRows(id);
41905
+ if (headers.some((h) => h < 0 || h >= maxHeaderValue)) {
41906
+ return 86 /* CommandResult.InvalidHeaderIndex */;
41907
+ }
41789
41908
  return 0 /* CommandResult.Success */;
41790
41909
  }
41791
41910
  //-------------------------------------------
@@ -44067,7 +44186,7 @@ class Spreadsheet extends Component {
44067
44186
  }
44068
44187
  onKeydown(ev) {
44069
44188
  let keyDownString = "";
44070
- if (ev.ctrlKey || ev.metaKey) {
44189
+ if (isCtrlKey(ev)) {
44071
44190
  keyDownString += "CTRL+";
44072
44191
  }
44073
44192
  keyDownString += ev.key.toUpperCase();
@@ -44938,9 +45057,9 @@ function revertChanges(revisions) {
44938
45057
  * Apply the changes of the given HistoryChange to the state
44939
45058
  */
44940
45059
  function applyChange(change, target) {
44941
- let val = change.root;
44942
- let key = change.path[change.path.length - 1];
44943
- for (let pathIndex = 0; pathIndex < change.path.slice(0, -1).length; pathIndex++) {
45060
+ let val = change.path[0];
45061
+ const key = change.path[change.path.length - 1];
45062
+ for (let pathIndex = 1; pathIndex < change.path.slice(0, -1).length; pathIndex++) {
44944
45063
  const p = change.path[pathIndex];
44945
45064
  if (val[p] === undefined) {
44946
45065
  const nextPath = change.path[pathIndex + 1];
@@ -45607,13 +45726,14 @@ class StateObserver {
45607
45726
  }
45608
45727
  addChange(...args) {
45609
45728
  const val = args.pop();
45610
- const [root, ...path] = args;
45729
+ const root = args[0];
45611
45730
  let value = root;
45612
- let key = path[path.length - 1];
45613
- for (let pathIndex = 0; pathIndex <= path.length - 2; pathIndex++) {
45614
- const p = path[pathIndex];
45731
+ let key = args[args.length - 1];
45732
+ const pathLength = args.length - 2;
45733
+ for (let pathIndex = 1; pathIndex <= pathLength; pathIndex++) {
45734
+ const p = args[pathIndex];
45615
45735
  if (value[p] === undefined) {
45616
- const nextPath = path[pathIndex + 1];
45736
+ const nextPath = args[pathIndex + 1];
45617
45737
  value[p] = createEmptyStructure(nextPath);
45618
45738
  }
45619
45739
  value = value[p];
@@ -45622,8 +45742,7 @@ class StateObserver {
45622
45742
  return;
45623
45743
  }
45624
45744
  this.changes.push({
45625
- root,
45626
- path,
45745
+ path: args,
45627
45746
  before: value[key],
45628
45747
  after: val,
45629
45748
  });
@@ -47473,19 +47592,22 @@ class Model extends EventBus {
47473
47592
  * Check if the given command is allowed by all the plugins and the history.
47474
47593
  */
47475
47594
  checkDispatchAllowed(command) {
47476
- if (isCoreCommand(command)) {
47477
- return this.checkDispatchAllowedCoreCommand(command);
47595
+ const results = isCoreCommand(command)
47596
+ ? this.checkDispatchAllowedCoreCommand(command)
47597
+ : this.checkDispatchAllowedLocalCommand(command);
47598
+ if (results.some((r) => r !== 0 /* CommandResult.Success */)) {
47599
+ return new DispatchResult(results.flat());
47478
47600
  }
47479
- return this.checkDispatchAllowedLocalCommand(command);
47601
+ return DispatchResult.Success;
47480
47602
  }
47481
47603
  checkDispatchAllowedCoreCommand(command) {
47482
47604
  const results = this.corePlugins.map((handler) => handler.allowDispatch(command));
47483
47605
  results.push(this.range.allowDispatch(command));
47484
- return new DispatchResult(results.flat());
47606
+ return results;
47485
47607
  }
47486
47608
  checkDispatchAllowedLocalCommand(command) {
47487
47609
  const results = this.uiHandlers.map((handler) => handler.allowDispatch(command));
47488
- return new DispatchResult(results.flat());
47610
+ return results;
47489
47611
  }
47490
47612
  finalize() {
47491
47613
  this.status = 3 /* Status.Finalizing */;
@@ -47773,6 +47895,6 @@ function addFunction(functionName, functionDescription) {
47773
47895
  export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, components, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, invalidateEvaluationCommands, links, load, parse, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
47774
47896
 
47775
47897
 
47776
- __info__.version = '16.3.17';
47777
- __info__.date = '2023-11-16T11:59:29.130Z';
47778
- __info__.hash = '4049460';
47898
+ __info__.version = '16.3.22';
47899
+ __info__.date = '2024-01-17T10:24:07.652Z';
47900
+ __info__.hash = '2c1b136';