@odoo/o-spreadsheet 16.1.28 → 16.1.31

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.
@@ -309,15 +309,76 @@ for (let font of fontSizes) {
309
309
  // -----------------------------------------------------------------------------
310
310
  // Date Type
311
311
  // -----------------------------------------------------------------------------
312
+ /**
313
+ * A DateTime object that can be used to manipulate spreadsheet dates.
314
+ * Conceptually, a spreadsheet date is simply a number with a date format,
315
+ * and it is timezone-agnostic.
316
+ * This DateTime object consistently uses UTC time to represent a naive date and time.
317
+ */
318
+ class DateTime {
319
+ constructor(year, month, day, hours = 0, minutes = 0, seconds = 0) {
320
+ this.jsDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds, 0));
321
+ }
322
+ static fromTimestamp(timestamp) {
323
+ const date = new Date(timestamp);
324
+ return new DateTime(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
325
+ }
326
+ static now() {
327
+ const now = new Date();
328
+ return new DateTime(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds());
329
+ }
330
+ toString() {
331
+ return this.jsDate.toString();
332
+ }
333
+ getTime() {
334
+ return this.jsDate.getTime();
335
+ }
336
+ getFullYear() {
337
+ return this.jsDate.getUTCFullYear();
338
+ }
339
+ getMonth() {
340
+ return this.jsDate.getUTCMonth();
341
+ }
342
+ getDate() {
343
+ return this.jsDate.getUTCDate();
344
+ }
345
+ getDay() {
346
+ return this.jsDate.getUTCDay();
347
+ }
348
+ getHours() {
349
+ return this.jsDate.getUTCHours();
350
+ }
351
+ getMinutes() {
352
+ return this.jsDate.getUTCMinutes();
353
+ }
354
+ getSeconds() {
355
+ return this.jsDate.getUTCSeconds();
356
+ }
357
+ setFullYear(year) {
358
+ this.jsDate.setFullYear(year);
359
+ }
360
+ setDate(date) {
361
+ this.jsDate.setUTCDate(date);
362
+ }
363
+ setHours(hours) {
364
+ this.jsDate.setUTCHours(hours);
365
+ }
366
+ setMinutes(minutes) {
367
+ this.jsDate.setUTCMinutes(minutes);
368
+ }
369
+ setSeconds(seconds) {
370
+ this.jsDate.setUTCSeconds(seconds);
371
+ }
372
+ }
312
373
  // -----------------------------------------------------------------------------
313
374
  // Parsing
314
375
  // -----------------------------------------------------------------------------
315
- const INITIAL_1900_DAY = new Date(1899, 11, 30);
376
+ const INITIAL_1900_DAY = new DateTime(1899, 11, 30);
316
377
  const MS_PER_DAY = 24 * 60 * 60 * 1000;
317
378
  const CURRENT_MILLENIAL = 2000; // note: don't forget to update this in 2999
318
- const CURRENT_YEAR = new Date().getFullYear();
319
- const INITIAL_JS_DAY = new Date(0);
320
- const DATE_JS_1900_OFFSET = INITIAL_JS_DAY - INITIAL_1900_DAY;
379
+ const CURRENT_YEAR = DateTime.now().getFullYear();
380
+ const INITIAL_JS_DAY = DateTime.fromTimestamp(0);
381
+ const DATE_JS_1900_OFFSET = INITIAL_JS_DAY.getTime() - INITIAL_1900_DAY.getTime();
321
382
  const mdyDateRegexp = /^\d{1,2}(\/|-|\s)\d{1,2}((\/|-|\s)\d{1,4})?$/;
322
383
  const ymdDateRegexp = /^\d{3,4}(\/|-|\s)\d{1,2}(\/|-|\s)\d{1,2}$/;
323
384
  const timeRegexp = /((\d+(:\d+)?(:\d+)?\s*(AM|PM))|(\d+:\d+(:\d+)?))$/;
@@ -366,7 +427,7 @@ function _parseDateTime(str) {
366
427
  return {
367
428
  value: date.value + time.value,
368
429
  format: date.format + " " + (time.format === "hhhh:mm:ss" ? "hh:mm:ss" : time.format),
369
- 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()),
430
+ 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()),
370
431
  };
371
432
  }
372
433
  return date || time;
@@ -383,13 +444,13 @@ function parseDate(str, dateFormat) {
383
444
  const day = Number(parts[dayIndex]);
384
445
  const leadingZero = (parts[monthIndex].length === 2 && month < 10) || (parts[dayIndex].length === 2 && day < 10);
385
446
  const year = parts[yearIndex] ? inferYear(parts[yearIndex]) : CURRENT_YEAR;
386
- const jsDate = new Date(year, month - 1, day);
447
+ const jsDate = new DateTime(year, month - 1, day);
387
448
  const sep = str.match(/\/|-|\s/)[0];
388
449
  if (jsDate.getMonth() !== month - 1 || jsDate.getDate() !== day) {
389
450
  // invalid date
390
451
  return null;
391
452
  }
392
- const delta = jsDate - INITIAL_1900_DAY;
453
+ const delta = jsDate.getTime() - INITIAL_1900_DAY.getTime();
393
454
  let format = leadingZero ? `mm${sep}dd` : `m${sep}d`;
394
455
  if (parts[yearIndex]) {
395
456
  format = isMDY ? format + sep + "yyyy" : "yyyy" + sep + format;
@@ -449,7 +510,7 @@ function parseTime(str) {
449
510
  if (hours >= 24) {
450
511
  format = "hhhh:mm:ss";
451
512
  }
452
- const jsDate = new Date(1899, 11, 30, hours, minutes, seconds);
513
+ const jsDate = new DateTime(1899, 11, 30, hours, minutes, seconds);
453
514
  return {
454
515
  value: hours / 24 + minutes / 1440 + seconds / 86400,
455
516
  format: format,
@@ -463,7 +524,7 @@ function parseTime(str) {
463
524
  // -----------------------------------------------------------------------------
464
525
  function numberToJsDate(value) {
465
526
  const truncValue = Math.trunc(value);
466
- let date = new Date(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
527
+ let date = DateTime.fromTimestamp(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
467
528
  let time = value - truncValue;
468
529
  time = time < 0 ? 1 + time : time;
469
530
  const hours = Math.round(time * 24);
@@ -480,7 +541,7 @@ function jsDateToRoundNumber(date) {
480
541
  }
481
542
  /** Return the number of days in the current month of the given date */
482
543
  function getDaysInMonth(date) {
483
- return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
544
+ return new DateTime(date.getFullYear(), date.getMonth() + 1, 0).getDate();
484
545
  }
485
546
  function isLastDayOfMonth(date) {
486
547
  return getDaysInMonth(date) === date.getDate();
@@ -498,7 +559,7 @@ function addMonthsToDate(date, months, keepEndOfMonth) {
498
559
  const yStart = date.getFullYear();
499
560
  const mStart = date.getMonth();
500
561
  const dStart = date.getDate();
501
- const jsDate = new Date(yStart, mStart + months);
562
+ const jsDate = new DateTime(yStart, mStart + months, 1);
502
563
  if (keepEndOfMonth && dStart === getDaysInMonth(date)) {
503
564
  jsDate.setDate(getDaysInMonth(jsDate));
504
565
  }
@@ -1347,8 +1408,10 @@ function isSameColor(color1, color2) {
1347
1408
  const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
1348
1409
  // Same as above, but matches the exact string (nothing before or after)
1349
1410
  const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
1350
- /** Reference of a column header (eg. A, AB) */
1351
- const colHeader = new RegExp(/^([A-Z]{1,3})+$/, "i");
1411
+ /** Reference of a column header (eg. A, AB, $A) */
1412
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
1413
+ /** Reference of a row header (eg. 1, $1) */
1414
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
1352
1415
  /** Reference of a column (eg. A, $CA, Sheet1!B) */
1353
1416
  const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
1354
1417
  /** Reference of a row (eg. 1, 59, Sheet1!9) */
@@ -1378,6 +1441,9 @@ function isRowReference(xc) {
1378
1441
  function isColHeader(str) {
1379
1442
  return colHeader.test(str);
1380
1443
  }
1444
+ function isRowHeader(str) {
1445
+ return rowHeader.test(str);
1446
+ }
1381
1447
  /**
1382
1448
  * Return true if the given xc is the reference of a single cell,
1383
1449
  * without any specified sheet (e.g. A1)
@@ -1876,7 +1942,7 @@ function formatJSTime(jsDate, format) {
1876
1942
  .map((p) => {
1877
1943
  switch (p) {
1878
1944
  case "hhhh":
1879
- const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY) / (60 * 60 * 1000));
1945
+ const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY.getTime()) / (60 * 60 * 1000));
1880
1946
  return helapsedHours.toString();
1881
1947
  case "hh":
1882
1948
  return hours.toString().padStart(2, "0");
@@ -2167,18 +2233,10 @@ class RangeImpl {
2167
2233
  if (isFullCol) {
2168
2234
  parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2169
2235
  parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2170
- if (zone.left === zone.right) {
2171
- parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2172
- parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2173
- }
2174
2236
  }
2175
2237
  if (isFullRow) {
2176
2238
  parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2177
2239
  parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2178
- if (zone.top === zone.bottom) {
2179
- parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2180
- parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2181
- }
2182
2240
  }
2183
2241
  return parts;
2184
2242
  }
@@ -5705,6 +5763,7 @@ function getMenuDescription(node) {
5705
5763
  return node.description ? node.description : "";
5706
5764
  }
5707
5765
 
5766
+ const macRegex = /Mac/i;
5708
5767
  /**
5709
5768
  * Return true if the event was triggered from
5710
5769
  * a child element.
@@ -5722,6 +5781,17 @@ function gridOverlayPosition() {
5722
5781
  }
5723
5782
  function getOpenedMenus() {
5724
5783
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
5784
+ }
5785
+ function isMacOS() {
5786
+ return Boolean(macRegex.test(navigator.userAgent));
5787
+ }
5788
+ /**
5789
+ * @param {KeyboardEvent | MouseEvent} ev
5790
+ * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
5791
+ * On Mac, this is the "meta" or "command" key.
5792
+ */
5793
+ function isCtrlKey(ev) {
5794
+ return isMacOS() ? ev.metaKey : ev.ctrlKey;
5725
5795
  }
5726
5796
 
5727
5797
  /**
@@ -10882,7 +10952,7 @@ const DATE = {
10882
10952
  if (_year < 1900) {
10883
10953
  _year += 1900;
10884
10954
  }
10885
- const jsDate = new Date(_year, _month - 1, _day);
10955
+ const jsDate = new DateTime(_year, _month - 1, _day);
10886
10956
  const result = jsDateToRoundNumber(jsDate);
10887
10957
  assert(() => result >= 0, _lt(`The function [[FUNCTION_NAME]] result must be greater than or equal 01/01/1900.`));
10888
10958
  return result;
@@ -10994,7 +11064,7 @@ const EOMONTH = {
10994
11064
  const _months = Math.trunc(toNumber(months));
10995
11065
  const yStart = _startDate.getFullYear();
10996
11066
  const mStart = _startDate.getMonth();
10997
- const jsDate = new Date(yStart, mStart + _months + 1, 0);
11067
+ const jsDate = new DateTime(yStart, mStart + _months + 1, 0);
10998
11068
  return jsDateToRoundNumber(jsDate);
10999
11069
  },
11000
11070
  isExported: true,
@@ -11033,17 +11103,17 @@ const ISOWEEKNUM = {
11033
11103
  // The first week of the year is the week that contains the first
11034
11104
  // Thursday of the year.
11035
11105
  let firstThursday = 1;
11036
- while (new Date(y, 0, firstThursday).getDay() !== 4) {
11106
+ while (new DateTime(y, 0, firstThursday).getDay() !== 4) {
11037
11107
  firstThursday += 1;
11038
11108
  }
11039
- const firstDayOfFirstWeek = new Date(y, 0, firstThursday - 3);
11109
+ const firstDayOfFirstWeek = new DateTime(y, 0, firstThursday - 3);
11040
11110
  // The last week of the year is the week that contains the last Thursday of
11041
11111
  // the year.
11042
11112
  let lastThursday = 31;
11043
- while (new Date(y, 11, lastThursday).getDay() !== 4) {
11113
+ while (new DateTime(y, 11, lastThursday).getDay() !== 4) {
11044
11114
  lastThursday -= 1;
11045
11115
  }
11046
- const lastDayOfLastWeek = new Date(y, 11, lastThursday + 3);
11116
+ const lastDayOfLastWeek = new DateTime(y, 11, lastThursday + 3);
11047
11117
  // B - If our date > lastDayOfLastWeek then it's in the weeks of the year after
11048
11118
  // If our date < firstDayOfFirstWeek then it's in the weeks of the year before
11049
11119
  let offsetYear;
@@ -11069,17 +11139,17 @@ const ISOWEEKNUM = {
11069
11139
  case 1:
11070
11140
  // firstDay is the 1st day of the 1st week of the year after
11071
11141
  // firstDay = lastDayOfLastWeek + 1 Day
11072
- firstDay = new Date(y, 11, lastThursday + 3 + 1);
11142
+ firstDay = new DateTime(y, 11, lastThursday + 3 + 1);
11073
11143
  break;
11074
11144
  case -1:
11075
11145
  // firstDay is the 1st day of the 1st week of the previous year.
11076
11146
  // The first week of the previous year is the week that contains the
11077
11147
  // first Thursday of the previous year.
11078
11148
  let firstThursdayPreviousYear = 1;
11079
- while (new Date(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
11149
+ while (new DateTime(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
11080
11150
  firstThursdayPreviousYear += 1;
11081
11151
  }
11082
- firstDay = new Date(y - 1, 0, firstThursdayPreviousYear - 3);
11152
+ firstDay = new DateTime(y - 1, 0, firstThursdayPreviousYear - 3);
11083
11153
  break;
11084
11154
  }
11085
11155
  const diff = (_date.getTime() - firstDay.getTime()) / MS_PER_DAY;
@@ -11218,8 +11288,8 @@ const NETWORKDAYS_INTL = {
11218
11288
  });
11219
11289
  }
11220
11290
  const invertDate = _startDate.getTime() > _endDate.getTime();
11221
- const stopDate = new Date((invertDate ? _startDate : _endDate).getTime());
11222
- let stepDate = new Date((invertDate ? _endDate : _startDate).getTime());
11291
+ const stopDate = DateTime.fromTimestamp((invertDate ? _startDate : _endDate).getTime());
11292
+ let stepDate = DateTime.fromTimestamp((invertDate ? _endDate : _startDate).getTime());
11223
11293
  const timeStopDate = stopDate.getTime();
11224
11294
  let timeStepDate = stepDate.getTime();
11225
11295
  let netWorkingDay = 0;
@@ -11243,8 +11313,7 @@ const NOW = {
11243
11313
  returns: ["DATE"],
11244
11314
  computeFormat: () => "m/d/yyyy hh:mm:ss",
11245
11315
  compute: function () {
11246
- let today = new Date();
11247
- today.setMilliseconds(0);
11316
+ let today = DateTime.now();
11248
11317
  const delta = today.getTime() - INITIAL_1900_DAY.getTime();
11249
11318
  const time = today.getHours() / 24 + today.getMinutes() / 1440 + today.getSeconds() / 86400;
11250
11319
  return Math.floor(delta / MS_PER_DAY) + time;
@@ -11318,8 +11387,8 @@ const TODAY = {
11318
11387
  returns: ["DATE"],
11319
11388
  computeFormat: () => "m/d/yyyy",
11320
11389
  compute: function () {
11321
- const today = new Date();
11322
- const jsDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());
11390
+ const today = DateTime.now();
11391
+ const jsDate = new DateTime(today.getFullYear(), today.getMonth(), today.getDate());
11323
11392
  return jsDateToRoundNumber(jsDate);
11324
11393
  },
11325
11394
  isExported: true,
@@ -11374,10 +11443,10 @@ const WEEKNUM = {
11374
11443
  }
11375
11444
  const y = _date.getFullYear();
11376
11445
  let dayStart = 1;
11377
- let startDayOfFirstWeek = new Date(y, 0, dayStart);
11446
+ let startDayOfFirstWeek = new DateTime(y, 0, dayStart);
11378
11447
  while (startDayOfFirstWeek.getDay() !== startDayOfWeek) {
11379
11448
  dayStart += 1;
11380
- startDayOfFirstWeek = new Date(y, 0, dayStart);
11449
+ startDayOfFirstWeek = new DateTime(y, 0, dayStart);
11381
11450
  }
11382
11451
  const dif = (_date.getTime() - startDayOfFirstWeek.getTime()) / MS_PER_DAY;
11383
11452
  if (dif < 0) {
@@ -11431,7 +11500,7 @@ const WORKDAY_INTL = {
11431
11500
  timesHoliday.add(holiday.getTime());
11432
11501
  });
11433
11502
  }
11434
- let stepDate = new Date(_startDate.getTime());
11503
+ let stepDate = DateTime.fromTimestamp(_startDate.getTime());
11435
11504
  let timeStepDate = stepDate.getTime();
11436
11505
  const unitDay = Math.sign(_numDays);
11437
11506
  let stepDay = Math.abs(_numDays);
@@ -11497,7 +11566,7 @@ const MONTH_START = {
11497
11566
  const _startDate = toJsDate(date);
11498
11567
  const yStart = _startDate.getFullYear();
11499
11568
  const mStart = _startDate.getMonth();
11500
- const jsDate = new Date(yStart, mStart, 1);
11569
+ const jsDate = new DateTime(yStart, mStart, 1);
11501
11570
  return jsDateToRoundNumber(jsDate);
11502
11571
  },
11503
11572
  };
@@ -11541,7 +11610,7 @@ const QUARTER_START = {
11541
11610
  compute: function (date) {
11542
11611
  const quarter = QUARTER.compute(date);
11543
11612
  const year = YEAR.compute(date);
11544
- const jsDate = new Date(year, (quarter - 1) * 3, 1);
11613
+ const jsDate = new DateTime(year, (quarter - 1) * 3, 1);
11545
11614
  return jsDateToRoundNumber(jsDate);
11546
11615
  },
11547
11616
  };
@@ -11558,7 +11627,7 @@ const QUARTER_END = {
11558
11627
  compute: function (date) {
11559
11628
  const quarter = QUARTER.compute(date);
11560
11629
  const year = YEAR.compute(date);
11561
- const jsDate = new Date(year, quarter * 3, 0);
11630
+ const jsDate = new DateTime(year, quarter * 3, 0);
11562
11631
  return jsDateToRoundNumber(jsDate);
11563
11632
  },
11564
11633
  };
@@ -11574,7 +11643,7 @@ const YEAR_START = {
11574
11643
  computeFormat: () => "m/d/yyyy",
11575
11644
  compute: function (date) {
11576
11645
  const year = YEAR.compute(date);
11577
- const jsDate = new Date(year, 0, 1);
11646
+ const jsDate = new DateTime(year, 0, 1);
11578
11647
  return jsDateToRoundNumber(jsDate);
11579
11648
  },
11580
11649
  };
@@ -11590,7 +11659,7 @@ const YEAR_END = {
11590
11659
  computeFormat: () => "m/d/yyyy",
11591
11660
  compute: function (date) {
11592
11661
  const year = YEAR.compute(date);
11593
- const jsDate = new Date(year + 1, 0, 0);
11662
+ const jsDate = new DateTime(year + 1, 0, 0);
11594
11663
  return jsDateToRoundNumber(jsDate);
11595
11664
  },
11596
11665
  };
@@ -11724,7 +11793,7 @@ function assertDeprecationFactorStrictlyPositive(factor) {
11724
11793
  function assertSettlementLessThanOneYearBeforeMaturity(settlement, maturity) {
11725
11794
  const startDate = toJsDate(settlement);
11726
11795
  const endDate = toJsDate(maturity);
11727
- const startDatePlusOneYear = new Date(startDate);
11796
+ const startDatePlusOneYear = toJsDate(settlement);
11728
11797
  startDatePlusOneYear.setFullYear(startDate.getFullYear() + 1);
11729
11798
  assert(() => endDate.getTime() <= startDatePlusOneYear.getTime(), _lt("The settlement date (%s) must at most one year after the maturity date (%s).", settlement.toString(), maturity.toString()));
11730
11799
  }
@@ -15650,7 +15719,7 @@ const machine = {
15650
15719
  SPACE: goTo(State.RightRef),
15651
15720
  NUMBER: goTo(State.Found),
15652
15721
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
15653
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
15722
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
15654
15723
  },
15655
15724
  [State.RightColumnRef]: {
15656
15725
  SPACE: goTo(State.RightColumnRef),
@@ -15661,6 +15730,7 @@ const machine = {
15661
15730
  SPACE: goTo(State.RightRowRef),
15662
15731
  NUMBER: goTo(State.Found),
15663
15732
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
15733
+ SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
15664
15734
  },
15665
15735
  [State.Found]: {},
15666
15736
  };
@@ -16170,6 +16240,10 @@ function setXcToReferenceType(xc, referenceType) {
16170
16240
  return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
16171
16241
  case "colrow":
16172
16242
  indexOfNumber = xc.search(/[0-9]/);
16243
+ if (indexOfNumber === -1 || indexOfNumber === 0) {
16244
+ // no row number (eg. A) or no column (eg. 1)
16245
+ return "$" + xc;
16246
+ }
16173
16247
  xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
16174
16248
  return "$" + xc;
16175
16249
  case "none":
@@ -16943,10 +17017,10 @@ const arrowMap = {
16943
17017
  function updateSelectionWithArrowKeys(ev, selection) {
16944
17018
  const direction = arrowMap[ev.key];
16945
17019
  if (ev.shiftKey) {
16946
- selection.resizeAnchorZone(direction, ev.ctrlKey ? "end" : 1);
17020
+ selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
16947
17021
  }
16948
17022
  else {
16949
- selection.moveAnchorCell(direction, ev.ctrlKey ? "end" : 1);
17023
+ selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
16950
17024
  }
16951
17025
  }
16952
17026
 
@@ -22603,7 +22677,10 @@ class GridOverlay extends owl.Component {
22603
22677
  return;
22604
22678
  }
22605
22679
  const [col, row] = this.getCartesianCoordinates(ev);
22606
- this.props.onCellClicked(col, row, { shiftKey: ev.shiftKey, ctrlKey: ev.ctrlKey });
22680
+ this.props.onCellClicked(col, row, {
22681
+ expandZone: ev.shiftKey,
22682
+ addZone: isCtrlKey(ev),
22683
+ });
22607
22684
  }
22608
22685
  onDoubleClick(ev) {
22609
22686
  const [col, row] = this.getCartesianCoordinates(ev);
@@ -22691,6 +22768,7 @@ class AbstractResizer extends owl.Component {
22691
22768
  draggerShadowThickness: 0,
22692
22769
  delta: 0,
22693
22770
  base: 0,
22771
+ position: "before",
22694
22772
  });
22695
22773
  }
22696
22774
  _computeHandleDisplay(ev) {
@@ -22816,11 +22894,13 @@ class AbstractResizer extends owl.Component {
22816
22894
  this.state.draggerLinePosition = dimensions.start;
22817
22895
  this.state.draggerShadowPosition = dimensions.start;
22818
22896
  this.state.base = elementIndex;
22897
+ this.state.position = "before";
22819
22898
  }
22820
22899
  else if (this._getSelectedZoneEnd() < elementIndex) {
22821
22900
  this.state.draggerLinePosition = dimensions.end;
22822
22901
  this.state.draggerShadowPosition = dimensions.end - this.state.draggerShadowThickness;
22823
- this.state.base = elementIndex + 1;
22902
+ this.state.base = elementIndex;
22903
+ this.state.position = "after";
22824
22904
  }
22825
22905
  else {
22826
22906
  this.state.draggerLinePosition = startDimensions.start;
@@ -22844,7 +22924,7 @@ class AbstractResizer extends owl.Component {
22844
22924
  this._increaseSelection(index);
22845
22925
  }
22846
22926
  else {
22847
- this._selectElement(index, ev.ctrlKey);
22927
+ this._selectElement(index, isCtrlKey(ev));
22848
22928
  }
22849
22929
  this.lastSelectedElementIndex = index;
22850
22930
  const mouseMoveSelect = (col, row) => {
@@ -22857,7 +22937,7 @@ class AbstractResizer extends owl.Component {
22857
22937
  const mouseUpSelect = () => {
22858
22938
  this.state.isSelecting = false;
22859
22939
  this.lastSelectedElementIndex = null;
22860
- this.env.model.dispatch(ev.ctrlKey ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
22940
+ this.env.model.dispatch(isCtrlKey(ev) ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
22861
22941
  this._computeGrabDisplay(ev);
22862
22942
  };
22863
22943
  dragAndDropBeyondTheViewport(this.env, mouseMoveSelect, mouseUpSelect);
@@ -23001,13 +23081,14 @@ class ColResizer extends AbstractResizer {
23001
23081
  dimension: "COL",
23002
23082
  base: this.state.base,
23003
23083
  elements,
23084
+ position: this.state.position,
23004
23085
  });
23005
23086
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
23006
23087
  this.env.raiseError(MergeErrorMessage);
23007
23088
  }
23008
23089
  }
23009
- _selectElement(index, ctrlKey) {
23010
- this.env.model.selection.selectColumn(index, ctrlKey ? "newAnchor" : "overrideSelection");
23090
+ _selectElement(index, addDistinctHeader) {
23091
+ this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
23011
23092
  }
23012
23093
  _increaseSelection(index) {
23013
23094
  this.env.model.selection.selectColumn(index, "updateAnchor");
@@ -23173,13 +23254,14 @@ class RowResizer extends AbstractResizer {
23173
23254
  dimension: "ROW",
23174
23255
  base: this.state.base,
23175
23256
  elements,
23257
+ position: this.state.position,
23176
23258
  });
23177
23259
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
23178
23260
  this.env.raiseError(MergeErrorMessage);
23179
23261
  }
23180
23262
  }
23181
- _selectElement(index, ctrlKey) {
23182
- this.env.model.selection.selectRow(index, ctrlKey ? "newAnchor" : "overrideSelection");
23263
+ _selectElement(index, addDistinctHeader) {
23264
+ this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
23183
23265
  }
23184
23266
  _increaseSelection(index) {
23185
23267
  this.env.model.selection.selectRow(index, "updateAnchor");
@@ -23281,8 +23363,8 @@ function useWheelHandler(handler) {
23281
23363
  return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT);
23282
23364
  }
23283
23365
  const onMouseWheel = (ev) => {
23284
- const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode);
23285
- const deltaY = normalize(ev.shiftKey ? ev.deltaX : ev.deltaY, ev.deltaMode);
23366
+ const deltaX = normalize(ev.shiftKey && !isMacOS() ? ev.deltaY : ev.deltaX, ev.deltaMode);
23367
+ const deltaY = normalize(ev.shiftKey && !isMacOS() ? ev.deltaX : ev.deltaY, ev.deltaMode);
23286
23368
  handler(deltaX, deltaY);
23287
23369
  };
23288
23370
  return onMouseWheel;
@@ -23926,8 +24008,8 @@ class Grid extends owl.Component {
23926
24008
  // ---------------------------------------------------------------------------
23927
24009
  // Zone selection with mouse
23928
24010
  // ---------------------------------------------------------------------------
23929
- onCellClicked(col, row, { ctrlKey, shiftKey }) {
23930
- if (ctrlKey) {
24011
+ onCellClicked(col, row, { addZone, expandZone }) {
24012
+ if (addZone) {
23931
24013
  this.env.model.dispatch("PREPARE_SELECTION_INPUT_EXPANSION");
23932
24014
  }
23933
24015
  if (this.env.model.getters.hasOpenedPopover()) {
@@ -23936,10 +24018,10 @@ class Grid extends owl.Component {
23936
24018
  if (this.env.model.getters.getEditionMode() === "editing") {
23937
24019
  this.env.model.dispatch("STOP_EDITION");
23938
24020
  }
23939
- if (shiftKey) {
24021
+ if (expandZone) {
23940
24022
  this.env.model.selection.setAnchorCorner(col, row);
23941
24023
  }
23942
- else if (ctrlKey) {
24024
+ else if (addZone) {
23943
24025
  this.env.model.selection.addCellToSelection(col, row);
23944
24026
  }
23945
24027
  else {
@@ -24001,9 +24083,7 @@ class Grid extends owl.Component {
24001
24083
  return;
24002
24084
  }
24003
24085
  let keyDownString = "";
24004
- if (ev.ctrlKey)
24005
- keyDownString += "CTRL+";
24006
- if (ev.metaKey)
24086
+ if (isCtrlKey(ev))
24007
24087
  keyDownString += "CTRL+";
24008
24088
  if (ev.altKey)
24009
24089
  keyDownString += "ALT+";
@@ -31351,7 +31431,7 @@ class SheetPlugin extends CorePlugin {
31351
31431
  const elements = cmd.dimension === "COL"
31352
31432
  ? this.getNumberCols(cmd.sheetId)
31353
31433
  : this.getNumberRows(cmd.sheetId);
31354
- if (cmd.base < 0 || cmd.base > elements) {
31434
+ if (cmd.base < 0 || cmd.base >= elements) {
31355
31435
  return 86 /* CommandResult.InvalidHeaderIndex */;
31356
31436
  }
31357
31437
  else if (cmd.quantity <= 0) {
@@ -32374,13 +32454,18 @@ class EvaluationPlugin extends UIPlugin {
32374
32454
  // Getters
32375
32455
  // ---------------------------------------------------------------------------
32376
32456
  evaluateFormula(formulaString, sheetId = this.getters.getActiveSheetId()) {
32377
- const compiledFormula = compile(formulaString);
32378
- const params = this.getCompilationParameters((cell) => this.getEvaluatedCell(this.getters.getCellPosition(cell.id)));
32379
- const ranges = [];
32380
- for (let xc of compiledFormula.dependencies) {
32381
- ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
32457
+ try {
32458
+ const compiledFormula = compile(formulaString);
32459
+ const params = this.getCompilationParameters((cell) => this.getEvaluatedCell(this.getters.getCellPosition(cell.id)));
32460
+ const ranges = [];
32461
+ for (let xc of compiledFormula.dependencies) {
32462
+ ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
32463
+ }
32464
+ return compiledFormula.execute(ranges, ...params).value;
32465
+ }
32466
+ catch (error) {
32467
+ return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
32382
32468
  }
32383
- return compiledFormula.execute(ranges, ...params).value;
32384
32469
  }
32385
32470
  /**
32386
32471
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -32872,39 +32957,34 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
32872
32957
  this.computedIcons[sheetId] = {};
32873
32958
  const computedStyle = this.computedStyles[sheetId];
32874
32959
  for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
32875
- try {
32876
- switch (cf.rule.type) {
32877
- case "ColorScaleRule":
32878
- for (let range of cf.ranges) {
32879
- this.applyColorScale(range, cf.rule);
32880
- }
32881
- break;
32882
- case "IconSetRule":
32883
- for (let range of cf.ranges) {
32884
- this.applyIcon(range, cf.rule);
32885
- }
32886
- break;
32887
- default:
32888
- for (let ref of cf.ranges) {
32889
- const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
32890
- for (let row = zone.top; row <= zone.bottom; row++) {
32891
- for (let col = zone.left; col <= zone.right; col++) {
32892
- const pr = this.rulePredicate[cf.rule.type];
32893
- let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
32894
- if (pr && pr(cell, cf.rule)) {
32895
- if (!computedStyle[col])
32896
- computedStyle[col] = [];
32897
- // we must combine all the properties of all the CF rules applied to the given cell
32898
- computedStyle[col][row] = Object.assign(((_a = computedStyle[col]) === null || _a === void 0 ? void 0 : _a[row]) || {}, cf.rule.style);
32899
- }
32960
+ switch (cf.rule.type) {
32961
+ case "ColorScaleRule":
32962
+ for (let range of cf.ranges) {
32963
+ this.applyColorScale(range, cf.rule);
32964
+ }
32965
+ break;
32966
+ case "IconSetRule":
32967
+ for (let range of cf.ranges) {
32968
+ this.applyIcon(range, cf.rule);
32969
+ }
32970
+ break;
32971
+ default:
32972
+ for (let ref of cf.ranges) {
32973
+ const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
32974
+ for (let row = zone.top; row <= zone.bottom; row++) {
32975
+ for (let col = zone.left; col <= zone.right; col++) {
32976
+ const pr = this.rulePredicate[cf.rule.type];
32977
+ let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
32978
+ if (pr && pr(cell, cf.rule)) {
32979
+ if (!computedStyle[col])
32980
+ computedStyle[col] = [];
32981
+ // we must combine all the properties of all the CF rules applied to the given cell
32982
+ computedStyle[col][row] = Object.assign(((_a = computedStyle[col]) === null || _a === void 0 ? void 0 : _a[row]) || {}, cf.rule.style);
32900
32983
  }
32901
32984
  }
32902
32985
  }
32903
- break;
32904
- }
32905
- }
32906
- catch (_) {
32907
- // we don't care about the errors within the evaluation of a rule
32986
+ }
32987
+ break;
32908
32988
  }
32909
32989
  }
32910
32990
  }
@@ -32929,7 +33009,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
32929
33009
  return percentile(rangeValues, Number(threshold.value) / 100, true);
32930
33010
  case "formula":
32931
33011
  const value = threshold.value && this.getters.evaluateFormula(threshold.value);
32932
- return !(value instanceof Promise) ? value : null;
33012
+ return typeof value === "number" ? value : null;
32933
33013
  default:
32934
33014
  return null;
32935
33015
  }
@@ -34491,24 +34571,27 @@ class AutofillPlugin extends UIPlugin {
34491
34571
  * autofiller
34492
34572
  */
34493
34573
  autofillAuto() {
34574
+ var _a, _b;
34494
34575
  const zone = this.getters.getSelectedZone();
34495
34576
  const sheetId = this.getters.getActiveSheetId();
34496
34577
  let col = zone.left;
34497
34578
  let row = zone.bottom;
34498
34579
  if (col > 0) {
34499
- let left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
34500
- while (left.type !== CellValueType.empty) {
34580
+ let leftPosition = { sheetId, col: col - 1, row };
34581
+ while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
34582
+ ((_a = this.getters.getCell(leftPosition)) === null || _a === void 0 ? void 0 : _a.content)) {
34501
34583
  row += 1;
34502
- left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
34584
+ leftPosition = { sheetId, col: col - 1, row };
34503
34585
  }
34504
34586
  }
34505
34587
  if (row === zone.bottom) {
34506
34588
  col = zone.right;
34507
34589
  if (col <= this.getters.getNumberCols(sheetId)) {
34508
- let right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
34509
- while (right.type !== CellValueType.empty) {
34590
+ let rightPosition = { sheetId, col: col + 1, row };
34591
+ while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
34592
+ ((_b = this.getters.getCell(rightPosition)) === null || _b === void 0 ? void 0 : _b.content)) {
34510
34593
  row += 1;
34511
- right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
34594
+ rightPosition = { sheetId, col: col + 1, row };
34512
34595
  }
34513
34596
  }
34514
34597
  }
@@ -39240,7 +39323,7 @@ class GridSelectionPlugin extends UIPlugin {
39240
39323
  sheetId: cmd.sheetId,
39241
39324
  base: cmd.base,
39242
39325
  quantity: thickness,
39243
- position: "before",
39326
+ position: cmd.position,
39244
39327
  });
39245
39328
  const isCol = cmd.dimension === "COL";
39246
39329
  const start = cmd.elements[0];
@@ -39257,12 +39340,13 @@ class GridSelectionPlugin extends UIPlugin {
39257
39340
  },
39258
39341
  ];
39259
39342
  const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
39343
+ const base = isBasedBefore ? cmd.base : cmd.base + 1;
39260
39344
  const pasteTarget = [
39261
39345
  {
39262
- left: isCol ? cmd.base : 0,
39263
- right: isCol ? cmd.base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
39264
- top: !isCol ? cmd.base : 0,
39265
- bottom: !isCol ? cmd.base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
39346
+ left: isCol ? base : 0,
39347
+ right: isCol ? base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
39348
+ top: !isCol ? base : 0,
39349
+ bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
39266
39350
  },
39267
39351
  ];
39268
39352
  state.paste(pasteTarget, { selectTarget: true });
@@ -39299,6 +39383,11 @@ class GridSelectionPlugin extends UIPlugin {
39299
39383
  doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) {
39300
39384
  return 2 /* CommandResult.WillRemoveExistingMerge */;
39301
39385
  }
39386
+ const headers = [cmd.base, ...cmd.elements];
39387
+ const maxHeaderValue = isCol ? this.getters.getNumberCols(id) : this.getters.getNumberRows(id);
39388
+ if (headers.some((h) => h < 0 || h >= maxHeaderValue)) {
39389
+ return 86 /* CommandResult.InvalidHeaderIndex */;
39390
+ }
39302
39391
  return 0 /* CommandResult.Success */;
39303
39392
  }
39304
39393
  //-------------------------------------------
@@ -40852,7 +40941,7 @@ class Spreadsheet extends owl.Component {
40852
40941
  }
40853
40942
  onKeydown(ev) {
40854
40943
  let keyDownString = "";
40855
- if (ev.ctrlKey || ev.metaKey) {
40944
+ if (isCtrlKey(ev)) {
40856
40945
  keyDownString += "CTRL+";
40857
40946
  }
40858
40947
  keyDownString += ev.key.toUpperCase();
@@ -44477,6 +44566,6 @@ exports.setTranslationMethod = setTranslationMethod;
44477
44566
  exports.tokenize = tokenize;
44478
44567
 
44479
44568
 
44480
- __info__.version = '16.1.28';
44481
- __info__.date = '2023-11-24T13:11:01.992Z';
44482
- __info__.hash = '257f369';
44569
+ __info__.version = '16.1.31';
44570
+ __info__.date = '2024-01-12T13:42:11.001Z';
44571
+ __info__.hash = 'f51befa';