@odoo/o-spreadsheet 1.0.1

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.
Files changed (199) hide show
  1. package/.eslintrc.json +20 -0
  2. package/.husky/post-checkout +9 -0
  3. package/.husky/pre-commit +39 -0
  4. package/.prettierignore +1 -0
  5. package/astToFormula.patch +0 -0
  6. package/demo/data.js +255 -0
  7. package/demo/index.html +9 -0
  8. package/demo/libs/Chart.bundle.js +20755 -0
  9. package/demo/main.js +101 -0
  10. package/doc/readme.md +46 -0
  11. package/package.json +78 -0
  12. package/readme.md +39 -0
  13. package/rollup.config.js +23 -0
  14. package/src/base_plugin.ts +102 -0
  15. package/src/components/autofill.ts +146 -0
  16. package/src/components/bottom_bar.ts +199 -0
  17. package/src/components/color_picker.ts +150 -0
  18. package/src/components/composer/autocomplete_dropdown.ts +135 -0
  19. package/src/components/composer/composer.ts +487 -0
  20. package/src/components/composer/content_editable_helper.ts +146 -0
  21. package/src/components/figures/chart.ts +70 -0
  22. package/src/components/figures/container.ts +268 -0
  23. package/src/components/figures/text_figure.ts +36 -0
  24. package/src/components/grid.ts +635 -0
  25. package/src/components/helpers/dom_helpers.ts +7 -0
  26. package/src/components/helpers/drag_and_drop.ts +17 -0
  27. package/src/components/icons.ts +36 -0
  28. package/src/components/index.ts +1 -0
  29. package/src/components/menu.ts +288 -0
  30. package/src/components/overlay.ts +457 -0
  31. package/src/components/scrollbar.ts +22 -0
  32. package/src/components/selection_input.ts +168 -0
  33. package/src/components/side_panel/cell_is_rule_editor.ts +169 -0
  34. package/src/components/side_panel/chart_panel.ts +104 -0
  35. package/src/components/side_panel/color_scale_rule_editor.ts +145 -0
  36. package/src/components/side_panel/conditional_formatting.ts +396 -0
  37. package/src/components/side_panel/side_panel.ts +127 -0
  38. package/src/components/side_panel/translations_terms.ts +45 -0
  39. package/src/components/spreadsheet.ts +198 -0
  40. package/src/components/top_bar.ts +535 -0
  41. package/src/constants.ts +26 -0
  42. package/src/data.ts +149 -0
  43. package/src/fonts.ts +41 -0
  44. package/src/formulas/compiler.ts +258 -0
  45. package/src/formulas/composer_tokenizer.ts +70 -0
  46. package/src/formulas/index.ts +15 -0
  47. package/src/formulas/normalize.ts +52 -0
  48. package/src/formulas/parser.ts +280 -0
  49. package/src/formulas/range_tokenizer.ts +97 -0
  50. package/src/formulas/tokenizer.ts +242 -0
  51. package/src/functions/arguments.ts +114 -0
  52. package/src/functions/dates.ts +302 -0
  53. package/src/functions/helpers.ts +592 -0
  54. package/src/functions/index.ts +52 -0
  55. package/src/functions/module_database.ts +282 -0
  56. package/src/functions/module_date.ts +743 -0
  57. package/src/functions/module_info.ts +69 -0
  58. package/src/functions/module_logical.ts +208 -0
  59. package/src/functions/module_lookup.ts +185 -0
  60. package/src/functions/module_math.ts +1252 -0
  61. package/src/functions/module_operators.ts +291 -0
  62. package/src/functions/module_statistical.ts +882 -0
  63. package/src/functions/module_text.ts +370 -0
  64. package/src/helpers/color.ts +30 -0
  65. package/src/helpers/coordinates.ts +71 -0
  66. package/src/helpers/grid_manipulation.ts +68 -0
  67. package/src/helpers/index.ts +7 -0
  68. package/src/helpers/misc.ts +76 -0
  69. package/src/helpers/numbers.ts +117 -0
  70. package/src/helpers/uuid.ts +19 -0
  71. package/src/helpers/zones.ts +263 -0
  72. package/src/history.ts +186 -0
  73. package/src/index.ts +84 -0
  74. package/src/model.ts +261 -0
  75. package/src/plugins/autofill.ts +412 -0
  76. package/src/plugins/chart.ts +404 -0
  77. package/src/plugins/clipboard.ts +461 -0
  78. package/src/plugins/conditional_format.ts +445 -0
  79. package/src/plugins/core.ts +1502 -0
  80. package/src/plugins/edition.ts +157 -0
  81. package/src/plugins/evaluation.ts +428 -0
  82. package/src/plugins/figures.ts +123 -0
  83. package/src/plugins/formatting.ts +630 -0
  84. package/src/plugins/highlight.ts +187 -0
  85. package/src/plugins/index.ts +32 -0
  86. package/src/plugins/merge.ts +524 -0
  87. package/src/plugins/renderer.ts +571 -0
  88. package/src/plugins/selection.ts +562 -0
  89. package/src/plugins/selection_inputs.ts +326 -0
  90. package/src/registries/autofill_modifiers.ts +76 -0
  91. package/src/registries/autofill_rules.ts +98 -0
  92. package/src/registries/figure_registry.ts +30 -0
  93. package/src/registries/index.ts +7 -0
  94. package/src/registries/menu_items_registry.ts +110 -0
  95. package/src/registries/menus/cell_menu_registry.ts +79 -0
  96. package/src/registries/menus/col_menu_registry.ts +63 -0
  97. package/src/registries/menus/index.ts +6 -0
  98. package/src/registries/menus/menu_items_actions.ts +425 -0
  99. package/src/registries/menus/row_menu_registry.ts +63 -0
  100. package/src/registries/menus/sheet_menu_registry.ts +75 -0
  101. package/src/registries/menus/topbar_menu_registry.ts +204 -0
  102. package/src/registries/side_panel_registry.ts +26 -0
  103. package/src/registries/topbar_component_registry.ts +12 -0
  104. package/src/registry.ts +51 -0
  105. package/src/translation.ts +44 -0
  106. package/src/types/autofill.ts +48 -0
  107. package/src/types/chart.ts +26 -0
  108. package/src/types/commands.ts +716 -0
  109. package/src/types/conditional_formatting.ts +124 -0
  110. package/src/types/env.ts +13 -0
  111. package/src/types/functions.ts +35 -0
  112. package/src/types/getters.ts +103 -0
  113. package/src/types/index.ts +20 -0
  114. package/src/types/misc.ts +143 -0
  115. package/src/types/rendering.ts +41 -0
  116. package/src/types/workbook_data.ts +62 -0
  117. package/tests/__mocks__/uuid.ts +7 -0
  118. package/tests/canvas.mock.ts +34 -0
  119. package/tests/components/__mocks__/content_editable_helper.ts +79 -0
  120. package/tests/components/__mocks__/scrollbar.ts +18 -0
  121. package/tests/components/__snapshots__/autocomplete_dropdown_test.ts.snap +29 -0
  122. package/tests/components/__snapshots__/bottom_bar_test.ts.snap +75 -0
  123. package/tests/components/__snapshots__/context_menu_test.ts.snap +88 -0
  124. package/tests/components/__snapshots__/grid_test.ts.snap +60 -0
  125. package/tests/components/__snapshots__/spreadsheet_test.ts.snap +455 -0
  126. package/tests/components/__snapshots__/top_bar_test.ts.snap +705 -0
  127. package/tests/components/autocomplete_dropdown_test.ts +315 -0
  128. package/tests/components/autofill_test.ts +167 -0
  129. package/tests/components/bottom_bar_test.ts +262 -0
  130. package/tests/components/chart_side_panel_test.ts +123 -0
  131. package/tests/components/composer_test.ts +400 -0
  132. package/tests/components/context_menu_test.ts +825 -0
  133. package/tests/components/grid_manipulation_test.ts +163 -0
  134. package/tests/components/grid_test.ts +581 -0
  135. package/tests/components/overlay_test.ts +541 -0
  136. package/tests/components/selection_input_test.ts +259 -0
  137. package/tests/components/side_panel_test.ts +148 -0
  138. package/tests/components/spreadsheet_test.ts +194 -0
  139. package/tests/components/top_bar_test.ts +427 -0
  140. package/tests/data_test.ts +175 -0
  141. package/tests/dom_helper.ts +42 -0
  142. package/tests/formulas/__snapshots__/compiler_test.ts.snap +302 -0
  143. package/tests/formulas/compiler_test.ts +266 -0
  144. package/tests/formulas/composer_tokenizer_test.ts +137 -0
  145. package/tests/formulas/expressions_test.ts +57 -0
  146. package/tests/formulas/formulas_test.ts +80 -0
  147. package/tests/formulas/normalize_test.ts +82 -0
  148. package/tests/formulas/parser_test.ts +224 -0
  149. package/tests/formulas/range_tokenizer_test.ts +187 -0
  150. package/tests/formulas/tokenizer_test.ts +164 -0
  151. package/tests/functions/arguments_test.ts +141 -0
  152. package/tests/functions/dates_test.ts +1081 -0
  153. package/tests/functions/functions_test.ts +53 -0
  154. package/tests/functions/helper_test.ts +98 -0
  155. package/tests/functions/module_database_test.ts +311 -0
  156. package/tests/functions/module_date_test.ts +941 -0
  157. package/tests/functions/module_info_test.ts +139 -0
  158. package/tests/functions/module_logical_test.ts +369 -0
  159. package/tests/functions/module_lookup_test.ts +541 -0
  160. package/tests/functions/module_math_test.ts +2482 -0
  161. package/tests/functions/module_operators_test.ts +955 -0
  162. package/tests/functions/module_statistical_test.ts +2014 -0
  163. package/tests/functions/module_text_test.ts +667 -0
  164. package/tests/helpers/coordinates_test.ts +25 -0
  165. package/tests/helpers/misc_test.ts +95 -0
  166. package/tests/helpers/numbers_test.ts +196 -0
  167. package/tests/helpers/zones_test.ts +97 -0
  168. package/tests/helpers.ts +384 -0
  169. package/tests/menu_items_registry_test.ts +540 -0
  170. package/tests/model/plugins/__snapshots__/chart_test.ts.snap +90 -0
  171. package/tests/model/plugins/chart_test.ts +562 -0
  172. package/tests/model_test.ts +82 -0
  173. package/tests/plugins/__snapshots__/conditional_formatting_test.ts.snap +131 -0
  174. package/tests/plugins/__snapshots__/renderer_test.ts.snap +371 -0
  175. package/tests/plugins/autofill_test.ts +457 -0
  176. package/tests/plugins/borders_test.ts +295 -0
  177. package/tests/plugins/clipboard_test.ts +1180 -0
  178. package/tests/plugins/conditional_formatting_test.ts +1253 -0
  179. package/tests/plugins/core_test.ts +614 -0
  180. package/tests/plugins/edition_test.ts +100 -0
  181. package/tests/plugins/evaluation_async_test.ts +332 -0
  182. package/tests/plugins/evaluation_dates_test.ts +79 -0
  183. package/tests/plugins/evaluation_test.ts +901 -0
  184. package/tests/plugins/figures_test.ts +327 -0
  185. package/tests/plugins/formatting_test.ts +391 -0
  186. package/tests/plugins/grid_manipulation_test.ts +1366 -0
  187. package/tests/plugins/highlight_test.ts +413 -0
  188. package/tests/plugins/history_test.ts +142 -0
  189. package/tests/plugins/import_export_test.ts +276 -0
  190. package/tests/plugins/merges_test.ts +490 -0
  191. package/tests/plugins/navigation_test.ts +181 -0
  192. package/tests/plugins/plugins/evaluation_test.ts +111 -0
  193. package/tests/plugins/renderer_test.ts +598 -0
  194. package/tests/plugins/resizing_test.ts +152 -0
  195. package/tests/plugins/selection_input_test.ts +409 -0
  196. package/tests/plugins/selection_test.ts +442 -0
  197. package/tests/plugins/sheets_test.ts +643 -0
  198. package/tests/plugins/style_test.ts +90 -0
  199. package/tsconfig.json +22 -0
@@ -0,0 +1,445 @@
1
+ import { BasePlugin } from "../base_plugin";
2
+ import {
3
+ colorNumberString,
4
+ isInside,
5
+ recomputeZones,
6
+ toXC,
7
+ toZone,
8
+ updateAddColumns,
9
+ updateAddRows,
10
+ updateRemoveColumns,
11
+ updateRemoveRows,
12
+ } from "../helpers/index";
13
+ import { _lt } from "../translation";
14
+ import {
15
+ Cell,
16
+ CellIsRule,
17
+ ColorScaleRule,
18
+ Command,
19
+ ConditionalFormat,
20
+ Style,
21
+ WorkbookData,
22
+ Zone,
23
+ } from "../types/index";
24
+
25
+ // -----------------------------------------------------------------------------
26
+ // Constants
27
+ // -----------------------------------------------------------------------------
28
+
29
+ export class ConditionalFormatPlugin extends BasePlugin {
30
+ static getters = ["getConditionalFormats", "getConditionalStyle", "getRulesSelection"];
31
+
32
+ private isStale: boolean = true;
33
+ private cfRules: { [sheet: string]: ConditionalFormat[] } = {};
34
+
35
+ // stores the computed styles in the format of computedStyles.sheetName.cellXC = Style
36
+ private computedStyles: { [sheet: string]: { [cellXc: string]: Style } } = {};
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Command Handling
40
+ // ---------------------------------------------------------------------------
41
+
42
+ handle(cmd: Command) {
43
+ switch (cmd.type) {
44
+ case "ACTIVATE_SHEET":
45
+ const activeSheet = cmd.to;
46
+ this.computedStyles[activeSheet] = this.computedStyles[activeSheet] || {};
47
+ this.isStale = true;
48
+ break;
49
+ case "CREATE_SHEET":
50
+ this.cfRules[cmd.id] = [];
51
+ this.isStale = true;
52
+ break;
53
+ case "DUPLICATE_SHEET":
54
+ this.history.updateLocalState(["cfRules", cmd.id], this.cfRules[cmd.sheet].slice());
55
+ this.isStale = true;
56
+ break;
57
+ case "DELETE_SHEET":
58
+ const cfRules = Object.assign({}, this.cfRules);
59
+ delete cfRules[cmd.sheet];
60
+ this.history.updateLocalState(["cfRules"], cfRules);
61
+ this.isStale = true;
62
+ break;
63
+ case "ADD_CONDITIONAL_FORMAT":
64
+ this.addConditionalFormatting(cmd.cf, cmd.sheet);
65
+ this.isStale = true;
66
+ break;
67
+ case "REMOVE_CONDITIONAL_FORMAT":
68
+ this.removeConditionalFormatting(cmd.id, cmd.sheet);
69
+ this.isStale = true;
70
+ break;
71
+ case "REMOVE_COLUMNS":
72
+ this.adaptcfRules(cmd.sheet, (range: string) => updateRemoveColumns(range, cmd.columns));
73
+ this.isStale = true;
74
+ break;
75
+ case "REMOVE_ROWS":
76
+ this.adaptcfRules(cmd.sheet, (range: string) => updateRemoveRows(range, cmd.rows));
77
+ this.isStale = true;
78
+ break;
79
+ case "ADD_COLUMNS":
80
+ const column = cmd.position === "before" ? cmd.column : cmd.column + 1;
81
+ this.adaptcfRules(cmd.sheet, (range: string) =>
82
+ updateAddColumns(range, column, cmd.quantity)
83
+ );
84
+ this.isStale = true;
85
+ break;
86
+ case "AUTOFILL_CELL":
87
+ const sheet = this.getters.getActiveSheet();
88
+ const cfOrigin = this.getRulesByCell(toXC(cmd.originCol, cmd.originRow));
89
+ for (const cf of cfOrigin) {
90
+ this.adaptRules(sheet, cf, [toXC(cmd.col, cmd.row)], []);
91
+ }
92
+ break;
93
+ case "ADD_ROWS":
94
+ const row = cmd.position === "before" ? cmd.row : cmd.row + 1;
95
+ this.adaptcfRules(cmd.sheet, (range: string) => updateAddRows(range, row, cmd.quantity));
96
+ this.isStale = true;
97
+ break;
98
+ case "PASTE_CELL":
99
+ if (!cmd.onlyValue) {
100
+ this.pasteCf(cmd.originCol, cmd.originRow, cmd.col, cmd.row, cmd.sheet, cmd.cut);
101
+ }
102
+ break;
103
+ case "EVALUATE_CELLS":
104
+ case "UPDATE_CELL":
105
+ case "UNDO":
106
+ case "REDO":
107
+ this.isStale = true;
108
+ break;
109
+ }
110
+ }
111
+
112
+ finalize() {
113
+ if (this.isStale && this.currentMode !== "headless") {
114
+ this.computeStyles();
115
+ this.isStale = false;
116
+ }
117
+ }
118
+
119
+ import(data: WorkbookData) {
120
+ for (let sheet of data.sheets) {
121
+ this.cfRules[sheet.id] = sheet.conditionalFormats;
122
+ }
123
+ }
124
+
125
+ export(data: Partial<WorkbookData>) {
126
+ if (data.sheets) {
127
+ for (let sheet of data.sheets) {
128
+ if (this.cfRules[sheet.id]) {
129
+ sheet.conditionalFormats = this.cfRules[sheet.id];
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Getters
137
+ // ---------------------------------------------------------------------------
138
+
139
+ /**
140
+ * Returns all the conditional format rules defined for the current sheet
141
+ */
142
+ getConditionalFormats(): ConditionalFormat[] {
143
+ return this.cfRules[this.workbook.activeSheet.id];
144
+ }
145
+
146
+ /**
147
+ * Returns the conditional style property for a given cell reference in the active sheet or
148
+ * undefined if this cell doesn't have a conditional style set.
149
+ */
150
+ getConditionalStyle(xc: string): Style | undefined {
151
+ return (
152
+ this.computedStyles[this.workbook.activeSheet.id] &&
153
+ this.computedStyles[this.workbook.activeSheet.id][xc]
154
+ );
155
+ }
156
+
157
+ getRulesSelection(selection: [Zone]): string[] {
158
+ const ruleIds: Set<string> = new Set();
159
+ selection.forEach((zone) => {
160
+ const zoneRuleId = this.getRulesByZone(zone);
161
+ zoneRuleId.forEach((ruleId) => {
162
+ ruleIds.add(ruleId);
163
+ });
164
+ });
165
+ return Array.from(ruleIds);
166
+ }
167
+ getRulesByZone(zone: Zone): Set<string> {
168
+ const ruleIds: Set<string> = new Set();
169
+ for (let row = zone.top; row <= zone.bottom; row++) {
170
+ for (let col = zone.left; col <= zone.right; col++) {
171
+ const cellRules = this.getRulesByCell(toXC(col, row));
172
+ cellRules.forEach((rule) => {
173
+ ruleIds.add(rule.id);
174
+ });
175
+ }
176
+ }
177
+ return ruleIds;
178
+ }
179
+ getRulesByCell(cellXc: string): Set<ConditionalFormat> {
180
+ const currentSheet = this.workbook.activeSheet.id;
181
+ const rulesId: Set<ConditionalFormat> = new Set();
182
+ for (let cf of this.cfRules[currentSheet]) {
183
+ for (let ref of cf.ranges) {
184
+ const zone: Zone = toZone(ref);
185
+ for (let row = zone.top; row <= zone.bottom; row++) {
186
+ for (let col = zone.left; col <= zone.right; col++) {
187
+ let xc = toXC(col, row);
188
+ if (cellXc == xc) {
189
+ rulesId.add(cf);
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ return rulesId;
196
+ }
197
+
198
+ // ---------------------------------------------------------------------------
199
+ // Private
200
+ // ---------------------------------------------------------------------------
201
+
202
+ /**
203
+ * Add or replace a conditional format rule
204
+ */
205
+ private addConditionalFormatting(cf: ConditionalFormat, sheet: string) {
206
+ const currentCF = this.cfRules[sheet].slice();
207
+ const replaceIndex = currentCF.findIndex((c) => c.id === cf.id);
208
+
209
+ if (replaceIndex > -1) {
210
+ currentCF.splice(replaceIndex, 1, cf);
211
+ } else {
212
+ currentCF.push(cf);
213
+ }
214
+ this.history.updateLocalState(["cfRules", sheet], currentCF);
215
+ }
216
+
217
+ /**
218
+ * Execute the complete color scale for the range of the conditional format for a 2 colors rule
219
+ */
220
+ private applyColorScale(range: string, rule: ColorScaleRule): void {
221
+ const minValue = Number(this.getters.evaluateFormula(`=min(${range})`));
222
+ const maxValue = Number(this.getters.evaluateFormula(`=max(${range})`));
223
+ if (Number.isNaN(minValue) || Number.isNaN(maxValue)) {
224
+ return;
225
+ }
226
+ const deltaValue = maxValue - minValue;
227
+ if (!deltaValue) {
228
+ return;
229
+ }
230
+ const deltaColorR = ((rule.minimum.color >> 16) % 256) - ((rule.maximum.color >> 16) % 256);
231
+ const deltaColorG = ((rule.minimum.color >> 8) % 256) - ((rule.maximum.color >> 8) % 256);
232
+ const deltaColorB = (rule.minimum.color % 256) - (rule.maximum.color % 256);
233
+
234
+ const colorDiffUnitR = deltaColorR / deltaValue;
235
+ const colorDiffUnitG = deltaColorG / deltaValue;
236
+ const colorDiffUnitB = deltaColorB / deltaValue;
237
+ const zone: Zone = toZone(range);
238
+ for (let row = zone.top; row <= zone.bottom; row++) {
239
+ for (let col = zone.left; col <= zone.right; col++) {
240
+ const cell = this.getters.getCell(col, row);
241
+ if (cell && !Number.isNaN(Number.parseFloat(cell.value))) {
242
+ const r = Math.round(
243
+ ((rule.minimum.color >> 16) % 256) - colorDiffUnitR * (cell.value - minValue)
244
+ );
245
+ const g = Math.round(
246
+ ((rule.minimum.color >> 8) % 256) - colorDiffUnitG * (cell.value - minValue)
247
+ );
248
+ const b = Math.round(
249
+ (rule.minimum.color % 256) - colorDiffUnitB * (cell.value - minValue)
250
+ );
251
+ const color = (r << 16) | (g << 8) | b;
252
+ this.computedStyles[this.workbook.activeSheet.id][cell.xc] =
253
+ this.computedStyles[this.workbook.activeSheet.id][cell.xc] || {};
254
+ this.computedStyles[this.workbook.activeSheet.id][cell.xc].fillColor =
255
+ "#" + colorNumberString(color);
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Execute the predicate to know if a conditional formatting rule should be applied to a cell
263
+ */
264
+ private rulePredicate: { CellIsRule: (cell: Cell, rule: CellIsRule) => boolean } = {
265
+ CellIsRule: (cell: Cell, rule: CellIsRule): boolean => {
266
+ switch (rule.operator) {
267
+ case "BeginsWith":
268
+ if (!cell && rule.values[0] === "") {
269
+ return false;
270
+ }
271
+ return cell && cell.value.startsWith(rule.values[0]);
272
+ case "EndsWith":
273
+ if (!cell && rule.values[0] === "") {
274
+ return false;
275
+ }
276
+ return cell && cell.value.endsWith(rule.values[0]);
277
+ case "Between":
278
+ return cell && cell.value >= rule.values[0] && cell.value <= rule.values[1];
279
+ case "NotBetween":
280
+ return !(cell && cell.value >= rule.values[0] && cell.value <= rule.values[1]);
281
+ case "ContainsText":
282
+ return cell && cell.value && cell.value.toString().indexOf(rule.values[0]) > -1;
283
+ case "NotContains":
284
+ return cell && cell.value && cell.value.toString().indexOf(rule.values[0]) == -1;
285
+ case "GreaterThan":
286
+ return cell && cell.value > rule.values[0];
287
+ case "GreaterThanOrEqual":
288
+ return cell && cell.value >= rule.values[0];
289
+ case "LessThan":
290
+ return cell && cell.value < rule.values[0];
291
+ case "LessThanOrEqual":
292
+ return cell && cell.value <= rule.values[0];
293
+ case "NotEqual":
294
+ if (!cell && rule.values[0] === "") {
295
+ return false;
296
+ }
297
+ return cell && cell.value != rule.values[0];
298
+ case "Equal":
299
+ if (!cell && rule.values[0] === "") {
300
+ return true;
301
+ }
302
+ return cell && cell.value == rule.values[0];
303
+ default:
304
+ console.warn(
305
+ _lt(
306
+ "Not implemented operator %s for kind of conditional formatting: %s",
307
+ rule.operator,
308
+ rule.type
309
+ )
310
+ );
311
+ }
312
+ return false;
313
+ },
314
+ };
315
+
316
+ /**
317
+ * Compute the styles according to the conditional formatting.
318
+ * This computation must happen after the cell values are computed if they change
319
+ *
320
+ * This result of the computation will be in the state.cell[XC].conditionalStyle and will be the union of all the style
321
+ * properties of the rules applied (in order).
322
+ * So if a cell has multiple conditional formatting applied to it, and each affect a different value of the style,
323
+ * the resulting style will have the combination of all those values.
324
+ * If multiple conditional formatting use the same style value, they will be applied in order so that the last applied wins
325
+ */
326
+ private computeStyles() {
327
+ const currentSheet = this.workbook.activeSheet.id;
328
+ this.computedStyles[currentSheet] = {};
329
+ for (let cf of this.cfRules[currentSheet]) {
330
+ try {
331
+ switch (cf.rule.type) {
332
+ case "ColorScaleRule":
333
+ for (let range of cf.ranges) {
334
+ this.applyColorScale(range, cf.rule);
335
+ }
336
+ break;
337
+ default:
338
+ for (let ref of cf.ranges) {
339
+ const zone: Zone = toZone(ref);
340
+ for (let row = zone.top; row <= zone.bottom; row++) {
341
+ for (let col = zone.left; col <= zone.right; col++) {
342
+ const pr = this.rulePredicate[cf.rule.type];
343
+ let cell = this.workbook.activeSheet.rows[row].cells[col];
344
+ let xc = toXC(col, row);
345
+ if (pr && pr(cell, cf.rule)) {
346
+ // we must combine all the properties of all the CF rules applied to the given cell
347
+ this.computedStyles[currentSheet][xc] = Object.assign(
348
+ this.computedStyles[currentSheet][xc] || {},
349
+ cf.rule.style
350
+ );
351
+ }
352
+ }
353
+ }
354
+ }
355
+ break;
356
+ }
357
+ } catch (_) {
358
+ // we don't care about the errors within the evaluation of a rule
359
+ }
360
+ }
361
+ }
362
+
363
+ private adaptcfRules(sheet: string, updateCb: (range: string) => string | null) {
364
+ const currentCfs = this.cfRules[sheet];
365
+ const newCfs: ConditionalFormat[] = [];
366
+ for (let cf of currentCfs) {
367
+ const updatedRanges: string[] = [];
368
+ for (let range of cf.ranges) {
369
+ const updatedRange = updateCb(range);
370
+ if (updatedRange) {
371
+ updatedRanges.push(updatedRange);
372
+ }
373
+ }
374
+ if (updatedRanges.length === 0) {
375
+ continue;
376
+ }
377
+ newCfs.push({ ...cf, ranges: updatedRanges });
378
+ }
379
+ this.history.updateLocalState(["cfRules", sheet], newCfs);
380
+ }
381
+
382
+ private removeConditionalFormatting(id: string, sheet: string) {
383
+ const cfIndex = this.cfRules[sheet].findIndex((s) => s.id === id);
384
+ if (cfIndex !== -1) {
385
+ const currentCF = this.cfRules[sheet].slice();
386
+ currentCF.splice(cfIndex, 1);
387
+ this.history.updateLocalState(["cfRules", sheet], currentCF);
388
+ }
389
+ }
390
+
391
+ // ---------------------------------------------------------------------------
392
+ // Copy/Cut/Paste and Merge
393
+ // ---------------------------------------------------------------------------
394
+ private pasteCf(
395
+ originCol: number,
396
+ originRow: number,
397
+ col: number,
398
+ row: number,
399
+ originSheet: string,
400
+ cut?: boolean
401
+ ) {
402
+ const xc = toXC(col, row);
403
+ for (let rule of this.cfRules[originSheet]) {
404
+ for (let range of rule.ranges) {
405
+ if (isInside(originCol, originRow, toZone(range))) {
406
+ const cf = rule;
407
+ const toRemoveRange: string[] = [];
408
+ if (cut) {
409
+ //remove from current rule
410
+ toRemoveRange.push(toXC(originCol, originRow));
411
+ }
412
+ if (originSheet === this.workbook.activeSheet.id) {
413
+ this.adaptRules(originSheet, cf, [xc], toRemoveRange);
414
+ } else {
415
+ this.adaptRules(this.workbook.activeSheet.id, cf, [xc], []);
416
+ this.adaptRules(originSheet, cf, [], toRemoveRange);
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }
422
+
423
+ private adaptRules(sheet: string, cf: ConditionalFormat, toAdd: string[], toRemove: string[]) {
424
+ if (toAdd.length === 0 && toRemove.length === 0) {
425
+ return;
426
+ }
427
+ const replaceIndex = this.cfRules[sheet].findIndex((c) => c.id === cf.id);
428
+ let currentRanges: string[] = [];
429
+ if (replaceIndex > -1) {
430
+ currentRanges = this.cfRules[sheet][replaceIndex].ranges;
431
+ }
432
+
433
+ currentRanges = currentRanges.concat(toAdd);
434
+ const newRange: string[] = recomputeZones(currentRanges, toRemove);
435
+ this.addConditionalFormatting(
436
+ {
437
+ id: cf.id,
438
+ rule: cf.rule,
439
+ stopIfTrue: cf.stopIfTrue,
440
+ ranges: newRange,
441
+ },
442
+ sheet
443
+ );
444
+ }
445
+ }