@odoo/o-spreadsheet 16.3.18 → 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
  }
@@ -1953,7 +2011,7 @@
1953
2011
  .map((p) => {
1954
2012
  switch (p) {
1955
2013
  case "hhhh":
1956
- 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));
1957
2015
  return helapsedHours.toString();
1958
2016
  case "hh":
1959
2017
  return hours.toString().padStart(2, "0");
@@ -2200,8 +2258,10 @@
2200
2258
  const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
2201
2259
  // Same as above, but matches the exact string (nothing before or after)
2202
2260
  const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
2203
- /** Reference of a column header (eg. A, AB) */
2204
- 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");
2205
2265
  /** Reference of a column (eg. A, $CA, Sheet1!B) */
2206
2266
  const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
2207
2267
  /** Reference of a row (eg. 1, 59, Sheet1!9) */
@@ -2231,6 +2291,9 @@
2231
2291
  function isColHeader(str) {
2232
2292
  return colHeader.test(str);
2233
2293
  }
2294
+ function isRowHeader(str) {
2295
+ return rowHeader.test(str);
2296
+ }
2234
2297
  /**
2235
2298
  * Return true if the given xc is the reference of a single cell,
2236
2299
  * without any specified sheet (e.g. A1)
@@ -2297,18 +2360,10 @@
2297
2360
  if (isFullCol) {
2298
2361
  parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2299
2362
  parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2300
- if (zone.left === zone.right) {
2301
- parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2302
- parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2303
- }
2304
2363
  }
2305
2364
  if (isFullRow) {
2306
2365
  parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2307
2366
  parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2308
- if (zone.top === zone.bottom) {
2309
- parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2310
- parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2311
- }
2312
2367
  }
2313
2368
  return parts;
2314
2369
  }
@@ -5168,6 +5223,7 @@
5168
5223
  },
5169
5224
  };
5170
5225
 
5226
+ const macRegex = /Mac/i;
5171
5227
  /**
5172
5228
  * Return true if the event was triggered from
5173
5229
  * a child element.
@@ -5213,6 +5269,17 @@
5213
5269
  }
5214
5270
  function getOpenedMenus() {
5215
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;
5216
5283
  }
5217
5284
 
5218
5285
  /**
@@ -9599,6 +9666,9 @@
9599
9666
  function makeArg(str, description) {
9600
9667
  let parts = str.match(ARG_REGEXP);
9601
9668
  let name = parts[1].trim();
9669
+ if (!name) {
9670
+ throw new Error(`Function argument definition is missing a name: '${str}'.`);
9671
+ }
9602
9672
  let types = [];
9603
9673
  let isOptional = false;
9604
9674
  let isRepeating = false;
@@ -12052,7 +12122,7 @@
12052
12122
  if (_year < 1900) {
12053
12123
  _year += 1900;
12054
12124
  }
12055
- const jsDate = new Date(_year, _month - 1, _day);
12125
+ const jsDate = new DateTime(_year, _month - 1, _day);
12056
12126
  const result = jsDateToRoundNumber(jsDate);
12057
12127
  assert(() => result >= 0, _lt(`The function [[FUNCTION_NAME]] result must be greater than or equal 01/01/1900.`));
12058
12128
  return result;
@@ -12095,7 +12165,7 @@
12095
12165
  // See: https://support.microsoft.com/en-us/office/datedif-function-25dba1a4-2812-480b-84dd-8b32a451b35c
12096
12166
  let days = jsEndDate.getDate() - jsStartDate.getDate();
12097
12167
  if (days < 0) {
12098
- const monthBeforeEndMonth = new Date(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
12168
+ const monthBeforeEndMonth = new DateTime(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
12099
12169
  const daysInMonthBeforeEndMonth = getDaysInMonth(monthBeforeEndMonth);
12100
12170
  days = daysInMonthBeforeEndMonth - Math.abs(days);
12101
12171
  }
@@ -12104,7 +12174,7 @@
12104
12174
  if (areTwoDatesWithinOneYear(_startDate, _endDate)) {
12105
12175
  return getTimeDifferenceInWholeDays(jsStartDate, jsEndDate);
12106
12176
  }
12107
- const endDateWithinOneYear = new Date(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
12177
+ const endDateWithinOneYear = new DateTime(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
12108
12178
  let days = getTimeDifferenceInWholeDays(jsStartDate, endDateWithinOneYear);
12109
12179
  if (days < 0) {
12110
12180
  endDateWithinOneYear.setFullYear(jsStartDate.getFullYear() + 1);
@@ -12217,7 +12287,7 @@
12217
12287
  const _months = Math.trunc(toNumber(months));
12218
12288
  const yStart = _startDate.getFullYear();
12219
12289
  const mStart = _startDate.getMonth();
12220
- const jsDate = new Date(yStart, mStart + _months + 1, 0);
12290
+ const jsDate = new DateTime(yStart, mStart + _months + 1, 0);
12221
12291
  return jsDateToRoundNumber(jsDate);
12222
12292
  },
12223
12293
  isExported: true,
@@ -12254,17 +12324,17 @@
12254
12324
  // The first week of the year is the week that contains the first
12255
12325
  // Thursday of the year.
12256
12326
  let firstThursday = 1;
12257
- while (new Date(y, 0, firstThursday).getDay() !== 4) {
12327
+ while (new DateTime(y, 0, firstThursday).getDay() !== 4) {
12258
12328
  firstThursday += 1;
12259
12329
  }
12260
- const firstDayOfFirstWeek = new Date(y, 0, firstThursday - 3);
12330
+ const firstDayOfFirstWeek = new DateTime(y, 0, firstThursday - 3);
12261
12331
  // The last week of the year is the week that contains the last Thursday of
12262
12332
  // the year.
12263
12333
  let lastThursday = 31;
12264
- while (new Date(y, 11, lastThursday).getDay() !== 4) {
12334
+ while (new DateTime(y, 11, lastThursday).getDay() !== 4) {
12265
12335
  lastThursday -= 1;
12266
12336
  }
12267
- const lastDayOfLastWeek = new Date(y, 11, lastThursday + 3);
12337
+ const lastDayOfLastWeek = new DateTime(y, 11, lastThursday + 3);
12268
12338
  // B - If our date > lastDayOfLastWeek then it's in the weeks of the year after
12269
12339
  // If our date < firstDayOfFirstWeek then it's in the weeks of the year before
12270
12340
  let offsetYear;
@@ -12290,17 +12360,17 @@
12290
12360
  case 1:
12291
12361
  // firstDay is the 1st day of the 1st week of the year after
12292
12362
  // firstDay = lastDayOfLastWeek + 1 Day
12293
- firstDay = new Date(y, 11, lastThursday + 3 + 1);
12363
+ firstDay = new DateTime(y, 11, lastThursday + 3 + 1);
12294
12364
  break;
12295
12365
  case -1:
12296
12366
  // firstDay is the 1st day of the 1st week of the previous year.
12297
12367
  // The first week of the previous year is the week that contains the
12298
12368
  // first Thursday of the previous year.
12299
12369
  let firstThursdayPreviousYear = 1;
12300
- while (new Date(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
12370
+ while (new DateTime(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
12301
12371
  firstThursdayPreviousYear += 1;
12302
12372
  }
12303
- firstDay = new Date(y - 1, 0, firstThursdayPreviousYear - 3);
12373
+ firstDay = new DateTime(y - 1, 0, firstThursdayPreviousYear - 3);
12304
12374
  break;
12305
12375
  }
12306
12376
  const diff = (_date.getTime() - firstDay.getTime()) / MS_PER_DAY;
@@ -12435,8 +12505,8 @@
12435
12505
  });
12436
12506
  }
12437
12507
  const invertDate = _startDate.getTime() > _endDate.getTime();
12438
- const stopDate = new Date((invertDate ? _startDate : _endDate).getTime());
12439
- 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());
12440
12510
  const timeStopDate = stopDate.getTime();
12441
12511
  let timeStepDate = stepDate.getTime();
12442
12512
  let netWorkingDay = 0;
@@ -12460,8 +12530,7 @@
12460
12530
  returns: ["DATE"],
12461
12531
  computeFormat: () => "m/d/yyyy hh:mm:ss",
12462
12532
  compute: function () {
12463
- let today = new Date();
12464
- today.setMilliseconds(0);
12533
+ let today = DateTime.now();
12465
12534
  const delta = today.getTime() - INITIAL_1900_DAY.getTime();
12466
12535
  const time = today.getHours() / 24 + today.getMinutes() / 1440 + today.getSeconds() / 86400;
12467
12536
  return Math.floor(delta / MS_PER_DAY) + time;
@@ -12531,8 +12600,8 @@
12531
12600
  returns: ["DATE"],
12532
12601
  computeFormat: () => "m/d/yyyy",
12533
12602
  compute: function () {
12534
- const today = new Date();
12535
- 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());
12536
12605
  return jsDateToRoundNumber(jsDate);
12537
12606
  },
12538
12607
  isExported: true,
@@ -12587,10 +12656,10 @@
12587
12656
  }
12588
12657
  const y = _date.getFullYear();
12589
12658
  let dayStart = 1;
12590
- let startDayOfFirstWeek = new Date(y, 0, dayStart);
12659
+ let startDayOfFirstWeek = new DateTime(y, 0, dayStart);
12591
12660
  while (startDayOfFirstWeek.getDay() !== startDayOfWeek) {
12592
12661
  dayStart += 1;
12593
- startDayOfFirstWeek = new Date(y, 0, dayStart);
12662
+ startDayOfFirstWeek = new DateTime(y, 0, dayStart);
12594
12663
  }
12595
12664
  const dif = (_date.getTime() - startDayOfFirstWeek.getTime()) / MS_PER_DAY;
12596
12665
  if (dif < 0) {
@@ -12644,7 +12713,7 @@
12644
12713
  timesHoliday.add(holiday.getTime());
12645
12714
  });
12646
12715
  }
12647
- let stepDate = new Date(_startDate.getTime());
12716
+ let stepDate = DateTime.fromTimestamp(_startDate.getTime());
12648
12717
  let timeStepDate = stepDate.getTime();
12649
12718
  const unitDay = Math.sign(_numDays);
12650
12719
  let stepDay = Math.abs(_numDays);
@@ -12706,7 +12775,7 @@
12706
12775
  const _startDate = toJsDate(date);
12707
12776
  const yStart = _startDate.getFullYear();
12708
12777
  const mStart = _startDate.getMonth();
12709
- const jsDate = new Date(yStart, mStart, 1);
12778
+ const jsDate = new DateTime(yStart, mStart, 1);
12710
12779
  return jsDateToRoundNumber(jsDate);
12711
12780
  },
12712
12781
  };
@@ -12744,7 +12813,7 @@
12744
12813
  compute: function (date) {
12745
12814
  const quarter = QUARTER.compute(date);
12746
12815
  const year = YEAR.compute(date);
12747
- const jsDate = new Date(year, (quarter - 1) * 3, 1);
12816
+ const jsDate = new DateTime(year, (quarter - 1) * 3, 1);
12748
12817
  return jsDateToRoundNumber(jsDate);
12749
12818
  },
12750
12819
  };
@@ -12759,7 +12828,7 @@
12759
12828
  compute: function (date) {
12760
12829
  const quarter = QUARTER.compute(date);
12761
12830
  const year = YEAR.compute(date);
12762
- const jsDate = new Date(year, quarter * 3, 0);
12831
+ const jsDate = new DateTime(year, quarter * 3, 0);
12763
12832
  return jsDateToRoundNumber(jsDate);
12764
12833
  },
12765
12834
  };
@@ -12773,7 +12842,7 @@
12773
12842
  computeFormat: () => "m/d/yyyy",
12774
12843
  compute: function (date) {
12775
12844
  const year = YEAR.compute(date);
12776
- const jsDate = new Date(year, 0, 1);
12845
+ const jsDate = new DateTime(year, 0, 1);
12777
12846
  return jsDateToRoundNumber(jsDate);
12778
12847
  },
12779
12848
  };
@@ -12787,7 +12856,7 @@
12787
12856
  computeFormat: () => "m/d/yyyy",
12788
12857
  compute: function (date) {
12789
12858
  const year = YEAR.compute(date);
12790
- const jsDate = new Date(year + 1, 0, 0);
12859
+ const jsDate = new DateTime(year + 1, 0, 0);
12791
12860
  return jsDateToRoundNumber(jsDate);
12792
12861
  },
12793
12862
  };
@@ -12835,8 +12904,8 @@
12835
12904
  const DELTA = {
12836
12905
  description: _lt("Compare two numeric values, returning 1 if they're equal."),
12837
12906
  args: [
12838
- arg(" (number)", _lt("The first number to compare.")),
12839
- 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.")),
12840
12909
  ],
12841
12910
  returns: ["NUMBER"],
12842
12911
  compute: function (number1, number2 = DEFAULT_DELTA_ARG) {
@@ -12922,7 +12991,7 @@
12922
12991
  function assertSettlementLessThanOneYearBeforeMaturity(settlement, maturity) {
12923
12992
  const startDate = toJsDate(settlement);
12924
12993
  const endDate = toJsDate(maturity);
12925
- const startDatePlusOneYear = new Date(startDate);
12994
+ const startDatePlusOneYear = toJsDate(settlement);
12926
12995
  startDatePlusOneYear.setFullYear(startDate.getFullYear() + 1);
12927
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()));
12928
12997
  }
@@ -13057,7 +13126,7 @@
13057
13126
  arg("salvage (number)", _lt("The value of the asset at the end of depreciation.")),
13058
13127
  arg("period (number)", _lt("The single period within life for which to calculate depreciation.")),
13059
13128
  arg("rate (number)", _lt("The deprecation rate.")),
13060
- 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.")),
13061
13130
  ],
13062
13131
  returns: ["NUMBER"],
13063
13132
  compute: function (cost, purchaseDate, firstPeriodEnd, salvage, period, rate, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {
@@ -16040,8 +16109,8 @@
16040
16109
  description: _lt("A segment of a string."),
16041
16110
  args: [
16042
16111
  arg("text (string)", _lt("The string to extract a segment from.")),
16043
- arg(" (number)", _lt("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
16044
- 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.")),
16045
16114
  ],
16046
16115
  returns: ["STRING"],
16047
16116
  compute: function (text, starting_at, extract_length) {
@@ -17710,10 +17779,10 @@
17710
17779
  function updateSelectionWithArrowKeys(ev, selection) {
17711
17780
  const direction = arrowMap[ev.key];
17712
17781
  if (ev.shiftKey) {
17713
- selection.resizeAnchorZone(direction, ev.ctrlKey ? "end" : 1);
17782
+ selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
17714
17783
  }
17715
17784
  else {
17716
- selection.moveAnchorCell(direction, ev.ctrlKey ? "end" : 1);
17785
+ selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
17717
17786
  }
17718
17787
  }
17719
17788
 
@@ -22410,7 +22479,10 @@
22410
22479
  return;
22411
22480
  }
22412
22481
  const [col, row] = this.getCartesianCoordinates(ev);
22413
- 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
+ });
22414
22486
  }
22415
22487
  onDoubleClick(ev) {
22416
22488
  const [col, row] = this.getCartesianCoordinates(ev);
@@ -22483,6 +22555,7 @@
22483
22555
  draggerShadowThickness: 0,
22484
22556
  delta: 0,
22485
22557
  base: 0,
22558
+ position: "before",
22486
22559
  });
22487
22560
  _computeHandleDisplay(ev) {
22488
22561
  const position = this._getEvOffset(ev);
@@ -22607,11 +22680,13 @@
22607
22680
  this.state.draggerLinePosition = dimensions.start;
22608
22681
  this.state.draggerShadowPosition = dimensions.start;
22609
22682
  this.state.base = elementIndex;
22683
+ this.state.position = "before";
22610
22684
  }
22611
22685
  else if (this._getSelectedZoneEnd() < elementIndex) {
22612
22686
  this.state.draggerLinePosition = dimensions.end;
22613
22687
  this.state.draggerShadowPosition = dimensions.end - this.state.draggerShadowThickness;
22614
- this.state.base = elementIndex + 1;
22688
+ this.state.base = elementIndex;
22689
+ this.state.position = "after";
22615
22690
  }
22616
22691
  else {
22617
22692
  this.state.draggerLinePosition = startDimensions.start;
@@ -22635,7 +22710,7 @@
22635
22710
  this._increaseSelection(index);
22636
22711
  }
22637
22712
  else {
22638
- this._selectElement(index, ev.ctrlKey);
22713
+ this._selectElement(index, isCtrlKey(ev));
22639
22714
  }
22640
22715
  this.lastSelectedElementIndex = index;
22641
22716
  const mouseMoveSelect = (col, row) => {
@@ -22648,7 +22723,7 @@
22648
22723
  const mouseUpSelect = () => {
22649
22724
  this.state.isSelecting = false;
22650
22725
  this.lastSelectedElementIndex = null;
22651
- 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");
22652
22727
  this._computeGrabDisplay(ev);
22653
22728
  };
22654
22729
  dragAndDropBeyondTheViewport(this.env, mouseMoveSelect, mouseUpSelect);
@@ -22794,13 +22869,14 @@
22794
22869
  dimension: "COL",
22795
22870
  base: this.state.base,
22796
22871
  elements,
22872
+ position: this.state.position,
22797
22873
  });
22798
22874
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
22799
22875
  this.env.raiseError(MergeErrorMessage);
22800
22876
  }
22801
22877
  }
22802
- _selectElement(index, ctrlKey) {
22803
- this.env.model.selection.selectColumn(index, ctrlKey ? "newAnchor" : "overrideSelection");
22878
+ _selectElement(index, addDistinctHeader) {
22879
+ this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
22804
22880
  }
22805
22881
  _increaseSelection(index) {
22806
22882
  this.env.model.selection.selectColumn(index, "updateAnchor");
@@ -22967,13 +23043,14 @@
22967
23043
  dimension: "ROW",
22968
23044
  base: this.state.base,
22969
23045
  elements,
23046
+ position: this.state.position,
22970
23047
  });
22971
23048
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
22972
23049
  this.env.raiseError(MergeErrorMessage);
22973
23050
  }
22974
23051
  }
22975
- _selectElement(index, ctrlKey) {
22976
- this.env.model.selection.selectRow(index, ctrlKey ? "newAnchor" : "overrideSelection");
23052
+ _selectElement(index, addDistinctHeader) {
23053
+ this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
22977
23054
  }
22978
23055
  _increaseSelection(index) {
22979
23056
  this.env.model.selection.selectRow(index, "updateAnchor");
@@ -23074,8 +23151,8 @@
23074
23151
  return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT);
23075
23152
  }
23076
23153
  const onMouseWheel = (ev) => {
23077
- const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode);
23078
- 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);
23079
23156
  handler(deltaX, deltaY);
23080
23157
  };
23081
23158
  return onMouseWheel;
@@ -23777,8 +23854,8 @@
23777
23854
  // ---------------------------------------------------------------------------
23778
23855
  // Zone selection with mouse
23779
23856
  // ---------------------------------------------------------------------------
23780
- onCellClicked(col, row, { ctrlKey, shiftKey }) {
23781
- if (ctrlKey) {
23857
+ onCellClicked(col, row, { addZone, expandZone }) {
23858
+ if (addZone) {
23782
23859
  this.env.model.dispatch("PREPARE_SELECTION_INPUT_EXPANSION");
23783
23860
  }
23784
23861
  if (this.env.model.getters.hasOpenedPopover()) {
@@ -23787,10 +23864,10 @@
23787
23864
  if (this.env.model.getters.getEditionMode() === "editing") {
23788
23865
  this.env.model.dispatch("STOP_EDITION");
23789
23866
  }
23790
- if (shiftKey) {
23867
+ if (expandZone) {
23791
23868
  this.env.model.selection.setAnchorCorner(col, row);
23792
23869
  }
23793
- else if (ctrlKey) {
23870
+ else if (addZone) {
23794
23871
  this.env.model.selection.addCellToSelection(col, row);
23795
23872
  }
23796
23873
  else {
@@ -23852,9 +23929,7 @@
23852
23929
  return;
23853
23930
  }
23854
23931
  let keyDownString = "";
23855
- if (ev.ctrlKey)
23856
- keyDownString += "CTRL+";
23857
- if (ev.metaKey)
23932
+ if (isCtrlKey(ev))
23858
23933
  keyDownString += "CTRL+";
23859
23934
  if (ev.altKey)
23860
23935
  keyDownString += "ALT+";
@@ -25762,7 +25837,7 @@
25762
25837
  SPACE: goTo(State.RightRef),
25763
25838
  NUMBER: goTo(State.Found),
25764
25839
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
25765
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
25840
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
25766
25841
  },
25767
25842
  [State.RightColumnRef]: {
25768
25843
  SPACE: goTo(State.RightColumnRef),
@@ -25773,6 +25848,7 @@
25773
25848
  SPACE: goTo(State.RightRowRef),
25774
25849
  NUMBER: goTo(State.Found),
25775
25850
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
25851
+ SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
25776
25852
  },
25777
25853
  [State.Found]: {},
25778
25854
  };
@@ -29480,7 +29556,7 @@
29480
29556
  */
29481
29557
  function getBorderId(border) {
29482
29558
  for (let [key, value] of Object.entries(borders)) {
29483
- if (stringify(value) === stringify(border)) {
29559
+ if (deepEquals(value, border)) {
29484
29560
  return parseInt(key, 10);
29485
29561
  }
29486
29562
  }
@@ -32448,7 +32524,7 @@
32448
32524
  const elements = cmd.dimension === "COL"
32449
32525
  ? this.getNumberCols(cmd.sheetId)
32450
32526
  : this.getNumberRows(cmd.sheetId);
32451
- if (cmd.base < 0 || cmd.base > elements) {
32527
+ if (cmd.base < 0 || cmd.base >= elements) {
32452
32528
  return 86 /* CommandResult.InvalidHeaderIndex */;
32453
32529
  }
32454
32530
  else if (cmd.quantity <= 0) {
@@ -33481,13 +33557,18 @@
33481
33557
  // Getters
33482
33558
  // ---------------------------------------------------------------------------
33483
33559
  evaluateFormula(formulaString, sheetId = this.getters.getActiveSheetId()) {
33484
- const compiledFormula = compile(formulaString);
33485
- const params = this.getCompilationParameters((cell) => this.getEvaluatedCell(this.getters.getCellPosition(cell.id)));
33486
- const ranges = [];
33487
- for (let xc of compiledFormula.dependencies) {
33488
- 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;
33489
33571
  }
33490
- return compiledFormula.execute(ranges, ...params).value;
33491
33572
  }
33492
33573
  /**
33493
33574
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -33912,39 +33993,34 @@
33912
33993
  this.computedIcons[sheetId] = {};
33913
33994
  const computedStyle = this.computedStyles[sheetId];
33914
33995
  for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
33915
- try {
33916
- switch (cf.rule.type) {
33917
- case "ColorScaleRule":
33918
- for (let range of cf.ranges) {
33919
- this.applyColorScale(range, cf.rule);
33920
- }
33921
- break;
33922
- case "IconSetRule":
33923
- for (let range of cf.ranges) {
33924
- this.applyIcon(range, cf.rule);
33925
- }
33926
- break;
33927
- default:
33928
- for (let ref of cf.ranges) {
33929
- const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
33930
- for (let row = zone.top; row <= zone.bottom; row++) {
33931
- for (let col = zone.left; col <= zone.right; col++) {
33932
- const pr = this.rulePredicate[cf.rule.type];
33933
- let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
33934
- if (pr && pr(cell, cf.rule)) {
33935
- if (!computedStyle[col])
33936
- computedStyle[col] = [];
33937
- // we must combine all the properties of all the CF rules applied to the given cell
33938
- computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
33939
- }
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);
33940
34019
  }
33941
34020
  }
33942
34021
  }
33943
- break;
33944
- }
33945
- }
33946
- catch (_) {
33947
- // we don't care about the errors within the evaluation of a rule
34022
+ }
34023
+ break;
33948
34024
  }
33949
34025
  }
33950
34026
  }
@@ -33969,7 +34045,7 @@
33969
34045
  return percentile(rangeValues, Number(threshold.value) / 100, true);
33970
34046
  case "formula":
33971
34047
  const value = threshold.value && this.getters.evaluateFormula(threshold.value);
33972
- return !(value instanceof Promise) ? value : null;
34048
+ return typeof value === "number" ? value : null;
33973
34049
  default:
33974
34050
  return null;
33975
34051
  }
@@ -35618,19 +35694,21 @@
35618
35694
  let col = zone.left;
35619
35695
  let row = zone.bottom;
35620
35696
  if (col > 0) {
35621
- let left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
35622
- 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) {
35623
35700
  row += 1;
35624
- left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
35701
+ leftPosition = { sheetId, col: col - 1, row };
35625
35702
  }
35626
35703
  }
35627
35704
  if (row === zone.bottom) {
35628
35705
  col = zone.right;
35629
35706
  if (col <= this.getters.getNumberCols(sheetId)) {
35630
- let right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
35631
- 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) {
35632
35710
  row += 1;
35633
- right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
35711
+ rightPosition = { sheetId, col: col + 1, row };
35634
35712
  }
35635
35713
  }
35636
35714
  }
@@ -40578,6 +40656,10 @@
40578
40656
  return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
40579
40657
  case "colrow":
40580
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
+ }
40581
40663
  xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
40582
40664
  return "$" + xc;
40583
40665
  case "none":
@@ -41759,7 +41841,7 @@
41759
41841
  sheetId: cmd.sheetId,
41760
41842
  base: cmd.base,
41761
41843
  quantity: thickness,
41762
- position: "before",
41844
+ position: cmd.position,
41763
41845
  });
41764
41846
  const isCol = cmd.dimension === "COL";
41765
41847
  const start = cmd.elements[0];
@@ -41776,12 +41858,13 @@
41776
41858
  },
41777
41859
  ];
41778
41860
  const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
41861
+ const base = isBasedBefore ? cmd.base : cmd.base + 1;
41779
41862
  const pasteTarget = [
41780
41863
  {
41781
- left: isCol ? cmd.base : 0,
41782
- right: isCol ? cmd.base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
41783
- top: !isCol ? cmd.base : 0,
41784
- 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,
41785
41868
  },
41786
41869
  ];
41787
41870
  state.paste(pasteTarget, { selectTarget: true });
@@ -41818,6 +41901,11 @@
41818
41901
  doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) {
41819
41902
  return 2 /* CommandResult.WillRemoveExistingMerge */;
41820
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
+ }
41821
41909
  return 0 /* CommandResult.Success */;
41822
41910
  }
41823
41911
  //-------------------------------------------
@@ -44099,7 +44187,7 @@
44099
44187
  }
44100
44188
  onKeydown(ev) {
44101
44189
  let keyDownString = "";
44102
- if (ev.ctrlKey || ev.metaKey) {
44190
+ if (isCtrlKey(ev)) {
44103
44191
  keyDownString += "CTRL+";
44104
44192
  }
44105
44193
  keyDownString += ev.key.toUpperCase();
@@ -47838,9 +47926,9 @@
47838
47926
  Object.defineProperty(exports, '__esModule', { value: true });
47839
47927
 
47840
47928
 
47841
- __info__.version = '16.3.18';
47842
- __info__.date = '2023-11-24T13:10:15.278Z';
47843
- __info__.hash = '45f745e';
47929
+ __info__.version = '16.3.22';
47930
+ __info__.date = '2024-01-17T10:24:07.652Z';
47931
+ __info__.hash = '2c1b136';
47844
47932
 
47845
47933
 
47846
47934
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);