@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.
@@ -302,15 +302,80 @@ const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 18, 24, 36];
302
302
  // -----------------------------------------------------------------------------
303
303
  // Date Type
304
304
  // -----------------------------------------------------------------------------
305
+ /**
306
+ * A DateTime object that can be used to manipulate spreadsheet dates.
307
+ * Conceptually, a spreadsheet date is simply a number with a date format,
308
+ * and it is timezone-agnostic.
309
+ * This DateTime object consistently uses UTC time to represent a naive date and time.
310
+ */
311
+ class DateTime {
312
+ jsDate;
313
+ constructor(year, month, day, hours = 0, minutes = 0, seconds = 0) {
314
+ this.jsDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds, 0));
315
+ }
316
+ static fromTimestamp(timestamp) {
317
+ const date = new Date(timestamp);
318
+ return new DateTime(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
319
+ }
320
+ static now() {
321
+ const now = new Date();
322
+ return new DateTime(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds());
323
+ }
324
+ toString() {
325
+ return this.jsDate.toString();
326
+ }
327
+ toLocaleDateString() {
328
+ return this.jsDate.toLocaleDateString();
329
+ }
330
+ getTime() {
331
+ return this.jsDate.getTime();
332
+ }
333
+ getFullYear() {
334
+ return this.jsDate.getUTCFullYear();
335
+ }
336
+ getMonth() {
337
+ return this.jsDate.getUTCMonth();
338
+ }
339
+ getDate() {
340
+ return this.jsDate.getUTCDate();
341
+ }
342
+ getDay() {
343
+ return this.jsDate.getUTCDay();
344
+ }
345
+ getHours() {
346
+ return this.jsDate.getUTCHours();
347
+ }
348
+ getMinutes() {
349
+ return this.jsDate.getUTCMinutes();
350
+ }
351
+ getSeconds() {
352
+ return this.jsDate.getUTCSeconds();
353
+ }
354
+ setFullYear(year) {
355
+ this.jsDate.setFullYear(year);
356
+ }
357
+ setDate(date) {
358
+ this.jsDate.setUTCDate(date);
359
+ }
360
+ setHours(hours) {
361
+ this.jsDate.setUTCHours(hours);
362
+ }
363
+ setMinutes(minutes) {
364
+ this.jsDate.setUTCMinutes(minutes);
365
+ }
366
+ setSeconds(seconds) {
367
+ this.jsDate.setUTCSeconds(seconds);
368
+ }
369
+ }
305
370
  // -----------------------------------------------------------------------------
306
371
  // Parsing
307
372
  // -----------------------------------------------------------------------------
308
- const INITIAL_1900_DAY = new Date(1899, 11, 30);
373
+ const INITIAL_1900_DAY = new DateTime(1899, 11, 30);
309
374
  const MS_PER_DAY = 24 * 60 * 60 * 1000;
310
375
  const CURRENT_MILLENIAL = 2000; // note: don't forget to update this in 2999
311
- const CURRENT_YEAR = new Date().getFullYear();
312
- const INITIAL_JS_DAY = new Date(0);
313
- const DATE_JS_1900_OFFSET = INITIAL_JS_DAY - INITIAL_1900_DAY;
376
+ const CURRENT_YEAR = DateTime.now().getFullYear();
377
+ const INITIAL_JS_DAY = DateTime.fromTimestamp(0);
378
+ const DATE_JS_1900_OFFSET = INITIAL_JS_DAY.getTime() - INITIAL_1900_DAY.getTime();
314
379
  const mdyDateRegexp = /^\d{1,2}(\/|-|\s)\d{1,2}((\/|-|\s)\d{1,4})?$/;
315
380
  const ymdDateRegexp = /^\d{3,4}(\/|-|\s)\d{1,2}(\/|-|\s)\d{1,2}$/;
316
381
  const timeRegexp = /((\d+(:\d+)?(:\d+)?\s*(AM|PM))|(\d+:\d+(:\d+)?))$/;
@@ -359,7 +424,7 @@ function _parseDateTime(str) {
359
424
  return {
360
425
  value: date.value + time.value,
361
426
  format: date.format + " " + (time.format === "hhhh:mm:ss" ? "hh:mm:ss" : time.format),
362
- jsDate: new Date(date.jsDate.getFullYear() + time.jsDate.getFullYear() - 1899, date.jsDate.getMonth() + time.jsDate.getMonth() - 11, date.jsDate.getDate() + time.jsDate.getDate() - 30, date.jsDate.getHours() + time.jsDate.getHours(), date.jsDate.getMinutes() + time.jsDate.getMinutes(), date.jsDate.getSeconds() + time.jsDate.getSeconds()),
427
+ jsDate: new DateTime(date.jsDate.getFullYear() + time.jsDate.getFullYear() - 1899, date.jsDate.getMonth() + time.jsDate.getMonth() - 11, date.jsDate.getDate() + time.jsDate.getDate() - 30, date.jsDate.getHours() + time.jsDate.getHours(), date.jsDate.getMinutes() + time.jsDate.getMinutes(), date.jsDate.getSeconds() + time.jsDate.getSeconds()),
363
428
  };
364
429
  }
365
430
  return date || time;
@@ -376,13 +441,13 @@ function parseDate(str, dateFormat) {
376
441
  const day = Number(parts[dayIndex]);
377
442
  const leadingZero = (parts[monthIndex].length === 2 && month < 10) || (parts[dayIndex].length === 2 && day < 10);
378
443
  const year = parts[yearIndex] ? inferYear(parts[yearIndex]) : CURRENT_YEAR;
379
- const jsDate = new Date(year, month - 1, day);
444
+ const jsDate = new DateTime(year, month - 1, day);
380
445
  const sep = str.match(/\/|-|\s/)[0];
381
446
  if (jsDate.getMonth() !== month - 1 || jsDate.getDate() !== day) {
382
447
  // invalid date
383
448
  return null;
384
449
  }
385
- const delta = jsDate - INITIAL_1900_DAY;
450
+ const delta = jsDate.getTime() - INITIAL_1900_DAY.getTime();
386
451
  let format = leadingZero ? `mm${sep}dd` : `m${sep}d`;
387
452
  if (parts[yearIndex]) {
388
453
  format = isMDY ? format + sep + "yyyy" : "yyyy" + sep + format;
@@ -442,7 +507,7 @@ function parseTime(str) {
442
507
  if (hours >= 24) {
443
508
  format = "hhhh:mm:ss";
444
509
  }
445
- const jsDate = new Date(1899, 11, 30, hours, minutes, seconds);
510
+ const jsDate = new DateTime(1899, 11, 30, hours, minutes, seconds);
446
511
  return {
447
512
  value: hours / 24 + minutes / 1440 + seconds / 86400,
448
513
  format: format,
@@ -456,7 +521,7 @@ function parseTime(str) {
456
521
  // -----------------------------------------------------------------------------
457
522
  function numberToJsDate(value) {
458
523
  const truncValue = Math.trunc(value);
459
- let date = new Date(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
524
+ let date = DateTime.fromTimestamp(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
460
525
  let time = value - truncValue;
461
526
  time = time < 0 ? 1 + time : time;
462
527
  const hours = Math.round(time * 24);
@@ -473,7 +538,7 @@ function jsDateToRoundNumber(date) {
473
538
  }
474
539
  /** Return the number of days in the current month of the given date */
475
540
  function getDaysInMonth(date) {
476
- return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
541
+ return new DateTime(date.getFullYear(), date.getMonth() + 1, 0).getDate();
477
542
  }
478
543
  function isLastDayOfMonth(date) {
479
544
  return getDaysInMonth(date) === date.getDate();
@@ -491,7 +556,7 @@ function addMonthsToDate(date, months, keepEndOfMonth) {
491
556
  const yStart = date.getFullYear();
492
557
  const mStart = date.getMonth();
493
558
  const dStart = date.getDate();
494
- const jsDate = new Date(yStart, mStart + months);
559
+ const jsDate = new DateTime(yStart, mStart + months, 1);
495
560
  if (keepEndOfMonth && dStart === getDaysInMonth(date)) {
496
561
  jsDate.setDate(getDaysInMonth(jsDate));
497
562
  }
@@ -651,13 +716,6 @@ function areTwoDatesWithinOneYear(startDate, endDate) {
651
716
  }
652
717
 
653
718
  //------------------------------------------------------------------------------
654
- /**
655
- * Stringify an object, like JSON.stringify, except that the first level of keys
656
- * is ordered.
657
- */
658
- function stringify(obj) {
659
- return JSON.stringify(obj, Object.keys(obj).sort());
660
- }
661
719
  /**
662
720
  * Remove quotes from a quoted string
663
721
  * ```js
@@ -887,7 +945,7 @@ function isBoolean(str) {
887
945
  function isDateTime(str) {
888
946
  return parseDateTime(str) !== null;
889
947
  }
890
- const MARKDOWN_LINK_REGEX = /^\[([^\[]+)\]\((.+)\)$/;
948
+ const MARKDOWN_LINK_REGEX = /^\[(.+)\]\((.+)\)$/;
891
949
  //link must start with http or https
892
950
  //https://stackoverflow.com/a/3809435/4760614
893
951
  const WEB_LINK_REGEX = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/;
@@ -956,7 +1014,7 @@ function isObjectEmptyRecursive(argument) {
956
1014
  */
957
1015
  function getItemId(item, itemsDic) {
958
1016
  for (let [key, value] of Object.entries(itemsDic)) {
959
- if (stringify(value) === stringify(item)) {
1017
+ if (deepEquals(value, item)) {
960
1018
  return parseInt(key, 10);
961
1019
  }
962
1020
  }
@@ -1956,7 +2014,7 @@ function formatJSTime(jsDate, format) {
1956
2014
  .map((p) => {
1957
2015
  switch (p) {
1958
2016
  case "hhhh":
1959
- const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY) / (60 * 60 * 1000));
2017
+ const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY.getTime()) / (60 * 60 * 1000));
1960
2018
  return helapsedHours.toString();
1961
2019
  case "hh":
1962
2020
  return hours.toString().padStart(2, "0");
@@ -2203,8 +2261,10 @@ function convertInternalFormatToFormat(internalFormat) {
2203
2261
  const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
2204
2262
  // Same as above, but matches the exact string (nothing before or after)
2205
2263
  const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
2206
- /** Reference of a column header (eg. A, AB) */
2207
- const colHeader = new RegExp(/^([A-Z]{1,3})+$/, "i");
2264
+ /** Reference of a column header (eg. A, AB, $A) */
2265
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
2266
+ /** Reference of a row header (eg. 1, $1) */
2267
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
2208
2268
  /** Reference of a column (eg. A, $CA, Sheet1!B) */
2209
2269
  const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
2210
2270
  /** Reference of a row (eg. 1, 59, Sheet1!9) */
@@ -2234,6 +2294,9 @@ function isRowReference(xc) {
2234
2294
  function isColHeader(str) {
2235
2295
  return colHeader.test(str);
2236
2296
  }
2297
+ function isRowHeader(str) {
2298
+ return rowHeader.test(str);
2299
+ }
2237
2300
  /**
2238
2301
  * Return true if the given xc is the reference of a single cell,
2239
2302
  * without any specified sheet (e.g. A1)
@@ -2300,18 +2363,10 @@ class RangeImpl {
2300
2363
  if (isFullCol) {
2301
2364
  parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2302
2365
  parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2303
- if (zone.left === zone.right) {
2304
- parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2305
- parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2306
- }
2307
2366
  }
2308
2367
  if (isFullRow) {
2309
2368
  parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2310
2369
  parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2311
- if (zone.top === zone.bottom) {
2312
- parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2313
- parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2314
- }
2315
2370
  }
2316
2371
  return parts;
2317
2372
  }
@@ -5171,6 +5226,7 @@ const FilterMenuPopoverBuilder = {
5171
5226
  },
5172
5227
  };
5173
5228
 
5229
+ const macRegex = /Mac/i;
5174
5230
  /**
5175
5231
  * Return true if the event was triggered from
5176
5232
  * a child element.
@@ -5216,6 +5272,17 @@ function setElementScrollTop(el, scroll) {
5216
5272
  }
5217
5273
  function getOpenedMenus() {
5218
5274
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
5275
+ }
5276
+ function isMacOS() {
5277
+ return Boolean(macRegex.test(navigator.userAgent));
5278
+ }
5279
+ /**
5280
+ * @param {KeyboardEvent | MouseEvent} ev
5281
+ * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
5282
+ * On Mac, this is the "meta" or "command" key.
5283
+ */
5284
+ function isCtrlKey(ev) {
5285
+ return isMacOS() ? ev.metaKey : ev.ctrlKey;
5219
5286
  }
5220
5287
 
5221
5288
  /**
@@ -9602,6 +9669,9 @@ function arg(definition, description = "") {
9602
9669
  function makeArg(str, description) {
9603
9670
  let parts = str.match(ARG_REGEXP);
9604
9671
  let name = parts[1].trim();
9672
+ if (!name) {
9673
+ throw new Error(`Function argument definition is missing a name: '${str}'.`);
9674
+ }
9605
9675
  let types = [];
9606
9676
  let isOptional = false;
9607
9677
  let isRepeating = false;
@@ -12055,7 +12125,7 @@ const DATE = {
12055
12125
  if (_year < 1900) {
12056
12126
  _year += 1900;
12057
12127
  }
12058
- const jsDate = new Date(_year, _month - 1, _day);
12128
+ const jsDate = new DateTime(_year, _month - 1, _day);
12059
12129
  const result = jsDateToRoundNumber(jsDate);
12060
12130
  assert(() => result >= 0, _lt(`The function [[FUNCTION_NAME]] result must be greater than or equal 01/01/1900.`));
12061
12131
  return result;
@@ -12098,7 +12168,7 @@ const DATEDIF = {
12098
12168
  // See: https://support.microsoft.com/en-us/office/datedif-function-25dba1a4-2812-480b-84dd-8b32a451b35c
12099
12169
  let days = jsEndDate.getDate() - jsStartDate.getDate();
12100
12170
  if (days < 0) {
12101
- const monthBeforeEndMonth = new Date(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
12171
+ const monthBeforeEndMonth = new DateTime(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
12102
12172
  const daysInMonthBeforeEndMonth = getDaysInMonth(monthBeforeEndMonth);
12103
12173
  days = daysInMonthBeforeEndMonth - Math.abs(days);
12104
12174
  }
@@ -12107,7 +12177,7 @@ const DATEDIF = {
12107
12177
  if (areTwoDatesWithinOneYear(_startDate, _endDate)) {
12108
12178
  return getTimeDifferenceInWholeDays(jsStartDate, jsEndDate);
12109
12179
  }
12110
- const endDateWithinOneYear = new Date(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
12180
+ const endDateWithinOneYear = new DateTime(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
12111
12181
  let days = getTimeDifferenceInWholeDays(jsStartDate, endDateWithinOneYear);
12112
12182
  if (days < 0) {
12113
12183
  endDateWithinOneYear.setFullYear(jsStartDate.getFullYear() + 1);
@@ -12220,7 +12290,7 @@ const EOMONTH = {
12220
12290
  const _months = Math.trunc(toNumber(months));
12221
12291
  const yStart = _startDate.getFullYear();
12222
12292
  const mStart = _startDate.getMonth();
12223
- const jsDate = new Date(yStart, mStart + _months + 1, 0);
12293
+ const jsDate = new DateTime(yStart, mStart + _months + 1, 0);
12224
12294
  return jsDateToRoundNumber(jsDate);
12225
12295
  },
12226
12296
  isExported: true,
@@ -12257,17 +12327,17 @@ const ISOWEEKNUM = {
12257
12327
  // The first week of the year is the week that contains the first
12258
12328
  // Thursday of the year.
12259
12329
  let firstThursday = 1;
12260
- while (new Date(y, 0, firstThursday).getDay() !== 4) {
12330
+ while (new DateTime(y, 0, firstThursday).getDay() !== 4) {
12261
12331
  firstThursday += 1;
12262
12332
  }
12263
- const firstDayOfFirstWeek = new Date(y, 0, firstThursday - 3);
12333
+ const firstDayOfFirstWeek = new DateTime(y, 0, firstThursday - 3);
12264
12334
  // The last week of the year is the week that contains the last Thursday of
12265
12335
  // the year.
12266
12336
  let lastThursday = 31;
12267
- while (new Date(y, 11, lastThursday).getDay() !== 4) {
12337
+ while (new DateTime(y, 11, lastThursday).getDay() !== 4) {
12268
12338
  lastThursday -= 1;
12269
12339
  }
12270
- const lastDayOfLastWeek = new Date(y, 11, lastThursday + 3);
12340
+ const lastDayOfLastWeek = new DateTime(y, 11, lastThursday + 3);
12271
12341
  // B - If our date > lastDayOfLastWeek then it's in the weeks of the year after
12272
12342
  // If our date < firstDayOfFirstWeek then it's in the weeks of the year before
12273
12343
  let offsetYear;
@@ -12293,17 +12363,17 @@ const ISOWEEKNUM = {
12293
12363
  case 1:
12294
12364
  // firstDay is the 1st day of the 1st week of the year after
12295
12365
  // firstDay = lastDayOfLastWeek + 1 Day
12296
- firstDay = new Date(y, 11, lastThursday + 3 + 1);
12366
+ firstDay = new DateTime(y, 11, lastThursday + 3 + 1);
12297
12367
  break;
12298
12368
  case -1:
12299
12369
  // firstDay is the 1st day of the 1st week of the previous year.
12300
12370
  // The first week of the previous year is the week that contains the
12301
12371
  // first Thursday of the previous year.
12302
12372
  let firstThursdayPreviousYear = 1;
12303
- while (new Date(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
12373
+ while (new DateTime(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
12304
12374
  firstThursdayPreviousYear += 1;
12305
12375
  }
12306
- firstDay = new Date(y - 1, 0, firstThursdayPreviousYear - 3);
12376
+ firstDay = new DateTime(y - 1, 0, firstThursdayPreviousYear - 3);
12307
12377
  break;
12308
12378
  }
12309
12379
  const diff = (_date.getTime() - firstDay.getTime()) / MS_PER_DAY;
@@ -12438,8 +12508,8 @@ const NETWORKDAYS_INTL = {
12438
12508
  });
12439
12509
  }
12440
12510
  const invertDate = _startDate.getTime() > _endDate.getTime();
12441
- const stopDate = new Date((invertDate ? _startDate : _endDate).getTime());
12442
- let stepDate = new Date((invertDate ? _endDate : _startDate).getTime());
12511
+ const stopDate = DateTime.fromTimestamp((invertDate ? _startDate : _endDate).getTime());
12512
+ let stepDate = DateTime.fromTimestamp((invertDate ? _endDate : _startDate).getTime());
12443
12513
  const timeStopDate = stopDate.getTime();
12444
12514
  let timeStepDate = stepDate.getTime();
12445
12515
  let netWorkingDay = 0;
@@ -12463,8 +12533,7 @@ const NOW = {
12463
12533
  returns: ["DATE"],
12464
12534
  computeFormat: () => "m/d/yyyy hh:mm:ss",
12465
12535
  compute: function () {
12466
- let today = new Date();
12467
- today.setMilliseconds(0);
12536
+ let today = DateTime.now();
12468
12537
  const delta = today.getTime() - INITIAL_1900_DAY.getTime();
12469
12538
  const time = today.getHours() / 24 + today.getMinutes() / 1440 + today.getSeconds() / 86400;
12470
12539
  return Math.floor(delta / MS_PER_DAY) + time;
@@ -12534,8 +12603,8 @@ const TODAY = {
12534
12603
  returns: ["DATE"],
12535
12604
  computeFormat: () => "m/d/yyyy",
12536
12605
  compute: function () {
12537
- const today = new Date();
12538
- const jsDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());
12606
+ const today = DateTime.now();
12607
+ const jsDate = new DateTime(today.getFullYear(), today.getMonth(), today.getDate());
12539
12608
  return jsDateToRoundNumber(jsDate);
12540
12609
  },
12541
12610
  isExported: true,
@@ -12590,10 +12659,10 @@ const WEEKNUM = {
12590
12659
  }
12591
12660
  const y = _date.getFullYear();
12592
12661
  let dayStart = 1;
12593
- let startDayOfFirstWeek = new Date(y, 0, dayStart);
12662
+ let startDayOfFirstWeek = new DateTime(y, 0, dayStart);
12594
12663
  while (startDayOfFirstWeek.getDay() !== startDayOfWeek) {
12595
12664
  dayStart += 1;
12596
- startDayOfFirstWeek = new Date(y, 0, dayStart);
12665
+ startDayOfFirstWeek = new DateTime(y, 0, dayStart);
12597
12666
  }
12598
12667
  const dif = (_date.getTime() - startDayOfFirstWeek.getTime()) / MS_PER_DAY;
12599
12668
  if (dif < 0) {
@@ -12647,7 +12716,7 @@ const WORKDAY_INTL = {
12647
12716
  timesHoliday.add(holiday.getTime());
12648
12717
  });
12649
12718
  }
12650
- let stepDate = new Date(_startDate.getTime());
12719
+ let stepDate = DateTime.fromTimestamp(_startDate.getTime());
12651
12720
  let timeStepDate = stepDate.getTime();
12652
12721
  const unitDay = Math.sign(_numDays);
12653
12722
  let stepDay = Math.abs(_numDays);
@@ -12709,7 +12778,7 @@ const MONTH_START = {
12709
12778
  const _startDate = toJsDate(date);
12710
12779
  const yStart = _startDate.getFullYear();
12711
12780
  const mStart = _startDate.getMonth();
12712
- const jsDate = new Date(yStart, mStart, 1);
12781
+ const jsDate = new DateTime(yStart, mStart, 1);
12713
12782
  return jsDateToRoundNumber(jsDate);
12714
12783
  },
12715
12784
  };
@@ -12747,7 +12816,7 @@ const QUARTER_START = {
12747
12816
  compute: function (date) {
12748
12817
  const quarter = QUARTER.compute(date);
12749
12818
  const year = YEAR.compute(date);
12750
- const jsDate = new Date(year, (quarter - 1) * 3, 1);
12819
+ const jsDate = new DateTime(year, (quarter - 1) * 3, 1);
12751
12820
  return jsDateToRoundNumber(jsDate);
12752
12821
  },
12753
12822
  };
@@ -12762,7 +12831,7 @@ const QUARTER_END = {
12762
12831
  compute: function (date) {
12763
12832
  const quarter = QUARTER.compute(date);
12764
12833
  const year = YEAR.compute(date);
12765
- const jsDate = new Date(year, quarter * 3, 0);
12834
+ const jsDate = new DateTime(year, quarter * 3, 0);
12766
12835
  return jsDateToRoundNumber(jsDate);
12767
12836
  },
12768
12837
  };
@@ -12776,7 +12845,7 @@ const YEAR_START = {
12776
12845
  computeFormat: () => "m/d/yyyy",
12777
12846
  compute: function (date) {
12778
12847
  const year = YEAR.compute(date);
12779
- const jsDate = new Date(year, 0, 1);
12848
+ const jsDate = new DateTime(year, 0, 1);
12780
12849
  return jsDateToRoundNumber(jsDate);
12781
12850
  },
12782
12851
  };
@@ -12790,7 +12859,7 @@ const YEAR_END = {
12790
12859
  computeFormat: () => "m/d/yyyy",
12791
12860
  compute: function (date) {
12792
12861
  const year = YEAR.compute(date);
12793
- const jsDate = new Date(year + 1, 0, 0);
12862
+ const jsDate = new DateTime(year + 1, 0, 0);
12794
12863
  return jsDateToRoundNumber(jsDate);
12795
12864
  },
12796
12865
  };
@@ -12838,8 +12907,8 @@ const DEFAULT_DELTA_ARG = 0;
12838
12907
  const DELTA = {
12839
12908
  description: _lt("Compare two numeric values, returning 1 if they're equal."),
12840
12909
  args: [
12841
- arg(" (number)", _lt("The first number to compare.")),
12842
- arg(` (number, default=${DEFAULT_DELTA_ARG})`, _lt("The second number to compare.")),
12910
+ arg("number1 (number)", _lt("The first number to compare.")),
12911
+ arg(`number2 (number, default=${DEFAULT_DELTA_ARG})`, _lt("The second number to compare.")),
12843
12912
  ],
12844
12913
  returns: ["NUMBER"],
12845
12914
  compute: function (number1, number2 = DEFAULT_DELTA_ARG) {
@@ -12925,7 +12994,7 @@ function assertDeprecationFactorStrictlyPositive(factor) {
12925
12994
  function assertSettlementLessThanOneYearBeforeMaturity(settlement, maturity) {
12926
12995
  const startDate = toJsDate(settlement);
12927
12996
  const endDate = toJsDate(maturity);
12928
- const startDatePlusOneYear = new Date(startDate);
12997
+ const startDatePlusOneYear = toJsDate(settlement);
12929
12998
  startDatePlusOneYear.setFullYear(startDate.getFullYear() + 1);
12930
12999
  assert(() => endDate.getTime() <= startDatePlusOneYear.getTime(), _lt("The settlement date (%s) must at most one year after the maturity date (%s).", settlement.toString(), maturity.toString()));
12931
13000
  }
@@ -13060,7 +13129,7 @@ const AMORLINC = {
13060
13129
  arg("salvage (number)", _lt("The value of the asset at the end of depreciation.")),
13061
13130
  arg("period (number)", _lt("The single period within life for which to calculate depreciation.")),
13062
13131
  arg("rate (number)", _lt("The deprecation rate.")),
13063
- arg(" (number, optional)", _lt("An indicator of what day count method to use.")),
13132
+ arg("day_count_convention (number, optional)", _lt("An indicator of what day count method to use.")),
13064
13133
  ],
13065
13134
  returns: ["NUMBER"],
13066
13135
  compute: function (cost, purchaseDate, firstPeriodEnd, salvage, period, rate, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {
@@ -16043,8 +16112,8 @@ const MID = {
16043
16112
  description: _lt("A segment of a string."),
16044
16113
  args: [
16045
16114
  arg("text (string)", _lt("The string to extract a segment from.")),
16046
- arg(" (number)", _lt("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
16047
- arg(" (number)", _lt("The length of the segment to extract.")),
16115
+ arg("starting_at (number)", _lt("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
16116
+ arg("extract_length (number)", _lt("The length of the segment to extract.")),
16048
16117
  ],
16049
16118
  returns: ["STRING"],
16050
16119
  compute: function (text, starting_at, extract_length) {
@@ -17713,10 +17782,10 @@ const arrowMap = {
17713
17782
  function updateSelectionWithArrowKeys(ev, selection) {
17714
17783
  const direction = arrowMap[ev.key];
17715
17784
  if (ev.shiftKey) {
17716
- selection.resizeAnchorZone(direction, ev.ctrlKey ? "end" : 1);
17785
+ selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
17717
17786
  }
17718
17787
  else {
17719
- selection.moveAnchorCell(direction, ev.ctrlKey ? "end" : 1);
17788
+ selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
17720
17789
  }
17721
17790
  }
17722
17791
 
@@ -22413,7 +22482,10 @@ class GridOverlay extends owl.Component {
22413
22482
  return;
22414
22483
  }
22415
22484
  const [col, row] = this.getCartesianCoordinates(ev);
22416
- this.props.onCellClicked(col, row, { shiftKey: ev.shiftKey, ctrlKey: ev.ctrlKey });
22485
+ this.props.onCellClicked(col, row, {
22486
+ expandZone: ev.shiftKey,
22487
+ addZone: isCtrlKey(ev),
22488
+ });
22417
22489
  }
22418
22490
  onDoubleClick(ev) {
22419
22491
  const [col, row] = this.getCartesianCoordinates(ev);
@@ -22486,6 +22558,7 @@ class AbstractResizer extends owl.Component {
22486
22558
  draggerShadowThickness: 0,
22487
22559
  delta: 0,
22488
22560
  base: 0,
22561
+ position: "before",
22489
22562
  });
22490
22563
  _computeHandleDisplay(ev) {
22491
22564
  const position = this._getEvOffset(ev);
@@ -22610,11 +22683,13 @@ class AbstractResizer extends owl.Component {
22610
22683
  this.state.draggerLinePosition = dimensions.start;
22611
22684
  this.state.draggerShadowPosition = dimensions.start;
22612
22685
  this.state.base = elementIndex;
22686
+ this.state.position = "before";
22613
22687
  }
22614
22688
  else if (this._getSelectedZoneEnd() < elementIndex) {
22615
22689
  this.state.draggerLinePosition = dimensions.end;
22616
22690
  this.state.draggerShadowPosition = dimensions.end - this.state.draggerShadowThickness;
22617
- this.state.base = elementIndex + 1;
22691
+ this.state.base = elementIndex;
22692
+ this.state.position = "after";
22618
22693
  }
22619
22694
  else {
22620
22695
  this.state.draggerLinePosition = startDimensions.start;
@@ -22638,7 +22713,7 @@ class AbstractResizer extends owl.Component {
22638
22713
  this._increaseSelection(index);
22639
22714
  }
22640
22715
  else {
22641
- this._selectElement(index, ev.ctrlKey);
22716
+ this._selectElement(index, isCtrlKey(ev));
22642
22717
  }
22643
22718
  this.lastSelectedElementIndex = index;
22644
22719
  const mouseMoveSelect = (col, row) => {
@@ -22651,7 +22726,7 @@ class AbstractResizer extends owl.Component {
22651
22726
  const mouseUpSelect = () => {
22652
22727
  this.state.isSelecting = false;
22653
22728
  this.lastSelectedElementIndex = null;
22654
- this.env.model.dispatch(ev.ctrlKey ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
22729
+ this.env.model.dispatch(isCtrlKey(ev) ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
22655
22730
  this._computeGrabDisplay(ev);
22656
22731
  };
22657
22732
  dragAndDropBeyondTheViewport(this.env, mouseMoveSelect, mouseUpSelect);
@@ -22797,13 +22872,14 @@ class ColResizer extends AbstractResizer {
22797
22872
  dimension: "COL",
22798
22873
  base: this.state.base,
22799
22874
  elements,
22875
+ position: this.state.position,
22800
22876
  });
22801
22877
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
22802
22878
  this.env.raiseError(MergeErrorMessage);
22803
22879
  }
22804
22880
  }
22805
- _selectElement(index, ctrlKey) {
22806
- this.env.model.selection.selectColumn(index, ctrlKey ? "newAnchor" : "overrideSelection");
22881
+ _selectElement(index, addDistinctHeader) {
22882
+ this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
22807
22883
  }
22808
22884
  _increaseSelection(index) {
22809
22885
  this.env.model.selection.selectColumn(index, "updateAnchor");
@@ -22970,13 +23046,14 @@ class RowResizer extends AbstractResizer {
22970
23046
  dimension: "ROW",
22971
23047
  base: this.state.base,
22972
23048
  elements,
23049
+ position: this.state.position,
22973
23050
  });
22974
23051
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
22975
23052
  this.env.raiseError(MergeErrorMessage);
22976
23053
  }
22977
23054
  }
22978
- _selectElement(index, ctrlKey) {
22979
- this.env.model.selection.selectRow(index, ctrlKey ? "newAnchor" : "overrideSelection");
23055
+ _selectElement(index, addDistinctHeader) {
23056
+ this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
22980
23057
  }
22981
23058
  _increaseSelection(index) {
22982
23059
  this.env.model.selection.selectRow(index, "updateAnchor");
@@ -23077,8 +23154,8 @@ function useWheelHandler(handler) {
23077
23154
  return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT);
23078
23155
  }
23079
23156
  const onMouseWheel = (ev) => {
23080
- const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode);
23081
- const deltaY = normalize(ev.shiftKey ? ev.deltaX : ev.deltaY, ev.deltaMode);
23157
+ const deltaX = normalize(ev.shiftKey && !isMacOS() ? ev.deltaY : ev.deltaX, ev.deltaMode);
23158
+ const deltaY = normalize(ev.shiftKey && !isMacOS() ? ev.deltaX : ev.deltaY, ev.deltaMode);
23082
23159
  handler(deltaX, deltaY);
23083
23160
  };
23084
23161
  return onMouseWheel;
@@ -23780,8 +23857,8 @@ class Grid extends owl.Component {
23780
23857
  // ---------------------------------------------------------------------------
23781
23858
  // Zone selection with mouse
23782
23859
  // ---------------------------------------------------------------------------
23783
- onCellClicked(col, row, { ctrlKey, shiftKey }) {
23784
- if (ctrlKey) {
23860
+ onCellClicked(col, row, { addZone, expandZone }) {
23861
+ if (addZone) {
23785
23862
  this.env.model.dispatch("PREPARE_SELECTION_INPUT_EXPANSION");
23786
23863
  }
23787
23864
  if (this.env.model.getters.hasOpenedPopover()) {
@@ -23790,10 +23867,10 @@ class Grid extends owl.Component {
23790
23867
  if (this.env.model.getters.getEditionMode() === "editing") {
23791
23868
  this.env.model.dispatch("STOP_EDITION");
23792
23869
  }
23793
- if (shiftKey) {
23870
+ if (expandZone) {
23794
23871
  this.env.model.selection.setAnchorCorner(col, row);
23795
23872
  }
23796
- else if (ctrlKey) {
23873
+ else if (addZone) {
23797
23874
  this.env.model.selection.addCellToSelection(col, row);
23798
23875
  }
23799
23876
  else {
@@ -23855,9 +23932,7 @@ class Grid extends owl.Component {
23855
23932
  return;
23856
23933
  }
23857
23934
  let keyDownString = "";
23858
- if (ev.ctrlKey)
23859
- keyDownString += "CTRL+";
23860
- if (ev.metaKey)
23935
+ if (isCtrlKey(ev))
23861
23936
  keyDownString += "CTRL+";
23862
23937
  if (ev.altKey)
23863
23938
  keyDownString += "ALT+";
@@ -25765,7 +25840,7 @@ const machine = {
25765
25840
  SPACE: goTo(State.RightRef),
25766
25841
  NUMBER: goTo(State.Found),
25767
25842
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
25768
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
25843
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
25769
25844
  },
25770
25845
  [State.RightColumnRef]: {
25771
25846
  SPACE: goTo(State.RightColumnRef),
@@ -25776,6 +25851,7 @@ const machine = {
25776
25851
  SPACE: goTo(State.RightRowRef),
25777
25852
  NUMBER: goTo(State.Found),
25778
25853
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
25854
+ SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
25779
25855
  },
25780
25856
  [State.Found]: {},
25781
25857
  };
@@ -29483,7 +29559,7 @@ class BordersPlugin extends CorePlugin {
29483
29559
  */
29484
29560
  function getBorderId(border) {
29485
29561
  for (let [key, value] of Object.entries(borders)) {
29486
- if (stringify(value) === stringify(border)) {
29562
+ if (deepEquals(value, border)) {
29487
29563
  return parseInt(key, 10);
29488
29564
  }
29489
29565
  }
@@ -32451,7 +32527,7 @@ class SheetPlugin extends CorePlugin {
32451
32527
  const elements = cmd.dimension === "COL"
32452
32528
  ? this.getNumberCols(cmd.sheetId)
32453
32529
  : this.getNumberRows(cmd.sheetId);
32454
- if (cmd.base < 0 || cmd.base > elements) {
32530
+ if (cmd.base < 0 || cmd.base >= elements) {
32455
32531
  return 86 /* CommandResult.InvalidHeaderIndex */;
32456
32532
  }
32457
32533
  else if (cmd.quantity <= 0) {
@@ -33484,13 +33560,18 @@ class EvaluationPlugin extends UIPlugin {
33484
33560
  // Getters
33485
33561
  // ---------------------------------------------------------------------------
33486
33562
  evaluateFormula(formulaString, sheetId = this.getters.getActiveSheetId()) {
33487
- const compiledFormula = compile(formulaString);
33488
- const params = this.getCompilationParameters((cell) => this.getEvaluatedCell(this.getters.getCellPosition(cell.id)));
33489
- const ranges = [];
33490
- for (let xc of compiledFormula.dependencies) {
33491
- ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
33563
+ try {
33564
+ const compiledFormula = compile(formulaString);
33565
+ const params = this.getCompilationParameters((cell) => this.getEvaluatedCell(this.getters.getCellPosition(cell.id)));
33566
+ const ranges = [];
33567
+ for (let xc of compiledFormula.dependencies) {
33568
+ ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
33569
+ }
33570
+ return compiledFormula.execute(ranges, ...params).value;
33571
+ }
33572
+ catch (error) {
33573
+ return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
33492
33574
  }
33493
- return compiledFormula.execute(ranges, ...params).value;
33494
33575
  }
33495
33576
  /**
33496
33577
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -33915,39 +33996,34 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
33915
33996
  this.computedIcons[sheetId] = {};
33916
33997
  const computedStyle = this.computedStyles[sheetId];
33917
33998
  for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
33918
- try {
33919
- switch (cf.rule.type) {
33920
- case "ColorScaleRule":
33921
- for (let range of cf.ranges) {
33922
- this.applyColorScale(range, cf.rule);
33923
- }
33924
- break;
33925
- case "IconSetRule":
33926
- for (let range of cf.ranges) {
33927
- this.applyIcon(range, cf.rule);
33928
- }
33929
- break;
33930
- default:
33931
- for (let ref of cf.ranges) {
33932
- const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
33933
- for (let row = zone.top; row <= zone.bottom; row++) {
33934
- for (let col = zone.left; col <= zone.right; col++) {
33935
- const pr = this.rulePredicate[cf.rule.type];
33936
- let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
33937
- if (pr && pr(cell, cf.rule)) {
33938
- if (!computedStyle[col])
33939
- computedStyle[col] = [];
33940
- // we must combine all the properties of all the CF rules applied to the given cell
33941
- computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
33942
- }
33999
+ switch (cf.rule.type) {
34000
+ case "ColorScaleRule":
34001
+ for (let range of cf.ranges) {
34002
+ this.applyColorScale(range, cf.rule);
34003
+ }
34004
+ break;
34005
+ case "IconSetRule":
34006
+ for (let range of cf.ranges) {
34007
+ this.applyIcon(range, cf.rule);
34008
+ }
34009
+ break;
34010
+ default:
34011
+ for (let ref of cf.ranges) {
34012
+ const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
34013
+ for (let row = zone.top; row <= zone.bottom; row++) {
34014
+ for (let col = zone.left; col <= zone.right; col++) {
34015
+ const pr = this.rulePredicate[cf.rule.type];
34016
+ let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
34017
+ if (pr && pr(cell, cf.rule)) {
34018
+ if (!computedStyle[col])
34019
+ computedStyle[col] = [];
34020
+ // we must combine all the properties of all the CF rules applied to the given cell
34021
+ computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
33943
34022
  }
33944
34023
  }
33945
34024
  }
33946
- break;
33947
- }
33948
- }
33949
- catch (_) {
33950
- // we don't care about the errors within the evaluation of a rule
34025
+ }
34026
+ break;
33951
34027
  }
33952
34028
  }
33953
34029
  }
@@ -33972,7 +34048,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
33972
34048
  return percentile(rangeValues, Number(threshold.value) / 100, true);
33973
34049
  case "formula":
33974
34050
  const value = threshold.value && this.getters.evaluateFormula(threshold.value);
33975
- return !(value instanceof Promise) ? value : null;
34051
+ return typeof value === "number" ? value : null;
33976
34052
  default:
33977
34053
  return null;
33978
34054
  }
@@ -35621,19 +35697,21 @@ class AutofillPlugin extends UIPlugin {
35621
35697
  let col = zone.left;
35622
35698
  let row = zone.bottom;
35623
35699
  if (col > 0) {
35624
- let left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
35625
- while (left.type !== CellValueType.empty) {
35700
+ let leftPosition = { sheetId, col: col - 1, row };
35701
+ while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
35702
+ this.getters.getCell(leftPosition)?.content) {
35626
35703
  row += 1;
35627
- left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
35704
+ leftPosition = { sheetId, col: col - 1, row };
35628
35705
  }
35629
35706
  }
35630
35707
  if (row === zone.bottom) {
35631
35708
  col = zone.right;
35632
35709
  if (col <= this.getters.getNumberCols(sheetId)) {
35633
- let right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
35634
- while (right.type !== CellValueType.empty) {
35710
+ let rightPosition = { sheetId, col: col + 1, row };
35711
+ while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
35712
+ this.getters.getCell(rightPosition)?.content) {
35635
35713
  row += 1;
35636
- right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
35714
+ rightPosition = { sheetId, col: col + 1, row };
35637
35715
  }
35638
35716
  }
35639
35717
  }
@@ -40581,6 +40659,10 @@ function setXcToReferenceType(xc, referenceType) {
40581
40659
  return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
40582
40660
  case "colrow":
40583
40661
  indexOfNumber = xc.search(/[0-9]/);
40662
+ if (indexOfNumber === -1 || indexOfNumber === 0) {
40663
+ // no row number (eg. A) or no column (eg. 1)
40664
+ return "$" + xc;
40665
+ }
40584
40666
  xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
40585
40667
  return "$" + xc;
40586
40668
  case "none":
@@ -41762,7 +41844,7 @@ class GridSelectionPlugin extends UIPlugin {
41762
41844
  sheetId: cmd.sheetId,
41763
41845
  base: cmd.base,
41764
41846
  quantity: thickness,
41765
- position: "before",
41847
+ position: cmd.position,
41766
41848
  });
41767
41849
  const isCol = cmd.dimension === "COL";
41768
41850
  const start = cmd.elements[0];
@@ -41779,12 +41861,13 @@ class GridSelectionPlugin extends UIPlugin {
41779
41861
  },
41780
41862
  ];
41781
41863
  const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
41864
+ const base = isBasedBefore ? cmd.base : cmd.base + 1;
41782
41865
  const pasteTarget = [
41783
41866
  {
41784
- left: isCol ? cmd.base : 0,
41785
- right: isCol ? cmd.base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
41786
- top: !isCol ? cmd.base : 0,
41787
- bottom: !isCol ? cmd.base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
41867
+ left: isCol ? base : 0,
41868
+ right: isCol ? base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
41869
+ top: !isCol ? base : 0,
41870
+ bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
41788
41871
  },
41789
41872
  ];
41790
41873
  state.paste(pasteTarget, { selectTarget: true });
@@ -41821,6 +41904,11 @@ class GridSelectionPlugin extends UIPlugin {
41821
41904
  doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) {
41822
41905
  return 2 /* CommandResult.WillRemoveExistingMerge */;
41823
41906
  }
41907
+ const headers = [cmd.base, ...cmd.elements];
41908
+ const maxHeaderValue = isCol ? this.getters.getNumberCols(id) : this.getters.getNumberRows(id);
41909
+ if (headers.some((h) => h < 0 || h >= maxHeaderValue)) {
41910
+ return 86 /* CommandResult.InvalidHeaderIndex */;
41911
+ }
41824
41912
  return 0 /* CommandResult.Success */;
41825
41913
  }
41826
41914
  //-------------------------------------------
@@ -44102,7 +44190,7 @@ class Spreadsheet extends owl.Component {
44102
44190
  }
44103
44191
  onKeydown(ev) {
44104
44192
  let keyDownString = "";
44105
- if (ev.ctrlKey || ev.metaKey) {
44193
+ if (isCtrlKey(ev)) {
44106
44194
  keyDownString += "CTRL+";
44107
44195
  }
44108
44196
  keyDownString += ev.key.toUpperCase();
@@ -47839,6 +47927,6 @@ exports.setTranslationMethod = setTranslationMethod;
47839
47927
  exports.tokenize = tokenize;
47840
47928
 
47841
47929
 
47842
- __info__.version = '16.3.18';
47843
- __info__.date = '2023-11-24T13:10:15.278Z';
47844
- __info__.hash = '45f745e';
47930
+ __info__.version = '16.3.22';
47931
+ __info__.date = '2024-01-17T10:24:07.652Z';
47932
+ __info__.hash = '2c1b136';