@odoo/o-spreadsheet 16.4.15 → 16.4.16

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.
@@ -301,13 +301,6 @@ const NEWLINE = "\n";
301
301
  const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 18, 24, 36];
302
302
 
303
303
  //------------------------------------------------------------------------------
304
- /**
305
- * Stringify an object, like JSON.stringify, except that the first level of keys
306
- * is ordered.
307
- */
308
- function stringify(obj) {
309
- return JSON.stringify(obj, Object.keys(obj).sort());
310
- }
311
304
  /**
312
305
  * Remove quotes from a quoted string
313
306
  * ```js
@@ -527,7 +520,7 @@ function isObjectEmptyRecursive(argument) {
527
520
  */
528
521
  function getItemId(item, itemsDic) {
529
522
  for (let [key, value] of Object.entries(itemsDic)) {
530
- if (stringify(value) === stringify(item)) {
523
+ if (deepEquals(value, item)) {
531
524
  return parseInt(key, 10);
532
525
  }
533
526
  }
@@ -2345,8 +2338,10 @@ function convertInternalFormatToFormat(internalFormat) {
2345
2338
  const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
2346
2339
  // Same as above, but matches the exact string (nothing before or after)
2347
2340
  const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
2348
- /** Reference of a column header (eg. A, AB) */
2349
- const colHeader = new RegExp(/^([A-Z]{1,3})+$/, "i");
2341
+ /** Reference of a column header (eg. A, AB, $A) */
2342
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
2343
+ /** Reference of a row header (eg. 1, $1) */
2344
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
2350
2345
  /** Reference of a column (eg. A, $CA, Sheet1!B) */
2351
2346
  const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
2352
2347
  /** Reference of a row (eg. 1, 59, Sheet1!9) */
@@ -2376,6 +2371,9 @@ function isRowReference(xc) {
2376
2371
  function isColHeader(str) {
2377
2372
  return colHeader.test(str);
2378
2373
  }
2374
+ function isRowHeader(str) {
2375
+ return rowHeader.test(str);
2376
+ }
2379
2377
  /**
2380
2378
  * Return true if the given xc is the reference of a single cell,
2381
2379
  * without any specified sheet (e.g. A1)
@@ -3048,18 +3046,10 @@ class RangeImpl {
3048
3046
  if (isFullCol) {
3049
3047
  parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
3050
3048
  parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
3051
- if (zone.left === zone.right) {
3052
- parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
3053
- parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
3054
- }
3055
3049
  }
3056
3050
  if (isFullRow) {
3057
3051
  parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
3058
3052
  parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
3059
- if (zone.top === zone.bottom) {
3060
- parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
3061
- parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
3062
- }
3063
3053
  }
3064
3054
  return parts;
3065
3055
  }
@@ -5648,6 +5638,9 @@ function* iterateChildren(el) {
5648
5638
  }
5649
5639
  function getOpenedMenus() {
5650
5640
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
5641
+ }
5642
+ function isMacOS() {
5643
+ return navigator.userAgent.toUpperCase().indexOf("MAC") >= 0;
5651
5644
  }
5652
5645
 
5653
5646
  /**
@@ -14704,7 +14697,7 @@ const machine = {
14704
14697
  SPACE: goTo(State.RightRef),
14705
14698
  NUMBER: goTo(State.Found),
14706
14699
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
14707
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
14700
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
14708
14701
  },
14709
14702
  [State.RightColumnRef]: {
14710
14703
  SPACE: goTo(State.RightColumnRef),
@@ -14715,6 +14708,7 @@ const machine = {
14715
14708
  SPACE: goTo(State.RightRowRef),
14716
14709
  NUMBER: goTo(State.Found),
14717
14710
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
14711
+ SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
14718
14712
  },
14719
14713
  [State.Found]: {},
14720
14714
  };
@@ -16872,7 +16866,7 @@ function createGaugeChartRuntime(chart, getters) {
16872
16866
  });
16873
16867
  return {
16874
16868
  chartJsConfig: config,
16875
- background: getters.getBackgroundOfSingleCellChart(chart.background, dataRange),
16869
+ background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
16876
16870
  };
16877
16871
  }
16878
16872
 
@@ -17596,7 +17590,7 @@ function createScorecardChartRuntime(chart, getters) {
17596
17590
  };
17597
17591
  baselineCell = getters.getEvaluatedCell(baselinePosition);
17598
17592
  }
17599
- const background = getters.getBackgroundOfSingleCellChart(chart.background, chart.keyValue);
17593
+ const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
17600
17594
  const locale = getters.getLocale();
17601
17595
  return {
17602
17596
  title: _t(chart.title),
@@ -17605,7 +17599,7 @@ function createScorecardChartRuntime(chart, getters) {
17605
17599
  baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
17606
17600
  baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
17607
17601
  baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
17608
- fontColor: chartFontColor(background),
17602
+ fontColor,
17609
17603
  background,
17610
17604
  baselineStyle: chart.baselineMode !== "percentage" && baseline
17611
17605
  ? getters.getCellStyle({
@@ -25621,6 +25615,7 @@ class AbstractResizer extends owl.Component {
25621
25615
  draggerShadowThickness: 0,
25622
25616
  delta: 0,
25623
25617
  base: 0,
25618
+ position: "before",
25624
25619
  });
25625
25620
  _computeHandleDisplay(ev) {
25626
25621
  const position = this._getEvOffset(ev);
@@ -25745,11 +25740,13 @@ class AbstractResizer extends owl.Component {
25745
25740
  this.state.draggerLinePosition = dimensions.start;
25746
25741
  this.state.draggerShadowPosition = dimensions.start;
25747
25742
  this.state.base = elementIndex;
25743
+ this.state.position = "before";
25748
25744
  }
25749
25745
  else if (this._getSelectedZoneEnd() < elementIndex) {
25750
25746
  this.state.draggerLinePosition = dimensions.end;
25751
25747
  this.state.draggerShadowPosition = dimensions.end - this.state.draggerShadowThickness;
25752
- this.state.base = elementIndex + 1;
25748
+ this.state.base = elementIndex;
25749
+ this.state.position = "after";
25753
25750
  }
25754
25751
  else {
25755
25752
  this.state.draggerLinePosition = startDimensions.start;
@@ -25932,6 +25929,7 @@ class ColResizer extends AbstractResizer {
25932
25929
  dimension: "COL",
25933
25930
  base: this.state.base,
25934
25931
  elements,
25932
+ position: this.state.position,
25935
25933
  });
25936
25934
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
25937
25935
  this.env.raiseError(MergeErrorMessage);
@@ -26105,6 +26103,7 @@ class RowResizer extends AbstractResizer {
26105
26103
  dimension: "ROW",
26106
26104
  base: this.state.base,
26107
26105
  elements,
26106
+ position: this.state.position,
26108
26107
  });
26109
26108
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
26110
26109
  this.env.raiseError(MergeErrorMessage);
@@ -26212,8 +26211,8 @@ function useWheelHandler(handler) {
26212
26211
  return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT);
26213
26212
  }
26214
26213
  const onMouseWheel = (ev) => {
26215
- const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode);
26216
- const deltaY = normalize(ev.shiftKey ? ev.deltaX : ev.deltaY, ev.deltaMode);
26214
+ const deltaX = normalize(ev.shiftKey && !isMacOS() ? ev.deltaY : ev.deltaX, ev.deltaMode);
26215
+ const deltaY = normalize(ev.shiftKey && !isMacOS() ? ev.deltaX : ev.deltaY, ev.deltaMode);
26217
26216
  handler(deltaX, deltaY);
26218
26217
  };
26219
26218
  return onMouseWheel;
@@ -31642,7 +31641,7 @@ class BordersPlugin extends CorePlugin {
31642
31641
  */
31643
31642
  function getBorderId(border) {
31644
31643
  for (let [key, value] of Object.entries(borders)) {
31645
- if (stringify(value) === stringify(border)) {
31644
+ if (deepEquals(value, border)) {
31646
31645
  return parseInt(key, 10);
31647
31646
  }
31648
31647
  }
@@ -34551,7 +34550,7 @@ class SheetPlugin extends CorePlugin {
34551
34550
  const elements = cmd.dimension === "COL"
34552
34551
  ? this.getNumberCols(cmd.sheetId)
34553
34552
  : this.getNumberRows(cmd.sheetId);
34554
- if (cmd.base < 0 || cmd.base > elements) {
34553
+ if (cmd.base < 0 || cmd.base >= elements) {
34555
34554
  return 88 /* CommandResult.InvalidHeaderIndex */;
34556
34555
  }
34557
34556
  else if (cmd.quantity <= 0) {
@@ -35492,10 +35491,13 @@ class CompilationParametersBuilder {
35492
35491
  if (evaluatedCell === undefined) {
35493
35492
  return { value: null, format: this.getters.getCell(position)?.format };
35494
35493
  }
35494
+ if (evaluatedCell.type === CellValueType.error) {
35495
+ throw evaluatedCell.error;
35496
+ }
35495
35497
  return evaluatedCell;
35496
35498
  }
35497
35499
  getEvaluatedCellIfNotEmpty(position) {
35498
- const evaluatedCell = this.getEvaluatedCell(position);
35500
+ const evaluatedCell = this.computeCell(position);
35499
35501
  if (evaluatedCell.type === CellValueType.empty) {
35500
35502
  const cell = this.getters.getCell(position);
35501
35503
  if (!cell || (!cell.isFormula && cell.content === "")) {
@@ -35504,13 +35506,6 @@ class CompilationParametersBuilder {
35504
35506
  }
35505
35507
  return evaluatedCell;
35506
35508
  }
35507
- getEvaluatedCell(position) {
35508
- const evaluatedCell = this.computeCell(position);
35509
- if (evaluatedCell.type === CellValueType.error) {
35510
- throw evaluatedCell.error;
35511
- }
35512
- return evaluatedCell;
35513
- }
35514
35509
  /**
35515
35510
  * Return the values of the cell(s) used in reference, but always in the format of a range even
35516
35511
  * if a single cell is referenced. It is a list of col values. This is useful for the formulas that describe parameters as
@@ -35533,7 +35528,11 @@ class CompilationParametersBuilder {
35533
35528
  const { top, left, bottom, right } = zone;
35534
35529
  const cacheKey = `${sheetId}-${top}-${left}-${bottom}-${right}`;
35535
35530
  if (cacheKey in this.rangeCache) {
35536
- return this.rangeCache[cacheKey];
35531
+ const result = this.rangeCache[cacheKey];
35532
+ if (result instanceof EvaluationError) {
35533
+ throw result;
35534
+ }
35535
+ return result;
35537
35536
  }
35538
35537
  const height = _zone.bottom - _zone.top + 1;
35539
35538
  const width = _zone.right - _zone.left + 1;
@@ -35546,6 +35545,10 @@ class CompilationParametersBuilder {
35546
35545
  format[colIndex] = new Array(height);
35547
35546
  for (let row = _zone.top; row <= _zone.bottom; row++) {
35548
35547
  const evaluatedCell = this.getEvaluatedCellIfNotEmpty({ sheetId, col, row });
35548
+ if (evaluatedCell?.type === CellValueType.error) {
35549
+ this.rangeCache[cacheKey] = evaluatedCell.error;
35550
+ throw evaluatedCell.error;
35551
+ }
35549
35552
  const rowIndex = row - _zone.top;
35550
35553
  value[colIndex][rowIndex] = evaluatedCell?.value;
35551
35554
  if (evaluatedCell?.format !== undefined) {
@@ -36535,7 +36538,7 @@ class CustomColorsPlugin extends UIPlugin {
36535
36538
  }
36536
36539
 
36537
36540
  class EvaluationChartPlugin extends UIPlugin {
36538
- static getters = ["getChartRuntime", "getBackgroundOfSingleCellChart"];
36541
+ static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
36539
36542
  charts = {};
36540
36543
  createRuntimeChart = chartRuntimeFactory(this.getters);
36541
36544
  handle(cmd) {
@@ -36573,25 +36576,26 @@ class EvaluationChartPlugin extends UIPlugin {
36573
36576
  return this.charts[figureId];
36574
36577
  }
36575
36578
  /**
36576
- * Get the background color of a chart based on the color of the first cell of the main range
36577
- * of the chart. In order of priority, it will return :
36578
- *
36579
- * - the chart background color if one is defined
36580
- * - the fill color of the cell if one is defined
36581
- * - the fill color of the cell from conditional formats if one is defined
36582
- * - the default chart color if no other color is defined
36579
+ * Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
36583
36580
  */
36584
- getBackgroundOfSingleCellChart(chartBackground, mainRange) {
36581
+ getStyleOfSingleCellChart(chartBackground, mainRange) {
36585
36582
  if (chartBackground)
36586
- return chartBackground;
36583
+ return { background: chartBackground, fontColor: chartFontColor(chartBackground) };
36587
36584
  if (!mainRange) {
36588
- return BACKGROUND_CHART_COLOR;
36585
+ return {
36586
+ background: BACKGROUND_CHART_COLOR,
36587
+ fontColor: chartFontColor(BACKGROUND_CHART_COLOR),
36588
+ };
36589
36589
  }
36590
36590
  const col = mainRange.zone.left;
36591
36591
  const row = mainRange.zone.top;
36592
36592
  const sheetId = mainRange.sheetId;
36593
36593
  const style = this.getters.getCellComputedStyle({ sheetId, col, row });
36594
- return style.fillColor || BACKGROUND_CHART_COLOR;
36594
+ const background = style.fillColor || BACKGROUND_CHART_COLOR;
36595
+ return {
36596
+ background,
36597
+ fontColor: style.textColor || chartFontColor(background),
36598
+ };
36595
36599
  }
36596
36600
  }
36597
36601
 
@@ -37290,19 +37294,21 @@ class AutofillPlugin extends UIPlugin {
37290
37294
  let col = zone.left;
37291
37295
  let row = zone.bottom;
37292
37296
  if (col > 0) {
37293
- let left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
37294
- while (left.type !== CellValueType.empty) {
37297
+ let leftPosition = { sheetId, col: col - 1, row };
37298
+ while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
37299
+ this.getters.getCell(leftPosition)?.content) {
37295
37300
  row += 1;
37296
- left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
37301
+ leftPosition = { sheetId, col: col - 1, row };
37297
37302
  }
37298
37303
  }
37299
37304
  if (row === zone.bottom) {
37300
37305
  col = zone.right;
37301
37306
  if (col <= this.getters.getNumberCols(sheetId)) {
37302
- let right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
37303
- while (right.type !== CellValueType.empty) {
37307
+ let rightPosition = { sheetId, col: col + 1, row };
37308
+ while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
37309
+ this.getters.getCell(rightPosition)?.content) {
37304
37310
  row += 1;
37305
- right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
37311
+ rightPosition = { sheetId, col: col + 1, row };
37306
37312
  }
37307
37313
  }
37308
37314
  }
@@ -40220,6 +40226,9 @@ class SelectionInputsManagerPlugin extends UIPlugin {
40220
40226
  // Other
40221
40227
  // ---------------------------------------------------------------------------
40222
40228
  initInput(id, initialRanges, inputHasSingleRange = false) {
40229
+ if (this.inputs[id]) {
40230
+ this.unfocus();
40231
+ }
40223
40232
  this.inputs[id] = new SelectionInputPlugin(this.config, initialRanges, inputHasSingleRange);
40224
40233
  if (initialRanges.length === 0) {
40225
40234
  const input = this.inputs[id];
@@ -42335,6 +42344,10 @@ function setXcToReferenceType(xc, referenceType) {
42335
42344
  return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
42336
42345
  case "colrow":
42337
42346
  indexOfNumber = xc.search(/[0-9]/);
42347
+ if (indexOfNumber === -1 || indexOfNumber === 0) {
42348
+ // no row number (eg. A) or no column (eg. 1)
42349
+ return "$" + xc;
42350
+ }
42338
42351
  xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
42339
42352
  return "$" + xc;
42340
42353
  case "none":
@@ -43750,7 +43763,7 @@ class GridSelectionPlugin extends UIPlugin {
43750
43763
  sheetId: cmd.sheetId,
43751
43764
  base: cmd.base,
43752
43765
  quantity: thickness,
43753
- position: "before",
43766
+ position: cmd.position,
43754
43767
  });
43755
43768
  const isCol = cmd.dimension === "COL";
43756
43769
  const start = cmd.elements[0];
@@ -43767,12 +43780,13 @@ class GridSelectionPlugin extends UIPlugin {
43767
43780
  },
43768
43781
  ];
43769
43782
  const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
43783
+ const base = isBasedBefore ? cmd.base : cmd.base + 1;
43770
43784
  const pasteTarget = [
43771
43785
  {
43772
- left: isCol ? cmd.base : 0,
43773
- right: isCol ? cmd.base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
43774
- top: !isCol ? cmd.base : 0,
43775
- bottom: !isCol ? cmd.base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
43786
+ left: isCol ? base : 0,
43787
+ right: isCol ? base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
43788
+ top: !isCol ? base : 0,
43789
+ bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
43776
43790
  },
43777
43791
  ];
43778
43792
  state.paste(pasteTarget, { selectTarget: true });
@@ -43809,6 +43823,11 @@ class GridSelectionPlugin extends UIPlugin {
43809
43823
  doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) {
43810
43824
  return 2 /* CommandResult.WillRemoveExistingMerge */;
43811
43825
  }
43826
+ const headers = [cmd.base, ...cmd.elements];
43827
+ const maxHeaderValue = isCol ? this.getters.getNumberCols(id) : this.getters.getNumberRows(id);
43828
+ if (headers.some((h) => h < 0 || h >= maxHeaderValue)) {
43829
+ return 88 /* CommandResult.InvalidHeaderIndex */;
43830
+ }
43812
43831
  return 0 /* CommandResult.Success */;
43813
43832
  }
43814
43833
  //-------------------------------------------
@@ -50793,6 +50812,6 @@ exports.setTranslationMethod = setTranslationMethod;
50793
50812
  exports.tokenize = tokenize;
50794
50813
 
50795
50814
 
50796
- __info__.version = '16.4.15';
50797
- __info__.date = '2023-11-24T13:12:26.608Z';
50798
- __info__.hash = '141943d';
50815
+ __info__.version = '16.4.16';
50816
+ __info__.date = '2023-12-05T09:37:14.210Z';
50817
+ __info__.hash = '1abbb92';
@@ -1715,6 +1715,7 @@ interface MoveColumnsRowsCommand extends GridDependentCommand {
1715
1715
  type: "MOVE_COLUMNS_ROWS";
1716
1716
  base: HeaderIndex;
1717
1717
  elements: HeaderIndex[];
1718
+ position: "before" | "after";
1718
1719
  }
1719
1720
  interface ResizeColumnsRowsCommand extends GridDependentCommand {
1720
1721
  type: "RESIZE_COLUMNS_ROWS";
@@ -3810,25 +3811,23 @@ declare class CustomColorsPlugin extends UIPlugin<CustomColorState> {
3810
3811
  private tryToAddColor;
3811
3812
  }
3812
3813
 
3814
+ interface EvaluationChartStyle {
3815
+ background: Color;
3816
+ fontColor: Color;
3817
+ }
3813
3818
  interface EvaluationChartState {
3814
3819
  charts: Record<UID, ChartRuntime | undefined>;
3815
3820
  }
3816
3821
  declare class EvaluationChartPlugin extends UIPlugin<EvaluationChartState> {
3817
- static getters: readonly ["getChartRuntime", "getBackgroundOfSingleCellChart"];
3822
+ static getters: readonly ["getChartRuntime", "getStyleOfSingleCellChart"];
3818
3823
  charts: Record<UID, ChartRuntime | undefined>;
3819
3824
  private createRuntimeChart;
3820
3825
  handle(cmd: CoreViewCommand): void;
3821
3826
  getChartRuntime(figureId: UID): ChartRuntime;
3822
3827
  /**
3823
- * Get the background color of a chart based on the color of the first cell of the main range
3824
- * of the chart. In order of priority, it will return :
3825
- *
3826
- * - the chart background color if one is defined
3827
- * - the fill color of the cell if one is defined
3828
- * - the fill color of the cell from conditional formats if one is defined
3829
- * - the default chart color if no other color is defined
3828
+ * Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
3830
3829
  */
3831
- getBackgroundOfSingleCellChart(chartBackground: Color | undefined, mainRange: Range | undefined): Color;
3830
+ getStyleOfSingleCellChart(chartBackground: Color | undefined, mainRange: Range | undefined): EvaluationChartStyle;
3832
3831
  }
3833
3832
 
3834
3833
  declare class EvaluationConditionalFormatPlugin extends UIPlugin {
@@ -6932,6 +6931,7 @@ interface ResizerState {
6932
6931
  draggerShadowThickness: number;
6933
6932
  delta: number;
6934
6933
  base: number;
6934
+ position: "before" | "after";
6935
6935
  }
6936
6936
  interface ResizerProps {
6937
6937
  onOpenContextMenu: (type: ContextMenuType, x: Pixel, y: Pixel) => void;