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