@odoo/o-spreadsheet 16.3.3 → 16.3.4

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.
@@ -750,12 +750,20 @@ function getDefaultCellHeight(cell) {
750
750
  return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
751
751
  }
752
752
  function computeTextWidth(context, text, style) {
753
- context.save();
754
- context.font = computeTextFont(style);
755
- const textWidth = context.measureText(text).width;
756
- context.restore();
757
- return textWidth;
753
+ const font = computeTextFont(style);
754
+ if (!textWidthCache[font]) {
755
+ textWidthCache[font] = {};
756
+ }
757
+ if (textWidthCache[font][text] === undefined) {
758
+ context.save();
759
+ context.font = font;
760
+ const textWidth = context.measureText(text).width;
761
+ context.restore();
762
+ textWidthCache[font][text] = textWidth;
763
+ }
764
+ return textWidthCache[font][text];
758
765
  }
766
+ const textWidthCache = {};
759
767
  function fontSizeInPixels(fontSize) {
760
768
  return Math.round((fontSize * 96) / 72);
761
769
  }
@@ -9515,9 +9523,9 @@ const ARG_TYPES = [
9515
9523
  "META",
9516
9524
  ];
9517
9525
  function arg(definition, description = "") {
9518
- return makeArg(`${definition} ${description}`);
9526
+ return makeArg(definition, description);
9519
9527
  }
9520
- function makeArg(str) {
9528
+ function makeArg(str, description) {
9521
9529
  let parts = str.match(ARG_REGEXP);
9522
9530
  let name = parts[1].trim();
9523
9531
  let types = [];
@@ -9547,7 +9555,6 @@ function makeArg(str) {
9547
9555
  defaultValue = param.trim().slice(8);
9548
9556
  }
9549
9557
  }
9550
- let description = parts[3].trim();
9551
9558
  const result = {
9552
9559
  name,
9553
9560
  description,
@@ -20928,6 +20935,7 @@ class Composer extends Component {
20928
20935
  (this.autoCompleteState.selectedIndex + 1) % this.autoCompleteState.values.length;
20929
20936
  }
20930
20937
  }
20938
+ this.updateCursorIfNeeded();
20931
20939
  }
20932
20940
  processTabKey(ev) {
20933
20941
  ev.preventDefault();
@@ -20996,10 +21004,12 @@ class Composer extends Component {
20996
21004
  }
20997
21005
  else {
20998
21006
  ev.stopPropagation();
21007
+ this.updateCursorIfNeeded();
20999
21008
  }
21000
- const { start, end } = this.contentHelper.getCurrentSelection();
21001
- if (!this.env.model.getters.isSelectingForComposer() &&
21002
- !(ev.key === "Enter" && (ev.altKey || ev.ctrlKey))) {
21009
+ }
21010
+ updateCursorIfNeeded() {
21011
+ if (!this.env.model.getters.isSelectingForComposer()) {
21012
+ const { start, end } = this.contentHelper.getCurrentSelection();
21003
21013
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", { start, end });
21004
21014
  this.isKeyStillDown = true;
21005
21015
  }
@@ -21057,7 +21067,12 @@ class Composer extends Component {
21057
21067
  };
21058
21068
  });
21059
21069
  if (searchTerm) {
21060
- values = fuzzyLookup(searchTerm, values, (t) => t.text);
21070
+ if (searchTerm.toLowerCase() === "true" || searchTerm.toLowerCase() === "false") {
21071
+ values = [];
21072
+ }
21073
+ else {
21074
+ values = fuzzyLookup(searchTerm, values, (t) => t.text);
21075
+ }
21061
21076
  }
21062
21077
  else {
21063
21078
  // alphabetical order
@@ -30640,7 +30655,14 @@ class FiltersPlugin extends CorePlugin {
30640
30655
  this.history.update("tables", filterTables);
30641
30656
  break;
30642
30657
  case "DUPLICATE_SHEET":
30643
- this.history.update("tables", cmd.sheetIdTo, deepCopy(this.tables[cmd.sheetId]));
30658
+ const tables = {};
30659
+ for (const filterTable of Object.values(this.tables[cmd.sheetId] || {})) {
30660
+ if (filterTable) {
30661
+ const newFilterTable = deepCopy(filterTable);
30662
+ tables[newFilterTable.id] = newFilterTable;
30663
+ }
30664
+ }
30665
+ this.history.update("tables", cmd.sheetIdTo, tables);
30644
30666
  break;
30645
30667
  case "ADD_COLUMNS_ROWS":
30646
30668
  this.onAddColumnsRows(cmd);
@@ -34189,6 +34211,7 @@ class FilterEvaluationPlugin extends UIPlugin {
34189
34211
  }
34190
34212
  exportForExcel(data) {
34191
34213
  for (const sheetData of data.sheets) {
34214
+ const sheetId = sheetData.id;
34192
34215
  for (const tableData of sheetData.filterTables) {
34193
34216
  const tableZone = toZone(tableData.range);
34194
34217
  const filters = [];
@@ -34204,20 +34227,20 @@ class FilterEvaluationPlugin extends UIPlugin {
34204
34227
  if (!filter)
34205
34228
  continue;
34206
34229
  const valuesInFilterZone = filter.filteredZone
34207
- ? positions(filter.filteredZone)
34208
- .map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId: sheetData.id, col, row }))
34209
- .filter((cell) => cell.type !== CellValueType.empty)
34210
- .map((cell) => cell.formattedValue)
34230
+ ? positions(filter.filteredZone).map((position) => this.getters.getEvaluatedCell({ sheetId, ...position }).formattedValue)
34211
34231
  : [];
34212
- // In xlsx, filtered values = values that are displayed, not values that are hidden
34213
- const xlsxFilteredValues = valuesInFilterZone.filter((val) => !filteredValues.includes(val));
34214
- filters.push({ colId: i, filteredValues: [...new Set(xlsxFilteredValues)] });
34215
- // In xlsx, filter header should ALWAYS be a string and should be unique
34216
- const headerPosition = {
34217
- col: filter.col,
34218
- row: filter.zoneWithHeaders.top,
34219
- sheetId: sheetData.id,
34220
- };
34232
+ if (filteredValues.length) {
34233
+ const xlsxDisplayedValues = valuesInFilterZone
34234
+ .filter((val) => val)
34235
+ .filter((val) => !filteredValues.includes(val));
34236
+ filters.push({
34237
+ colId: i,
34238
+ displayedValues: [...new Set(xlsxDisplayedValues)],
34239
+ displayBlanks: !filteredValues.includes("") && valuesInFilterZone.some((val) => !val),
34240
+ });
34241
+ }
34242
+ // In xlsx, filter header should ALWAYS be a string and should be unique in the table
34243
+ const headerPosition = { col: filter.col, row: filter.zoneWithHeaders.top, sheetId };
34221
34244
  const headerString = this.getters.getEvaluatedCell(headerPosition).formattedValue;
34222
34245
  const headerName = this.getUniqueColNameForExcel(i, headerString, headerNames);
34223
34246
  headerNames.push(headerName);
@@ -43289,11 +43312,9 @@ TopBarComposer.props = {
43289
43312
  onComposerContentFocused: Function,
43290
43313
  };
43291
43314
 
43292
- // -----------------------------------------------------------------------------
43293
- // TopBar
43294
- // -----------------------------------------------------------------------------
43295
43315
  css /* scss */ `
43296
43316
  .o-font-size-editor {
43317
+ height: calc(100% - 4px);
43297
43318
  input.o-font-size {
43298
43319
  outline-color: ${SELECTION_BORDER_COLOR};
43299
43320
  height: 20px;
@@ -43306,13 +43327,13 @@ css /* scss */ `
43306
43327
  input::-webkit-inner-spin-button {
43307
43328
  -webkit-appearance: none;
43308
43329
  }
43309
-
43310
- .o-text-options > div {
43311
- line-height: 26px;
43312
- padding: 3px 12px;
43313
- &:hover {
43314
- background-color: rgba(0, 0, 0, 0.08);
43315
- }
43330
+ }
43331
+ .o-text-options > div {
43332
+ cursor: pointer;
43333
+ line-height: 26px;
43334
+ padding: 3px 12px;
43335
+ &:hover {
43336
+ background-color: rgba(0, 0, 0, 0.08);
43316
43337
  }
43317
43338
  }
43318
43339
  `;
@@ -43377,6 +43398,7 @@ class FontSizeEditor extends Component {
43377
43398
  FontSizeEditor.props = {
43378
43399
  onToggle: Function,
43379
43400
  dropdownStyle: String,
43401
+ class: String,
43380
43402
  };
43381
43403
 
43382
43404
  // -----------------------------------------------------------------------------
@@ -46545,15 +46567,10 @@ function addAutoFilter(table) {
46545
46567
  `;
46546
46568
  }
46547
46569
  function addFilterColumns(table) {
46548
- const tableZone = toZone(table.range);
46549
46570
  const columns = [];
46550
- for (const i of range(0, zoneToDimension(tableZone).numberOfCols)) {
46551
- const filter = table.filters[i];
46552
- if (!filter || !filter.filteredValues.length) {
46553
- continue;
46554
- }
46571
+ for (const filter of table.filters) {
46555
46572
  const colXml = escapeXml /*xml*/ `
46556
- <filterColumn ${formatAttributes([["colId", i]])}>
46573
+ <filterColumn ${formatAttributes([["colId", filter.colId]])}>
46557
46574
  ${addFilter(filter)}
46558
46575
  </filterColumn>
46559
46576
  `;
@@ -46562,9 +46579,10 @@ function addFilterColumns(table) {
46562
46579
  return columns;
46563
46580
  }
46564
46581
  function addFilter(filter) {
46565
- const filterValues = filter.filteredValues.map((val) => escapeXml /*xml*/ `<filter ${formatAttributes([["val", val]])}/>`);
46582
+ const filterValues = filter.displayedValues.map((val) => escapeXml /*xml*/ `<filter ${formatAttributes([["val", val]])}/>`);
46583
+ const filterAttributes = filter.displayBlanks ? [["blank", 1]] : [];
46566
46584
  return escapeXml /*xml*/ `
46567
- <filters>
46585
+ <filters ${formatAttributes(filterAttributes)}>
46568
46586
  ${joinXmlNodes(filterValues)}
46569
46587
  </filters>
46570
46588
  `;
@@ -46683,8 +46701,13 @@ function addHyperlinks(construct, data, sheetIndex) {
46683
46701
  const sheetId = parseSheetUrl(url);
46684
46702
  const sheet = data.sheets.find((sheet) => sheet.id === sheetId);
46685
46703
  const location = sheet ? `${sheet.name}!A1` : INCORRECT_RANGE_STRING;
46704
+ const hyperlinkAttributes = [
46705
+ ["display", label],
46706
+ ["location", location],
46707
+ ["ref", xc],
46708
+ ];
46686
46709
  linkNodes.push(escapeXml /*xml*/ `
46687
- <hyperlink display="${label}" location="${location}" ref="${xc}"/>
46710
+ <hyperlink ${formatAttributes(hyperlinkAttributes)}/>
46688
46711
  `);
46689
46712
  }
46690
46713
  else {
@@ -46693,8 +46716,12 @@ function addHyperlinks(construct, data, sheetIndex) {
46693
46716
  type: XLSX_RELATION_TYPE.hyperlink,
46694
46717
  targetMode: "External",
46695
46718
  });
46719
+ const hyperlinkAttributes = [
46720
+ ["r:id", linkRelId],
46721
+ ["ref", xc],
46722
+ ];
46696
46723
  linkNodes.push(escapeXml /*xml*/ `
46697
- <hyperlink r:id="${linkRelId}" ref="${xc}"/>
46724
+ <hyperlink ${formatAttributes(hyperlinkAttributes)}/>
46698
46725
  `);
46699
46726
  }
46700
46727
  }
@@ -47574,6 +47601,6 @@ function addFunction(functionName, functionDescription) {
47574
47601
  export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, components, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, invalidateEvaluationCommands, links, load, parse, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
47575
47602
 
47576
47603
 
47577
- __info__.version = '16.3.3';
47578
- __info__.date = '2023-06-12T13:50:01.114Z';
47579
- __info__.hash = 'd3ab96f';
47604
+ __info__.version = '16.3.4';
47605
+ __info__.date = '2023-06-26T14:34:27.019Z';
47606
+ __info__.hash = '94f9dcd';
@@ -751,12 +751,20 @@
751
751
  return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
752
752
  }
753
753
  function computeTextWidth(context, text, style) {
754
- context.save();
755
- context.font = computeTextFont(style);
756
- const textWidth = context.measureText(text).width;
757
- context.restore();
758
- return textWidth;
754
+ const font = computeTextFont(style);
755
+ if (!textWidthCache[font]) {
756
+ textWidthCache[font] = {};
757
+ }
758
+ if (textWidthCache[font][text] === undefined) {
759
+ context.save();
760
+ context.font = font;
761
+ const textWidth = context.measureText(text).width;
762
+ context.restore();
763
+ textWidthCache[font][text] = textWidth;
764
+ }
765
+ return textWidthCache[font][text];
759
766
  }
767
+ const textWidthCache = {};
760
768
  function fontSizeInPixels(fontSize) {
761
769
  return Math.round((fontSize * 96) / 72);
762
770
  }
@@ -9516,9 +9524,9 @@
9516
9524
  "META",
9517
9525
  ];
9518
9526
  function arg(definition, description = "") {
9519
- return makeArg(`${definition} ${description}`);
9527
+ return makeArg(definition, description);
9520
9528
  }
9521
- function makeArg(str) {
9529
+ function makeArg(str, description) {
9522
9530
  let parts = str.match(ARG_REGEXP);
9523
9531
  let name = parts[1].trim();
9524
9532
  let types = [];
@@ -9548,7 +9556,6 @@
9548
9556
  defaultValue = param.trim().slice(8);
9549
9557
  }
9550
9558
  }
9551
- let description = parts[3].trim();
9552
9559
  const result = {
9553
9560
  name,
9554
9561
  description,
@@ -20929,6 +20936,7 @@
20929
20936
  (this.autoCompleteState.selectedIndex + 1) % this.autoCompleteState.values.length;
20930
20937
  }
20931
20938
  }
20939
+ this.updateCursorIfNeeded();
20932
20940
  }
20933
20941
  processTabKey(ev) {
20934
20942
  ev.preventDefault();
@@ -20997,10 +21005,12 @@
20997
21005
  }
20998
21006
  else {
20999
21007
  ev.stopPropagation();
21008
+ this.updateCursorIfNeeded();
21000
21009
  }
21001
- const { start, end } = this.contentHelper.getCurrentSelection();
21002
- if (!this.env.model.getters.isSelectingForComposer() &&
21003
- !(ev.key === "Enter" && (ev.altKey || ev.ctrlKey))) {
21010
+ }
21011
+ updateCursorIfNeeded() {
21012
+ if (!this.env.model.getters.isSelectingForComposer()) {
21013
+ const { start, end } = this.contentHelper.getCurrentSelection();
21004
21014
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", { start, end });
21005
21015
  this.isKeyStillDown = true;
21006
21016
  }
@@ -21058,7 +21068,12 @@
21058
21068
  };
21059
21069
  });
21060
21070
  if (searchTerm) {
21061
- values = fuzzyLookup(searchTerm, values, (t) => t.text);
21071
+ if (searchTerm.toLowerCase() === "true" || searchTerm.toLowerCase() === "false") {
21072
+ values = [];
21073
+ }
21074
+ else {
21075
+ values = fuzzyLookup(searchTerm, values, (t) => t.text);
21076
+ }
21062
21077
  }
21063
21078
  else {
21064
21079
  // alphabetical order
@@ -30641,7 +30656,14 @@
30641
30656
  this.history.update("tables", filterTables);
30642
30657
  break;
30643
30658
  case "DUPLICATE_SHEET":
30644
- this.history.update("tables", cmd.sheetIdTo, deepCopy(this.tables[cmd.sheetId]));
30659
+ const tables = {};
30660
+ for (const filterTable of Object.values(this.tables[cmd.sheetId] || {})) {
30661
+ if (filterTable) {
30662
+ const newFilterTable = deepCopy(filterTable);
30663
+ tables[newFilterTable.id] = newFilterTable;
30664
+ }
30665
+ }
30666
+ this.history.update("tables", cmd.sheetIdTo, tables);
30645
30667
  break;
30646
30668
  case "ADD_COLUMNS_ROWS":
30647
30669
  this.onAddColumnsRows(cmd);
@@ -34190,6 +34212,7 @@
34190
34212
  }
34191
34213
  exportForExcel(data) {
34192
34214
  for (const sheetData of data.sheets) {
34215
+ const sheetId = sheetData.id;
34193
34216
  for (const tableData of sheetData.filterTables) {
34194
34217
  const tableZone = toZone(tableData.range);
34195
34218
  const filters = [];
@@ -34205,20 +34228,20 @@
34205
34228
  if (!filter)
34206
34229
  continue;
34207
34230
  const valuesInFilterZone = filter.filteredZone
34208
- ? positions(filter.filteredZone)
34209
- .map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId: sheetData.id, col, row }))
34210
- .filter((cell) => cell.type !== CellValueType.empty)
34211
- .map((cell) => cell.formattedValue)
34231
+ ? positions(filter.filteredZone).map((position) => this.getters.getEvaluatedCell({ sheetId, ...position }).formattedValue)
34212
34232
  : [];
34213
- // In xlsx, filtered values = values that are displayed, not values that are hidden
34214
- const xlsxFilteredValues = valuesInFilterZone.filter((val) => !filteredValues.includes(val));
34215
- filters.push({ colId: i, filteredValues: [...new Set(xlsxFilteredValues)] });
34216
- // In xlsx, filter header should ALWAYS be a string and should be unique
34217
- const headerPosition = {
34218
- col: filter.col,
34219
- row: filter.zoneWithHeaders.top,
34220
- sheetId: sheetData.id,
34221
- };
34233
+ if (filteredValues.length) {
34234
+ const xlsxDisplayedValues = valuesInFilterZone
34235
+ .filter((val) => val)
34236
+ .filter((val) => !filteredValues.includes(val));
34237
+ filters.push({
34238
+ colId: i,
34239
+ displayedValues: [...new Set(xlsxDisplayedValues)],
34240
+ displayBlanks: !filteredValues.includes("") && valuesInFilterZone.some((val) => !val),
34241
+ });
34242
+ }
34243
+ // In xlsx, filter header should ALWAYS be a string and should be unique in the table
34244
+ const headerPosition = { col: filter.col, row: filter.zoneWithHeaders.top, sheetId };
34222
34245
  const headerString = this.getters.getEvaluatedCell(headerPosition).formattedValue;
34223
34246
  const headerName = this.getUniqueColNameForExcel(i, headerString, headerNames);
34224
34247
  headerNames.push(headerName);
@@ -43290,11 +43313,9 @@
43290
43313
  onComposerContentFocused: Function,
43291
43314
  };
43292
43315
 
43293
- // -----------------------------------------------------------------------------
43294
- // TopBar
43295
- // -----------------------------------------------------------------------------
43296
43316
  css /* scss */ `
43297
43317
  .o-font-size-editor {
43318
+ height: calc(100% - 4px);
43298
43319
  input.o-font-size {
43299
43320
  outline-color: ${SELECTION_BORDER_COLOR};
43300
43321
  height: 20px;
@@ -43307,13 +43328,13 @@
43307
43328
  input::-webkit-inner-spin-button {
43308
43329
  -webkit-appearance: none;
43309
43330
  }
43310
-
43311
- .o-text-options > div {
43312
- line-height: 26px;
43313
- padding: 3px 12px;
43314
- &:hover {
43315
- background-color: rgba(0, 0, 0, 0.08);
43316
- }
43331
+ }
43332
+ .o-text-options > div {
43333
+ cursor: pointer;
43334
+ line-height: 26px;
43335
+ padding: 3px 12px;
43336
+ &:hover {
43337
+ background-color: rgba(0, 0, 0, 0.08);
43317
43338
  }
43318
43339
  }
43319
43340
  `;
@@ -43378,6 +43399,7 @@
43378
43399
  FontSizeEditor.props = {
43379
43400
  onToggle: Function,
43380
43401
  dropdownStyle: String,
43402
+ class: String,
43381
43403
  };
43382
43404
 
43383
43405
  // -----------------------------------------------------------------------------
@@ -46546,15 +46568,10 @@
46546
46568
  `;
46547
46569
  }
46548
46570
  function addFilterColumns(table) {
46549
- const tableZone = toZone(table.range);
46550
46571
  const columns = [];
46551
- for (const i of range(0, zoneToDimension(tableZone).numberOfCols)) {
46552
- const filter = table.filters[i];
46553
- if (!filter || !filter.filteredValues.length) {
46554
- continue;
46555
- }
46572
+ for (const filter of table.filters) {
46556
46573
  const colXml = escapeXml /*xml*/ `
46557
- <filterColumn ${formatAttributes([["colId", i]])}>
46574
+ <filterColumn ${formatAttributes([["colId", filter.colId]])}>
46558
46575
  ${addFilter(filter)}
46559
46576
  </filterColumn>
46560
46577
  `;
@@ -46563,9 +46580,10 @@
46563
46580
  return columns;
46564
46581
  }
46565
46582
  function addFilter(filter) {
46566
- const filterValues = filter.filteredValues.map((val) => escapeXml /*xml*/ `<filter ${formatAttributes([["val", val]])}/>`);
46583
+ const filterValues = filter.displayedValues.map((val) => escapeXml /*xml*/ `<filter ${formatAttributes([["val", val]])}/>`);
46584
+ const filterAttributes = filter.displayBlanks ? [["blank", 1]] : [];
46567
46585
  return escapeXml /*xml*/ `
46568
- <filters>
46586
+ <filters ${formatAttributes(filterAttributes)}>
46569
46587
  ${joinXmlNodes(filterValues)}
46570
46588
  </filters>
46571
46589
  `;
@@ -46684,8 +46702,13 @@
46684
46702
  const sheetId = parseSheetUrl(url);
46685
46703
  const sheet = data.sheets.find((sheet) => sheet.id === sheetId);
46686
46704
  const location = sheet ? `${sheet.name}!A1` : INCORRECT_RANGE_STRING;
46705
+ const hyperlinkAttributes = [
46706
+ ["display", label],
46707
+ ["location", location],
46708
+ ["ref", xc],
46709
+ ];
46687
46710
  linkNodes.push(escapeXml /*xml*/ `
46688
- <hyperlink display="${label}" location="${location}" ref="${xc}"/>
46711
+ <hyperlink ${formatAttributes(hyperlinkAttributes)}/>
46689
46712
  `);
46690
46713
  }
46691
46714
  else {
@@ -46694,8 +46717,12 @@
46694
46717
  type: XLSX_RELATION_TYPE.hyperlink,
46695
46718
  targetMode: "External",
46696
46719
  });
46720
+ const hyperlinkAttributes = [
46721
+ ["r:id", linkRelId],
46722
+ ["ref", xc],
46723
+ ];
46697
46724
  linkNodes.push(escapeXml /*xml*/ `
46698
- <hyperlink r:id="${linkRelId}" ref="${xc}"/>
46725
+ <hyperlink ${formatAttributes(hyperlinkAttributes)}/>
46699
46726
  `);
46700
46727
  }
46701
46728
  }
@@ -47605,9 +47632,9 @@
47605
47632
  Object.defineProperty(exports, '__esModule', { value: true });
47606
47633
 
47607
47634
 
47608
- __info__.version = '16.3.3';
47609
- __info__.date = '2023-06-12T13:50:01.114Z';
47610
- __info__.hash = 'd3ab96f';
47635
+ __info__.version = '16.3.4';
47636
+ __info__.date = '2023-06-26T14:34:27.019Z';
47637
+ __info__.hash = '94f9dcd';
47611
47638
 
47612
47639
 
47613
47640
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);