@odoo/o-spreadsheet 18.5.0-alpha.11 → 18.5.0-alpha.12

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.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.5.0-alpha.11
6
- * @date 2025-08-26T10:14:05.357Z
7
- * @hash b913e49
5
+ * @version 18.5.0-alpha.12
6
+ * @date 2025-08-29T08:05:09.767Z
7
+ * @hash 8ee7677
8
8
  */
9
9
 
10
10
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, App, blockDom, useState, onPatched, useExternalListener, onWillUpdateProps, onWillStart, onWillPatch, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -1345,6 +1345,18 @@ function hslaToHex(hsla) {
1345
1345
  function hexToHSLA(hex) {
1346
1346
  return rgbaToHSLA(colorToRGBA(hex));
1347
1347
  }
1348
+ /**
1349
+ * Blend color2 on top of color1, with alpha blending.
1350
+ */
1351
+ function blendColors(color1, color2) {
1352
+ const rgba2 = colorToRGBA(color2);
1353
+ const rgba1 = colorToRGBA(color1);
1354
+ const a = rgba2.a + rgba1.a * (1 - rgba2.a);
1355
+ const r = Math.round((rgba2.r * rgba2.a + rgba1.r * rgba1.a * (1 - rgba2.a)) / a);
1356
+ const g = Math.round((rgba2.g * rgba2.a + rgba1.g * rgba1.a * (1 - rgba2.a)) / a);
1357
+ const b = Math.round((rgba2.b * rgba2.a + rgba1.b * rgba1.a * (1 - rgba2.a)) / a);
1358
+ return rgbaToHex({ r, g, b, a });
1359
+ }
1348
1360
  function colorOrNumberToRGBA(color) {
1349
1361
  if (typeof color === "number") {
1350
1362
  return colorToRGBA(colorNumberToHex(color));
@@ -2659,6 +2671,7 @@ const invalidateBordersCommands = new Set([
2659
2671
  "AUTOFILL_CELL",
2660
2672
  "SET_BORDER",
2661
2673
  "SET_ZONE_BORDERS",
2674
+ "SET_BORDERS_ON_TARGET",
2662
2675
  ]);
2663
2676
  const invalidSubtotalFormulasCommands = new Set([
2664
2677
  "UNHIDE_COLUMNS_ROWS",
@@ -2686,6 +2699,7 @@ const readonlyAllowedCommands = new Set([
2686
2699
  "UPDATE_FILTER",
2687
2700
  "UPDATE_CHART",
2688
2701
  "UPDATE_CAROUSEL_ACTIVE_ITEM",
2702
+ "UPDATE_PIVOT",
2689
2703
  ]);
2690
2704
  const coreTypes = new Set([
2691
2705
  /** CELLS */
@@ -46924,18 +46938,18 @@ const pivotProperties = {
46924
46938
  };
46925
46939
  const pivotSortingAsc = {
46926
46940
  name: _t("Ascending"),
46927
- execute: (env) => sortPivot(env, "asc"),
46928
- isActive: (env) => isPivotSortMenuItemActive(env, "asc"),
46941
+ execute: (env) => sortPivot(env, env.model.getters.getActivePosition(), "asc"),
46942
+ isActive: (env) => env.model.getters.getPivotCellSortDirection(env.model.getters.getActivePosition()) === "asc",
46929
46943
  };
46930
46944
  const pivotSortingDesc = {
46931
46945
  name: _t("Descending"),
46932
- execute: (env) => sortPivot(env, "desc"),
46933
- isActive: (env) => isPivotSortMenuItemActive(env, "desc"),
46946
+ execute: (env) => sortPivot(env, env.model.getters.getActivePosition(), "desc"),
46947
+ isActive: (env) => env.model.getters.getPivotCellSortDirection(env.model.getters.getActivePosition()) === "desc",
46934
46948
  };
46935
46949
  const noPivotSorting = {
46936
46950
  name: _t("No sorting"),
46937
- execute: (env) => sortPivot(env, "none"),
46938
- isActive: (env) => isPivotSortMenuItemActive(env, "none"),
46951
+ execute: (env) => sortPivot(env, env.model.getters.getActivePosition(), "none"),
46952
+ isActive: (env) => env.model.getters.getPivotCellSortDirection(env.model.getters.getActivePosition()) === "none",
46939
46953
  };
46940
46954
  const FIX_FORMULAS = {
46941
46955
  name: _t("Convert to individual formulas"),
@@ -47071,23 +47085,19 @@ const ungroupPivotHeadersAction = {
47071
47085
  return areFieldValuesInGroups(definition, values, field, pivot.getFields());
47072
47086
  },
47073
47087
  };
47074
- function canSortPivot(env) {
47075
- const position = env.model.getters.getActivePosition();
47076
- const pivotId = env.model.getters.getPivotIdFromPosition(position);
47077
- if (!pivotId ||
47078
- !env.model.getters.isExistingPivot(pivotId) ||
47079
- !env.model.getters.isSpillPivotFormula(position)) {
47088
+ function canSortPivot(getters, position) {
47089
+ const pivotId = getters.getPivotIdFromPosition(position);
47090
+ if (!pivotId || !getters.isExistingPivot(pivotId) || !getters.isSpillPivotFormula(position)) {
47080
47091
  return false;
47081
47092
  }
47082
- const pivot = env.model.getters.getPivot(pivotId);
47093
+ const pivot = getters.getPivot(pivotId);
47083
47094
  if (!pivot.isValid()) {
47084
47095
  return false;
47085
47096
  }
47086
- const pivotCell = env.model.getters.getPivotCellFromPosition(position);
47097
+ const pivotCell = getters.getPivotCellFromPosition(position);
47087
47098
  return pivotCell.type === "VALUE" || pivotCell.type === "MEASURE_HEADER";
47088
47099
  }
47089
- function sortPivot(env, order) {
47090
- const position = env.model.getters.getActivePosition();
47100
+ function sortPivot(env, position, order) {
47091
47101
  const pivotId = env.model.getters.getPivotIdFromPosition(position);
47092
47102
  const pivotCell = env.model.getters.getPivotCellFromPosition(position);
47093
47103
  if (pivotCell.type === "EMPTY" || pivotCell.type === "HEADER" || !pivotId) {
@@ -47113,24 +47123,6 @@ function sortPivot(env, order) {
47113
47123
  },
47114
47124
  });
47115
47125
  }
47116
- function isPivotSortMenuItemActive(env, order) {
47117
- const position = env.model.getters.getActivePosition();
47118
- const pivotId = env.model.getters.getPivotIdFromPosition(position);
47119
- const pivotCell = env.model.getters.getPivotCellFromPosition(position);
47120
- if (pivotCell.type === "EMPTY" || pivotCell.type === "HEADER" || !pivotId) {
47121
- return false;
47122
- }
47123
- const pivot = env.model.getters.getPivot(pivotId);
47124
- const colDomain = domainToColRowDomain(pivot, pivotCell.domain).colDomain;
47125
- const sortedColumn = pivot.definition.sortedColumn;
47126
- if (order === "none") {
47127
- return !sortedColumn;
47128
- }
47129
- if (!sortedColumn || sortedColumn.order !== order) {
47130
- return false;
47131
- }
47132
- return sortedColumn.measure === pivotCell.measure && deepEquals(sortedColumn.domain, colDomain);
47133
- }
47134
47126
  /*
47135
47127
  * Get the values of the pivot headers in the current selection, if all the pivot headers on the selection belong
47136
47128
  * to the same pivot, the same field and that the pivot formula is a dynamic pivot. Otherwise return undefined.
@@ -47433,7 +47425,10 @@ cellMenuRegistry
47433
47425
  name: _t("Sort pivot"),
47434
47426
  sequence: 155,
47435
47427
  icon: "o-spreadsheet-Icon.SORT_RANGE",
47436
- isVisible: canSortPivot,
47428
+ isVisible: (env) => {
47429
+ const position = env.model.getters.getActivePosition();
47430
+ return canSortPivot(env.model.getters, position);
47431
+ },
47437
47432
  })
47438
47433
  .add("pivot_fix_formulas", {
47439
47434
  ...FIX_FORMULAS,
@@ -60699,6 +60694,42 @@ class Grid extends Component {
60699
60694
  const supportedPivotPositionalFormulaRegistry = new Registry();
60700
60695
  supportedPivotPositionalFormulaRegistry.add("SPREADSHEET", false);
60701
60696
 
60697
+ class ClickableCellSortIcon extends Component {
60698
+ static template = "o-spreadsheet-ClickableCellSortIcon";
60699
+ static props = {
60700
+ position: Object,
60701
+ sortDirection: String,
60702
+ };
60703
+ hoveredTableStore;
60704
+ setup() {
60705
+ this.hoveredTableStore = useStore(HoveredTableStore);
60706
+ }
60707
+ get style() {
60708
+ const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.position);
60709
+ return cssPropertiesToCss({
60710
+ color: cellStyle.textColor || TEXT_BODY_MUTED,
60711
+ "background-color": this.getBackgroundColor(cellStyle),
60712
+ });
60713
+ }
60714
+ get icon() {
60715
+ switch (this.props.sortDirection) {
60716
+ case "asc":
60717
+ return "fa-sort-asc";
60718
+ case "desc":
60719
+ return "fa-sort-desc";
60720
+ default:
60721
+ return "fa-sort";
60722
+ }
60723
+ }
60724
+ getBackgroundColor(cellStyle) {
60725
+ const overlayColor = this.hoveredTableStore.overlayColors.get(this.props.position);
60726
+ if (overlayColor) {
60727
+ return blendColors(cellStyle.fillColor || "#FFFFFF", overlayColor);
60728
+ }
60729
+ return cellStyle.fillColor || "#FFFFFF";
60730
+ }
60731
+ }
60732
+
60702
60733
  class FullScreenChart extends Component {
60703
60734
  static template = "o-spreadsheet-FullScreenChart";
60704
60735
  static props = {};
@@ -71055,6 +71086,7 @@ class PivotUIPlugin extends CoreViewPlugin {
71055
71086
  static getters = [
71056
71087
  "getPivot",
71057
71088
  "getFirstPivotFunction",
71089
+ "getPivotCellSortDirection",
71058
71090
  "getPivotIdFromPosition",
71059
71091
  "getPivotCellFromPosition",
71060
71092
  "generateNewCalculatedMeasureName",
@@ -71275,6 +71307,20 @@ class PivotUIPlugin extends CoreViewPlugin {
71275
71307
  isPivotUnused(pivotId) {
71276
71308
  return this._getUnusedPivots().includes(pivotId);
71277
71309
  }
71310
+ getPivotCellSortDirection(position) {
71311
+ const pivotId = this.getters.getPivotIdFromPosition(position);
71312
+ const pivotCell = this.getters.getPivotCellFromPosition(position);
71313
+ if (pivotCell.type === "EMPTY" || pivotCell.type === "HEADER" || !pivotId) {
71314
+ return undefined;
71315
+ }
71316
+ const pivot = this.getters.getPivot(pivotId);
71317
+ const colDomain = domainToColRowDomain(pivot, pivotCell.domain).colDomain;
71318
+ const sortedColumn = pivot.definition.sortedColumn;
71319
+ if (sortedColumn?.measure === pivotCell.measure && deepEquals(sortedColumn.domain, colDomain)) {
71320
+ return sortedColumn.order;
71321
+ }
71322
+ return "none";
71323
+ }
71278
71324
  // ---------------------------------------------------------------------
71279
71325
  // Private
71280
71326
  // ---------------------------------------------------------------------
@@ -78967,6 +79013,34 @@ clickableCellRegistry.add("link", {
78967
79013
  },
78968
79014
  sequence: 5,
78969
79015
  });
79016
+ clickableCellRegistry.add("dashboard_pivot_sorting", {
79017
+ condition: (position, getters) => {
79018
+ if (!getters.isDashboard()) {
79019
+ return false;
79020
+ }
79021
+ const pivotCell = getters.getPivotCellFromPosition(position);
79022
+ return canSortPivot(getters, position) && pivotCell.type === "MEASURE_HEADER";
79023
+ },
79024
+ execute: (position, env) => {
79025
+ sortPivot(env, position, getNextSortDirection(env.model.getters, position));
79026
+ },
79027
+ component: ClickableCellSortIcon,
79028
+ componentProps: (position, getters) => {
79029
+ return {
79030
+ position,
79031
+ sortDirection: getters.getPivotCellSortDirection(position),
79032
+ };
79033
+ },
79034
+ sequence: 2,
79035
+ });
79036
+ const NEXT_SORT_DIRECTION = {
79037
+ none: "asc",
79038
+ asc: "desc",
79039
+ desc: "none",
79040
+ };
79041
+ function getNextSortDirection(getters, position) {
79042
+ return NEXT_SORT_DIRECTION[getters.getPivotCellSortDirection(position) ?? "none"];
79043
+ }
78970
79044
 
78971
79045
  const inverseCommandRegistry = new Registry()
78972
79046
  .add("ADD_COLUMNS_ROWS", inverseAddColumnsRows)
@@ -80683,6 +80757,8 @@ class ClickableCellsStore extends SpreadsheetStore {
80683
80757
  position,
80684
80758
  action: item.execute,
80685
80759
  title: title || "",
80760
+ component: item.component,
80761
+ componentProps: item.componentProps?.(position, getters) ?? {},
80686
80762
  });
80687
80763
  }
80688
80764
  return cells;
@@ -83029,6 +83105,27 @@ class Spreadsheet extends Component {
83029
83105
  }
83030
83106
  }
83031
83107
 
83108
+ class ReadonlyTransportFilter {
83109
+ transportService;
83110
+ constructor(transportService) {
83111
+ this.transportService = transportService;
83112
+ }
83113
+ async sendMessage(message) {
83114
+ if (message.type === "CLIENT_JOINED" ||
83115
+ message.type === "CLIENT_LEFT" ||
83116
+ message.type === "CLIENT_MOVED") {
83117
+ this.transportService.sendMessage(message);
83118
+ }
83119
+ // ignore all other messages
83120
+ }
83121
+ onNewMessage(id, callback) {
83122
+ this.transportService.onNewMessage(id, callback);
83123
+ }
83124
+ leave(id) {
83125
+ this.transportService.leave(id);
83126
+ }
83127
+ }
83128
+
83032
83129
  function inverseCommand(cmd) {
83033
83130
  return inverseCommandRegistry.get(cmd.type)(cmd);
83034
83131
  }
@@ -87121,12 +87218,15 @@ class Model extends EventBus {
87121
87218
  name: _t("Anonymous").toString(),
87122
87219
  };
87123
87220
  const transportService = config.transportService || new LocalTransportService();
87221
+ const isReadonly = config.mode === "readonly" || config.mode === "dashboard";
87124
87222
  return {
87125
87223
  ...config,
87126
87224
  mode: config.mode || "normal",
87127
87225
  custom: config.custom || {},
87128
87226
  external: this.setupExternalConfig(config.external || {}),
87129
- transportService,
87227
+ transportService: isReadonly
87228
+ ? new ReadonlyTransportFilter(transportService)
87229
+ : transportService,
87130
87230
  client,
87131
87231
  moveClient: () => { },
87132
87232
  snapshotRequested: false,
@@ -87539,6 +87639,7 @@ const components = {
87539
87639
  ChartPanel,
87540
87640
  ChartFigure,
87541
87641
  ChartJsComponent,
87642
+ ClickableCellSortIcon,
87542
87643
  ZoomableChartJsComponent,
87543
87644
  Grid,
87544
87645
  GridOverlay,
@@ -87624,12 +87725,14 @@ const constants = {
87624
87725
  PIVOT_TABLE_CONFIG,
87625
87726
  ChartTerms,
87626
87727
  FIGURE_ID_SPLITTER,
87728
+ GRID_ICON_EDGE_LENGTH,
87729
+ GRID_ICON_MARGIN,
87627
87730
  };
87628
87731
  const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
87629
87732
 
87630
87733
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, ClientDisconnectedError, CommandResult, CorePlugin, CoreViewPlugin, DispatchResult, EvaluationError, LocalTransportService, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
87631
87734
 
87632
87735
 
87633
- __info__.version = "18.5.0-alpha.11";
87634
- __info__.date = "2025-08-26T10:14:05.357Z";
87635
- __info__.hash = "b913e49";
87736
+ __info__.version = "18.5.0-alpha.12";
87737
+ __info__.date = "2025-08-29T08:05:09.767Z";
87738
+ __info__.hash = "8ee7677";
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.5.0-alpha.11
6
- * @date 2025-08-26T10:14:05.357Z
7
- * @hash b913e49
5
+ * @version 18.5.0-alpha.12
6
+ * @date 2025-08-29T08:05:09.767Z
7
+ * @hash 8ee7677
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -1346,6 +1346,18 @@
1346
1346
  function hexToHSLA(hex) {
1347
1347
  return rgbaToHSLA(colorToRGBA(hex));
1348
1348
  }
1349
+ /**
1350
+ * Blend color2 on top of color1, with alpha blending.
1351
+ */
1352
+ function blendColors(color1, color2) {
1353
+ const rgba2 = colorToRGBA(color2);
1354
+ const rgba1 = colorToRGBA(color1);
1355
+ const a = rgba2.a + rgba1.a * (1 - rgba2.a);
1356
+ const r = Math.round((rgba2.r * rgba2.a + rgba1.r * rgba1.a * (1 - rgba2.a)) / a);
1357
+ const g = Math.round((rgba2.g * rgba2.a + rgba1.g * rgba1.a * (1 - rgba2.a)) / a);
1358
+ const b = Math.round((rgba2.b * rgba2.a + rgba1.b * rgba1.a * (1 - rgba2.a)) / a);
1359
+ return rgbaToHex({ r, g, b, a });
1360
+ }
1349
1361
  function colorOrNumberToRGBA(color) {
1350
1362
  if (typeof color === "number") {
1351
1363
  return colorToRGBA(colorNumberToHex(color));
@@ -2660,6 +2672,7 @@
2660
2672
  "AUTOFILL_CELL",
2661
2673
  "SET_BORDER",
2662
2674
  "SET_ZONE_BORDERS",
2675
+ "SET_BORDERS_ON_TARGET",
2663
2676
  ]);
2664
2677
  const invalidSubtotalFormulasCommands = new Set([
2665
2678
  "UNHIDE_COLUMNS_ROWS",
@@ -2687,6 +2700,7 @@
2687
2700
  "UPDATE_FILTER",
2688
2701
  "UPDATE_CHART",
2689
2702
  "UPDATE_CAROUSEL_ACTIVE_ITEM",
2703
+ "UPDATE_PIVOT",
2690
2704
  ]);
2691
2705
  const coreTypes = new Set([
2692
2706
  /** CELLS */
@@ -46925,18 +46939,18 @@ stores.inject(MyMetaStore, storeInstance);
46925
46939
  };
46926
46940
  const pivotSortingAsc = {
46927
46941
  name: _t("Ascending"),
46928
- execute: (env) => sortPivot(env, "asc"),
46929
- isActive: (env) => isPivotSortMenuItemActive(env, "asc"),
46942
+ execute: (env) => sortPivot(env, env.model.getters.getActivePosition(), "asc"),
46943
+ isActive: (env) => env.model.getters.getPivotCellSortDirection(env.model.getters.getActivePosition()) === "asc",
46930
46944
  };
46931
46945
  const pivotSortingDesc = {
46932
46946
  name: _t("Descending"),
46933
- execute: (env) => sortPivot(env, "desc"),
46934
- isActive: (env) => isPivotSortMenuItemActive(env, "desc"),
46947
+ execute: (env) => sortPivot(env, env.model.getters.getActivePosition(), "desc"),
46948
+ isActive: (env) => env.model.getters.getPivotCellSortDirection(env.model.getters.getActivePosition()) === "desc",
46935
46949
  };
46936
46950
  const noPivotSorting = {
46937
46951
  name: _t("No sorting"),
46938
- execute: (env) => sortPivot(env, "none"),
46939
- isActive: (env) => isPivotSortMenuItemActive(env, "none"),
46952
+ execute: (env) => sortPivot(env, env.model.getters.getActivePosition(), "none"),
46953
+ isActive: (env) => env.model.getters.getPivotCellSortDirection(env.model.getters.getActivePosition()) === "none",
46940
46954
  };
46941
46955
  const FIX_FORMULAS = {
46942
46956
  name: _t("Convert to individual formulas"),
@@ -47072,23 +47086,19 @@ stores.inject(MyMetaStore, storeInstance);
47072
47086
  return areFieldValuesInGroups(definition, values, field, pivot.getFields());
47073
47087
  },
47074
47088
  };
47075
- function canSortPivot(env) {
47076
- const position = env.model.getters.getActivePosition();
47077
- const pivotId = env.model.getters.getPivotIdFromPosition(position);
47078
- if (!pivotId ||
47079
- !env.model.getters.isExistingPivot(pivotId) ||
47080
- !env.model.getters.isSpillPivotFormula(position)) {
47089
+ function canSortPivot(getters, position) {
47090
+ const pivotId = getters.getPivotIdFromPosition(position);
47091
+ if (!pivotId || !getters.isExistingPivot(pivotId) || !getters.isSpillPivotFormula(position)) {
47081
47092
  return false;
47082
47093
  }
47083
- const pivot = env.model.getters.getPivot(pivotId);
47094
+ const pivot = getters.getPivot(pivotId);
47084
47095
  if (!pivot.isValid()) {
47085
47096
  return false;
47086
47097
  }
47087
- const pivotCell = env.model.getters.getPivotCellFromPosition(position);
47098
+ const pivotCell = getters.getPivotCellFromPosition(position);
47088
47099
  return pivotCell.type === "VALUE" || pivotCell.type === "MEASURE_HEADER";
47089
47100
  }
47090
- function sortPivot(env, order) {
47091
- const position = env.model.getters.getActivePosition();
47101
+ function sortPivot(env, position, order) {
47092
47102
  const pivotId = env.model.getters.getPivotIdFromPosition(position);
47093
47103
  const pivotCell = env.model.getters.getPivotCellFromPosition(position);
47094
47104
  if (pivotCell.type === "EMPTY" || pivotCell.type === "HEADER" || !pivotId) {
@@ -47114,24 +47124,6 @@ stores.inject(MyMetaStore, storeInstance);
47114
47124
  },
47115
47125
  });
47116
47126
  }
47117
- function isPivotSortMenuItemActive(env, order) {
47118
- const position = env.model.getters.getActivePosition();
47119
- const pivotId = env.model.getters.getPivotIdFromPosition(position);
47120
- const pivotCell = env.model.getters.getPivotCellFromPosition(position);
47121
- if (pivotCell.type === "EMPTY" || pivotCell.type === "HEADER" || !pivotId) {
47122
- return false;
47123
- }
47124
- const pivot = env.model.getters.getPivot(pivotId);
47125
- const colDomain = domainToColRowDomain(pivot, pivotCell.domain).colDomain;
47126
- const sortedColumn = pivot.definition.sortedColumn;
47127
- if (order === "none") {
47128
- return !sortedColumn;
47129
- }
47130
- if (!sortedColumn || sortedColumn.order !== order) {
47131
- return false;
47132
- }
47133
- return sortedColumn.measure === pivotCell.measure && deepEquals(sortedColumn.domain, colDomain);
47134
- }
47135
47127
  /*
47136
47128
  * Get the values of the pivot headers in the current selection, if all the pivot headers on the selection belong
47137
47129
  * to the same pivot, the same field and that the pivot formula is a dynamic pivot. Otherwise return undefined.
@@ -47434,7 +47426,10 @@ stores.inject(MyMetaStore, storeInstance);
47434
47426
  name: _t("Sort pivot"),
47435
47427
  sequence: 155,
47436
47428
  icon: "o-spreadsheet-Icon.SORT_RANGE",
47437
- isVisible: canSortPivot,
47429
+ isVisible: (env) => {
47430
+ const position = env.model.getters.getActivePosition();
47431
+ return canSortPivot(env.model.getters, position);
47432
+ },
47438
47433
  })
47439
47434
  .add("pivot_fix_formulas", {
47440
47435
  ...FIX_FORMULAS,
@@ -60700,6 +60695,42 @@ stores.inject(MyMetaStore, storeInstance);
60700
60695
  const supportedPivotPositionalFormulaRegistry = new Registry();
60701
60696
  supportedPivotPositionalFormulaRegistry.add("SPREADSHEET", false);
60702
60697
 
60698
+ class ClickableCellSortIcon extends owl.Component {
60699
+ static template = "o-spreadsheet-ClickableCellSortIcon";
60700
+ static props = {
60701
+ position: Object,
60702
+ sortDirection: String,
60703
+ };
60704
+ hoveredTableStore;
60705
+ setup() {
60706
+ this.hoveredTableStore = useStore(HoveredTableStore);
60707
+ }
60708
+ get style() {
60709
+ const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.position);
60710
+ return cssPropertiesToCss({
60711
+ color: cellStyle.textColor || TEXT_BODY_MUTED,
60712
+ "background-color": this.getBackgroundColor(cellStyle),
60713
+ });
60714
+ }
60715
+ get icon() {
60716
+ switch (this.props.sortDirection) {
60717
+ case "asc":
60718
+ return "fa-sort-asc";
60719
+ case "desc":
60720
+ return "fa-sort-desc";
60721
+ default:
60722
+ return "fa-sort";
60723
+ }
60724
+ }
60725
+ getBackgroundColor(cellStyle) {
60726
+ const overlayColor = this.hoveredTableStore.overlayColors.get(this.props.position);
60727
+ if (overlayColor) {
60728
+ return blendColors(cellStyle.fillColor || "#FFFFFF", overlayColor);
60729
+ }
60730
+ return cellStyle.fillColor || "#FFFFFF";
60731
+ }
60732
+ }
60733
+
60703
60734
  class FullScreenChart extends owl.Component {
60704
60735
  static template = "o-spreadsheet-FullScreenChart";
60705
60736
  static props = {};
@@ -71056,6 +71087,7 @@ stores.inject(MyMetaStore, storeInstance);
71056
71087
  static getters = [
71057
71088
  "getPivot",
71058
71089
  "getFirstPivotFunction",
71090
+ "getPivotCellSortDirection",
71059
71091
  "getPivotIdFromPosition",
71060
71092
  "getPivotCellFromPosition",
71061
71093
  "generateNewCalculatedMeasureName",
@@ -71276,6 +71308,20 @@ stores.inject(MyMetaStore, storeInstance);
71276
71308
  isPivotUnused(pivotId) {
71277
71309
  return this._getUnusedPivots().includes(pivotId);
71278
71310
  }
71311
+ getPivotCellSortDirection(position) {
71312
+ const pivotId = this.getters.getPivotIdFromPosition(position);
71313
+ const pivotCell = this.getters.getPivotCellFromPosition(position);
71314
+ if (pivotCell.type === "EMPTY" || pivotCell.type === "HEADER" || !pivotId) {
71315
+ return undefined;
71316
+ }
71317
+ const pivot = this.getters.getPivot(pivotId);
71318
+ const colDomain = domainToColRowDomain(pivot, pivotCell.domain).colDomain;
71319
+ const sortedColumn = pivot.definition.sortedColumn;
71320
+ if (sortedColumn?.measure === pivotCell.measure && deepEquals(sortedColumn.domain, colDomain)) {
71321
+ return sortedColumn.order;
71322
+ }
71323
+ return "none";
71324
+ }
71279
71325
  // ---------------------------------------------------------------------
71280
71326
  // Private
71281
71327
  // ---------------------------------------------------------------------
@@ -78968,6 +79014,34 @@ stores.inject(MyMetaStore, storeInstance);
78968
79014
  },
78969
79015
  sequence: 5,
78970
79016
  });
79017
+ clickableCellRegistry.add("dashboard_pivot_sorting", {
79018
+ condition: (position, getters) => {
79019
+ if (!getters.isDashboard()) {
79020
+ return false;
79021
+ }
79022
+ const pivotCell = getters.getPivotCellFromPosition(position);
79023
+ return canSortPivot(getters, position) && pivotCell.type === "MEASURE_HEADER";
79024
+ },
79025
+ execute: (position, env) => {
79026
+ sortPivot(env, position, getNextSortDirection(env.model.getters, position));
79027
+ },
79028
+ component: ClickableCellSortIcon,
79029
+ componentProps: (position, getters) => {
79030
+ return {
79031
+ position,
79032
+ sortDirection: getters.getPivotCellSortDirection(position),
79033
+ };
79034
+ },
79035
+ sequence: 2,
79036
+ });
79037
+ const NEXT_SORT_DIRECTION = {
79038
+ none: "asc",
79039
+ asc: "desc",
79040
+ desc: "none",
79041
+ };
79042
+ function getNextSortDirection(getters, position) {
79043
+ return NEXT_SORT_DIRECTION[getters.getPivotCellSortDirection(position) ?? "none"];
79044
+ }
78971
79045
 
78972
79046
  const inverseCommandRegistry = new Registry()
78973
79047
  .add("ADD_COLUMNS_ROWS", inverseAddColumnsRows)
@@ -80684,6 +80758,8 @@ stores.inject(MyMetaStore, storeInstance);
80684
80758
  position,
80685
80759
  action: item.execute,
80686
80760
  title: title || "",
80761
+ component: item.component,
80762
+ componentProps: item.componentProps?.(position, getters) ?? {},
80687
80763
  });
80688
80764
  }
80689
80765
  return cells;
@@ -83030,6 +83106,27 @@ stores.inject(MyMetaStore, storeInstance);
83030
83106
  }
83031
83107
  }
83032
83108
 
83109
+ class ReadonlyTransportFilter {
83110
+ transportService;
83111
+ constructor(transportService) {
83112
+ this.transportService = transportService;
83113
+ }
83114
+ async sendMessage(message) {
83115
+ if (message.type === "CLIENT_JOINED" ||
83116
+ message.type === "CLIENT_LEFT" ||
83117
+ message.type === "CLIENT_MOVED") {
83118
+ this.transportService.sendMessage(message);
83119
+ }
83120
+ // ignore all other messages
83121
+ }
83122
+ onNewMessage(id, callback) {
83123
+ this.transportService.onNewMessage(id, callback);
83124
+ }
83125
+ leave(id) {
83126
+ this.transportService.leave(id);
83127
+ }
83128
+ }
83129
+
83033
83130
  function inverseCommand(cmd) {
83034
83131
  return inverseCommandRegistry.get(cmd.type)(cmd);
83035
83132
  }
@@ -87122,12 +87219,15 @@ stores.inject(MyMetaStore, storeInstance);
87122
87219
  name: _t("Anonymous").toString(),
87123
87220
  };
87124
87221
  const transportService = config.transportService || new LocalTransportService();
87222
+ const isReadonly = config.mode === "readonly" || config.mode === "dashboard";
87125
87223
  return {
87126
87224
  ...config,
87127
87225
  mode: config.mode || "normal",
87128
87226
  custom: config.custom || {},
87129
87227
  external: this.setupExternalConfig(config.external || {}),
87130
- transportService,
87228
+ transportService: isReadonly
87229
+ ? new ReadonlyTransportFilter(transportService)
87230
+ : transportService,
87131
87231
  client,
87132
87232
  moveClient: () => { },
87133
87233
  snapshotRequested: false,
@@ -87540,6 +87640,7 @@ stores.inject(MyMetaStore, storeInstance);
87540
87640
  ChartPanel,
87541
87641
  ChartFigure,
87542
87642
  ChartJsComponent,
87643
+ ClickableCellSortIcon,
87543
87644
  ZoomableChartJsComponent,
87544
87645
  Grid,
87545
87646
  GridOverlay,
@@ -87625,6 +87726,8 @@ stores.inject(MyMetaStore, storeInstance);
87625
87726
  PIVOT_TABLE_CONFIG,
87626
87727
  ChartTerms,
87627
87728
  FIGURE_ID_SPLITTER,
87729
+ GRID_ICON_EDGE_LENGTH,
87730
+ GRID_ICON_MARGIN,
87628
87731
  };
87629
87732
  const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
87630
87733
 
@@ -87679,9 +87782,9 @@ stores.inject(MyMetaStore, storeInstance);
87679
87782
  exports.tokenize = tokenize;
87680
87783
 
87681
87784
 
87682
- __info__.version = "18.5.0-alpha.11";
87683
- __info__.date = "2025-08-26T10:14:05.357Z";
87684
- __info__.hash = "b913e49";
87785
+ __info__.version = "18.5.0-alpha.12";
87786
+ __info__.date = "2025-08-29T08:05:09.767Z";
87787
+ __info__.hash = "8ee7677";
87685
87788
 
87686
87789
 
87687
87790
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);