@odoo/o-spreadsheet 19.4.0-alpha.6 → 19.4.0-alpha.7

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 19.4.0-alpha.6
6
- * @date 2026-05-15T07:08:41.621Z
7
- * @hash 0207b9d
5
+ * @version 19.4.0-alpha.7
6
+ * @date 2026-05-20T14:40:27.236Z
7
+ * @hash 7ee566f
8
8
  */
9
9
 
10
10
  (function(exports, _odoo_owl) {
@@ -141,17 +141,234 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
141
141
  };
142
142
  }
143
143
 
144
+ //#endregion
145
+ //#region src/components/helpers/dom_helpers.ts
146
+ const macRegex = /Mac/i;
147
+ let zoomCssDoesNotAffectBoundingRect = false;
148
+ function defineZoomCssImpactOnBoundingRect() {
149
+ const div = document.createElement("div");
150
+ div.setAttribute("style", `width:10px;height:1px;zoom:2;position:absolute;z-index:-10000`);
151
+ document.body.appendChild(div);
152
+ zoomCssDoesNotAffectBoundingRect = div.getBoundingClientRect().width !== 20;
153
+ document.body.removeChild(div);
154
+ }
155
+ (0, _odoo_owl.whenReady)(defineZoomCssImpactOnBoundingRect);
156
+ const MODIFIER_KEYS = [
157
+ "Shift",
158
+ "Control",
159
+ "Alt",
160
+ "Meta"
161
+ ];
162
+ /**
163
+ * Return true if the event was triggered from
164
+ * a child element.
165
+ */
166
+ function isChildEvent(parent, ev) {
167
+ if (!parent) return false;
168
+ return !!ev.target && parent.contains(ev.target);
169
+ }
170
+ function gridOverlayPosition(zoom = 1) {
171
+ const spreadsheetElement = document.querySelector(".o-grid-overlay");
172
+ const result = spreadsheetElement && zoomCorrectedElementRect(spreadsheetElement, zoom);
173
+ if (!result) throw new Error("Can't find spreadsheet position");
174
+ return result;
175
+ }
176
+ function zoomCorrectedElementRect(el, zoomLevel) {
177
+ const zoomedElement = el.closest(".o-zoomable");
178
+ let targetEl;
179
+ let zoom = 1;
180
+ if (zoomedElement) {
181
+ targetEl = zoomedElement;
182
+ zoom = zoomCssDoesNotAffectBoundingRect ? zoomLevel : 1;
183
+ } else targetEl = el;
184
+ const rect = targetEl.getBoundingClientRect();
185
+ return {
186
+ x: rect.x * zoom,
187
+ y: rect.y * zoom,
188
+ width: rect.width * zoom,
189
+ height: rect.height * zoom
190
+ };
191
+ }
192
+ function getRefBoundingRect(ref) {
193
+ if (!ref.el) return {
194
+ x: 0,
195
+ y: 0,
196
+ width: 0,
197
+ height: 0
198
+ };
199
+ return getBoundingRectAsPOJO(ref.el);
200
+ }
201
+ function getBoundingRectAsPOJO(el) {
202
+ const rect = el.getBoundingClientRect();
203
+ return {
204
+ x: rect.x,
205
+ y: rect.y,
206
+ width: rect.width,
207
+ height: rect.height
208
+ };
209
+ }
210
+ /**
211
+ * Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
212
+ */
213
+ function* iterateChildren(el) {
214
+ yield el;
215
+ if (el.hasChildNodes()) for (const child of el.childNodes) yield* iterateChildren(child);
216
+ }
217
+ function getOpenedMenus() {
218
+ return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
219
+ }
220
+ function getCurrentSelection(el) {
221
+ const { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
222
+ return {
223
+ start: findSelectionIndex(el, startElement, startSelectionOffset),
224
+ end: findSelectionIndex(el, endElement, endSelectionOffset)
225
+ };
226
+ }
227
+ function getStartAndEndSelection(el) {
228
+ const selection = document.getSelection();
229
+ return {
230
+ startElement: selection.anchorNode || el,
231
+ startSelectionOffset: selection.anchorOffset,
232
+ endElement: selection.focusNode || el,
233
+ endSelectionOffset: selection.focusOffset
234
+ };
235
+ }
236
+ /**
237
+ * Computes the text 'index' inside this.el based on the currently selected node and its offset.
238
+ * The selected node is either a Text node or an Element node.
239
+ *
240
+ * case 1 -Text node:
241
+ * the offset is the number of characters from the start of the node. We have to add this offset to the
242
+ * content length of all previous nodes.
243
+ *
244
+ * case 2 - Element node:
245
+ * the offset is the number of child nodes before the selected node. We have to add the content length of
246
+ * all the nodes prior to the selected node as well as the content of the child node before the offset.
247
+ *
248
+ * See the MDN documentation for more details.
249
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
250
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
251
+ *
252
+ */
253
+ function findSelectionIndex(el, nodeToFind, nodeOffset) {
254
+ let usedCharacters = 0;
255
+ const it = iterateChildren(el);
256
+ let current = it.next();
257
+ let isFirstParagraph = true;
258
+ while (!current.done && current.value !== nodeToFind) {
259
+ if (!current.value.hasChildNodes()) {
260
+ if (current.value.textContent) usedCharacters += current.value.textContent.length;
261
+ }
262
+ if (current.value.nodeName === "P" || current.value.nodeName === "DIV" && current.value !== el) if (isFirstParagraph) isFirstParagraph = false;
263
+ else usedCharacters++;
264
+ current = it.next();
265
+ }
266
+ if (current.value !== nodeToFind)
267
+ /** This situation can happen if the code is called while the selection is not currently on the element.
268
+ * In this case, we return 0 because we don't know the size of the text before the selection.
269
+ *
270
+ * A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
271
+ */
272
+ return 0;
273
+ else if (!current.value.hasChildNodes()) usedCharacters += nodeOffset;
274
+ else {
275
+ const children = [...current.value.childNodes].slice(0, nodeOffset);
276
+ usedCharacters += children.reduce((acc, child, index) => {
277
+ if (child.textContent !== null) {
278
+ let chars = child.textContent.length;
279
+ if (child.nodeName === "P" && index !== children.length - 1) chars++;
280
+ return acc + chars;
281
+ } else return acc;
282
+ }, 0);
283
+ }
284
+ if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") usedCharacters++;
285
+ return usedCharacters;
286
+ }
287
+ const letterRegex = /^[a-zA-Z]$/;
288
+ /**
289
+ * Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
290
+ *
291
+ * @argument ev - The keyboard event to transform
292
+ * @argument mode - Use either ev.key of ev.code to get the string shortcut
293
+ *
294
+ * @example
295
+ * event : { ctrlKey: true, key: "a" } => "Ctrl+A"
296
+ * event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
297
+ */
298
+ function keyboardEventToShortcutString(ev, mode = "key") {
299
+ let keyDownString = "";
300
+ if (!MODIFIER_KEYS.includes(ev.key)) {
301
+ if (isCtrlKey(ev)) keyDownString += "Ctrl+";
302
+ if (ev.altKey) keyDownString += "Alt+";
303
+ if (ev.shiftKey) keyDownString += "Shift+";
304
+ }
305
+ const key = mode === "key" ? ev.key : ev.code;
306
+ keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
307
+ return keyDownString;
308
+ }
309
+ function isMacOS() {
310
+ return Boolean(macRegex.test(navigator.userAgent));
311
+ }
312
+ /**
313
+ * @param {KeyboardEvent | MouseEvent} ev
314
+ * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
315
+ * On Mac, this is the "meta" or "command" key.
316
+ */
317
+ function isCtrlKey(ev) {
318
+ return isMacOS() || isIOS() ? ev.metaKey : ev.ctrlKey;
319
+ }
320
+ /**
321
+ * @param {MouseEvent} ev - The mouse event.
322
+ * @returns {boolean} Returns true if the event was triggered by a middle-click
323
+ * or a Ctrl + Click (Cmd + Click on Mac).
324
+ */
325
+ function isMiddleClickOrCtrlClick(ev) {
326
+ return ev.button === 1 || isCtrlKey(ev) && ev.button === 0;
327
+ }
328
+ function downloadFile(dataUrl, fileName) {
329
+ const a = document.createElement("a");
330
+ a.href = dataUrl;
331
+ a.download = fileName;
332
+ document.body.appendChild(a);
333
+ a.click();
334
+ document.body.removeChild(a);
335
+ }
336
+ /**
337
+ * Detects if the current browser is Firefox
338
+ */
339
+ function isBrowserFirefox() {
340
+ return /Firefox/i.test(navigator.userAgent);
341
+ }
342
+ function maxTouchPoints() {
343
+ return navigator.maxTouchPoints || 1;
344
+ }
345
+ function isAndroid() {
346
+ return /Android/i.test(navigator.userAgent);
347
+ }
348
+ function isIOS() {
349
+ return /(iPad|iPhone|iPod)/i.test(navigator.userAgent) || navigator.platform === "MacIntel" && maxTouchPoints() > 1;
350
+ }
351
+ function isOtherMobileOS() {
352
+ return /(webOS|BlackBerry|Windows Phone)/i.test(navigator.userAgent);
353
+ }
354
+ function isMobileOS() {
355
+ return isAndroid() || isIOS() || isOtherMobileOS();
356
+ }
357
+
144
358
  //#endregion
145
359
  //#region src/actions/action.ts
146
360
  function createActions(menuItems) {
147
361
  return menuItems.map(createAction).sort((a, b) => a.sequence - b.sequence);
148
362
  }
363
+ function adaptShortcutMacOs(shortcut) {
364
+ return shortcut.replace("Ctrl", "⌘").replace("Alt", "⌃");
365
+ }
149
366
  let nextItemId = 1;
150
367
  function createAction(item) {
151
368
  const name = item.name;
152
369
  const children = item.children;
153
370
  const description = item.description;
154
- const shortcut = item.shortcut;
371
+ const shortcut = item.shortcut && isMacOS() ? adaptShortcutMacOs(item.shortcut) : item.shortcut;
155
372
  const icon = item.icon;
156
373
  const secondaryIcon = item.secondaryIcon;
157
374
  const itemId = item.id || nextItemId++;
@@ -5033,36 +5250,77 @@ stores.inject(MyMetaStore, storeInstance);
5033
5250
  } else args[i] = arg[0][0];
5034
5251
  }
5035
5252
  }
5036
- if (countVectorizedCol === 1 && countVectorizedRow === 1) return errorHandlingCompute(descr, context, args);
5037
- const getArgOffset = (i, j) => args.map((arg, index) => {
5038
- switch (vectorArgsType?.[index]) {
5039
- case "matrix": return arg[i][j];
5040
- case "horizontal": return arg[i][0];
5041
- case "vertical": return arg[0][j];
5042
- case void 0: return arg;
5253
+ const argsToFocus = argTargeting(descr, args.length);
5254
+ const argDefinitions = new Array(args.length);
5255
+ for (let k = 0; k < args.length; k++) argDefinitions[k] = descr.args[argsToFocus[k].index];
5256
+ if (countVectorizedCol === 1 && countVectorizedRow === 1) return errorHandlingCompute(descr, context, args, argDefinitions);
5257
+ const argsBuffer = new Array(args.length);
5258
+ const argGetters = [];
5259
+ const vectorizedIndices = [];
5260
+ for (let k = 0; k < args.length; k++) {
5261
+ const arg = args[k];
5262
+ switch (vectorArgsType?.[k]) {
5263
+ case "matrix":
5264
+ argGetters.push((i, j) => arg[i][j]);
5265
+ vectorizedIndices.push(k);
5266
+ break;
5267
+ case "horizontal":
5268
+ argGetters.push((i) => arg[i][0]);
5269
+ vectorizedIndices.push(k);
5270
+ break;
5271
+ case "vertical":
5272
+ argGetters.push((_i, j) => arg[0][j]);
5273
+ vectorizedIndices.push(k);
5274
+ break;
5275
+ case void 0:
5276
+ argsBuffer[k] = arg;
5277
+ break;
5043
5278
  }
5044
- });
5045
- return generateMatrix(countVectorizedCol, countVectorizedRow, (col, row) => {
5046
- if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) return new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
5047
- const singleCellComputeResult = errorHandlingCompute(descr, context, getArgOffset(col, row));
5048
- return isMatrix(singleCellComputeResult) ? singleCellComputeResult[0][0] : singleCellComputeResult;
5049
- });
5279
+ }
5280
+ const nbVectorized = vectorizedIndices.length;
5281
+ const result = new Array(countVectorizedCol);
5282
+ for (let col = 0; col < countVectorizedCol; col++) {
5283
+ const column = new Array(countVectorizedRow);
5284
+ result[col] = column;
5285
+ for (let row = 0; row < countVectorizedRow; row++) {
5286
+ if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) {
5287
+ column[row] = new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
5288
+ continue;
5289
+ }
5290
+ for (let k = 0; k < nbVectorized; k++) argsBuffer[vectorizedIndices[k]] = argGetters[k](col, row);
5291
+ const singleCellComputeResult = errorHandlingCompute(descr, context, argsBuffer, argDefinitions);
5292
+ column[row] = isMatrix(singleCellComputeResult) ? singleCellComputeResult[0][0] : singleCellComputeResult;
5293
+ }
5294
+ }
5295
+ return result;
5050
5296
  }
5051
5297
  function computeFunctionToObject(descr, context, args) {
5052
5298
  if (context.debug) {
5053
5299
  debugger;
5054
5300
  context.debug = false;
5055
5301
  }
5056
- const result = descr.compute.apply(context, args);
5302
+ const compute = descr.compute;
5303
+ let result;
5304
+ switch (args.length) {
5305
+ case 1:
5306
+ result = compute.call(context, args[0]);
5307
+ break;
5308
+ case 2:
5309
+ result = compute.call(context, args[0], args[1]);
5310
+ break;
5311
+ case 3:
5312
+ result = compute.call(context, args[0], args[1], args[2]);
5313
+ break;
5314
+ default: result = compute.apply(context, args);
5315
+ }
5057
5316
  if (!isMatrix(result)) return isFunctionResultObject(result) ? result : { value: result };
5058
5317
  if (isFunctionResultObject(result[0][0])) return result;
5059
5318
  return matrixMap(result, (row) => ({ value: row }));
5060
5319
  }
5061
- function errorHandlingCompute(descr, context, args) {
5062
- const argsToFocus = argTargeting(descr, args.length);
5320
+ function errorHandlingCompute(descr, context, args, argDefinitions) {
5063
5321
  for (let i = 0; i < args.length; i++) {
5064
5322
  const arg = args[i];
5065
- if (!descr.args[argsToFocus[i].index].acceptErrors && !isMatrix(arg) && isEvaluationError(arg?.value)) return arg;
5323
+ if (!argDefinitions[i].acceptErrors && !isMatrix(arg) && isEvaluationError(arg?.value)) return arg;
5066
5324
  }
5067
5325
  try {
5068
5326
  return computeFunctionToObject(descr, context, args);
@@ -6261,8 +6519,13 @@ stores.inject(MyMetaStore, storeInstance);
6261
6519
  changeType: "NONE",
6262
6520
  range
6263
6521
  };
6522
+ const isUnboundedAtEnd = range.unboundedZone[end] === void 0;
6523
+ if (isUnboundedAtEnd && !range.unboundedZone.hasHeader) return {
6524
+ changeType: "RESIZE",
6525
+ range: createAdaptedRange(range, dimension, "RESIZE", cmd.quantity)
6526
+ };
6264
6527
  if (cmd.position === "after") {
6265
- if (range.zone[start] <= cmd.base && cmd.base < range.zone[end]) return {
6528
+ if (range.zone[start] <= cmd.base && (cmd.base < range.zone[end] || isUnboundedAtEnd)) return {
6266
6529
  changeType: "RESIZE",
6267
6530
  range: createAdaptedRange(range, dimension, "RESIZE", cmd.quantity)
6268
6531
  };
@@ -6508,40 +6771,65 @@ stores.inject(MyMetaStore, storeInstance);
6508
6771
 
6509
6772
  //#endregion
6510
6773
  //#region src/formulas/code_builder.ts
6774
+ var JsString = class extends String {};
6775
+ /**
6776
+ * Creates a JsString from a raw string, bypassing the template string interpolation checks.
6777
+ * This can lead to security vulnerabilities if the string is not trusted!
6778
+ */
6779
+ function dangerouslyCreateJsStr(trustedStr) {
6780
+ return new JsString(trustedStr);
6781
+ }
6782
+ /**
6783
+ * Creates a JsString from a template string, ensuring that all interpolated values are safe.
6784
+ */
6785
+ function jsStr(strings, ...values) {
6786
+ let str = "";
6787
+ for (let i = 0; i < strings.length; i++) {
6788
+ const value = values[i];
6789
+ if (i >= values.length) str += strings[i];
6790
+ else if (isSafeJsValue(value)) str += strings[i] + value;
6791
+ else if (Array.isArray(value) && value.every(isSafeJsValue)) str += strings[i] + value.map((v) => v.toString()).join(",");
6792
+ else throw new Error(`Invalid interpolated value at index ${i}: ${value}`);
6793
+ }
6794
+ return dangerouslyCreateJsStr(str);
6795
+ }
6796
+ function isSafeJsValue(value) {
6797
+ return value instanceof JsString || typeof value === "number" || typeof value === "boolean";
6798
+ }
6511
6799
  var FunctionCodeBuilder = class {
6512
6800
  scope;
6513
- code = "";
6801
+ code = [];
6514
6802
  constructor(scope = new Scope()) {
6515
6803
  this.scope = scope;
6516
6804
  }
6517
6805
  append(...lines) {
6518
- this.code += lines.map((line) => line.toString()).join("\n") + "\n";
6806
+ for (const line of lines) if (line instanceof FunctionCodeImpl) this.code.push(...line.code);
6807
+ else if (line instanceof JsString) this.code.push(line);
6808
+ else throw new Error(`Invalid line: ${line}`);
6519
6809
  }
6520
6810
  return(expression) {
6811
+ if (!isSafeJsValue(expression)) throw new Error(`Expected JsString, got ${expression}`);
6521
6812
  return new FunctionCodeImpl(this.scope, this.code, expression);
6522
6813
  }
6523
6814
  toString() {
6524
- return indentCode(this.code);
6815
+ return indentCode(this.code.join("\n"));
6525
6816
  }
6526
6817
  };
6527
6818
  var FunctionCodeImpl = class {
6528
6819
  scope;
6529
- returnExpression;
6530
6820
  code;
6821
+ returnExpression;
6531
6822
  constructor(scope, code, returnExpression) {
6532
6823
  this.scope = scope;
6824
+ this.code = code;
6533
6825
  this.returnExpression = returnExpression;
6534
- this.code = indentCode(code);
6535
- }
6536
- toString() {
6537
- return this.code;
6538
6826
  }
6539
6827
  assignResultToVariable() {
6540
6828
  if (this.scope.isAlreadyDeclared(this.returnExpression)) return this;
6541
6829
  const variableName = this.scope.nextVariableName();
6542
6830
  const code = new FunctionCodeBuilder(this.scope);
6543
- code.append(this.code);
6544
- code.append(`const ${variableName} = ${this.returnExpression};`);
6831
+ code.append(...this.code);
6832
+ code.append(jsStr`const ${variableName} = ${this.returnExpression};`);
6545
6833
  return code.return(variableName);
6546
6834
  }
6547
6835
  };
@@ -6549,12 +6837,12 @@ stores.inject(MyMetaStore, storeInstance);
6549
6837
  nextId = 1;
6550
6838
  declaredVariables = /* @__PURE__ */ new Set();
6551
6839
  nextVariableName() {
6552
- const name = `_${this.nextId++}`;
6553
- this.declaredVariables.add(name);
6840
+ const name = jsStr`_${this.nextId++}`;
6841
+ this.declaredVariables.add(name.toString());
6554
6842
  return name;
6555
6843
  }
6556
6844
  isAlreadyDeclared(name) {
6557
- return this.declaredVariables.has(name);
6845
+ return this.declaredVariables.has(name.toString());
6558
6846
  }
6559
6847
  };
6560
6848
  /**
@@ -6771,6 +7059,7 @@ stores.inject(MyMetaStore, storeInstance);
6771
7059
  try {
6772
7060
  return compileTokensOrThrow(tokens);
6773
7061
  } catch (error) {
7062
+ if (!(error instanceof EvaluationError)) throw error;
6774
7063
  return {
6775
7064
  tokens,
6776
7065
  literalValues: {
@@ -6801,7 +7090,7 @@ stores.inject(MyMetaStore, storeInstance);
6801
7090
  const compiledAST = compileAST(ast);
6802
7091
  const code = new FunctionCodeBuilder();
6803
7092
  code.append(compiledAST);
6804
- code.append(`return ${compiledAST.returnExpression};`);
7093
+ code.append(jsStr`return ${compiledAST.returnExpression};`);
6805
7094
  functionCache[cacheKey] = new Function("deps", "ref", "range", "getSymbolValue", "ctx", code.toString());
6806
7095
  /**
6807
7096
  * This function compile the function arguments. It is mostly straightforward,
@@ -6834,19 +7123,21 @@ stores.inject(MyMetaStore, storeInstance);
6834
7123
  function compileAST(ast, hasRange = false) {
6835
7124
  const code = new FunctionCodeBuilder(scope);
6836
7125
  if (ast.debug) {
6837
- code.append("debugger;");
6838
- code.append(`ctx["debug"] = true;`);
7126
+ code.append(jsStr`debugger;`);
7127
+ code.append(jsStr`ctx["debug"] = true;`);
6839
7128
  }
6840
7129
  switch (ast.type) {
6841
- case "BOOLEAN": return code.return(`{ value: ${ast.value} }`);
6842
- case "NUMBER": return code.return(`this.literalValues.numbers[${numberCount++}]`);
6843
- case "STRING": return code.return(`this.literalValues.strings[${stringCount++}]`);
6844
- case "REFERENCE": return code.return(`${ast.value.includes(":") || hasRange ? `range` : `ref`}(deps[${dependencyCount++}])`);
7130
+ case "BOOLEAN": return code.return(jsStr`{ value: ${ast.value} }`);
7131
+ case "NUMBER": return code.return(jsStr`this.literalValues.numbers[${numberCount++}]`);
7132
+ case "STRING": return code.return(jsStr`this.literalValues.strings[${stringCount++}]`);
7133
+ case "REFERENCE": return code.return(jsStr`${ast.value.includes(":") || hasRange ? jsStr`range` : jsStr`ref`}(deps[${dependencyCount++}])`);
6845
7134
  case "FUNCALL":
6846
7135
  const args = compileFunctionArgs(ast).map((arg) => arg.assignResultToVariable());
6847
7136
  code.append(...args);
6848
7137
  const fnName = ast.value.toUpperCase();
6849
- return code.return(`ctx['${fnName}'](${args.map((arg) => arg.returnExpression)})`);
7138
+ if (!Object.hasOwn(functions$1, fnName)) throw new Error(`Unknown function: "${fnName}"`);
7139
+ const jsFnName = dangerouslyCreateJsStr(fnName);
7140
+ return code.return(jsStr`ctx['${jsFnName}'](${args.map((arg) => arg.returnExpression)})`);
6850
7141
  case "ARRAY": return compileAST({
6851
7142
  type: "FUNCALL",
6852
7143
  value: "ARRAY.LITERAL",
@@ -6861,23 +7152,25 @@ stores.inject(MyMetaStore, storeInstance);
6861
7152
  tokenEndIndex: 0
6862
7153
  });
6863
7154
  case "UNARY_OPERATION": {
6864
- const fnName = UNARY_OPERATOR_MAP[ast.value];
7155
+ if (!Object.hasOwn(UNARY_OPERATOR_MAP, ast.value)) throw new Error(`Unknown operator: "${ast.value}"`);
7156
+ const fnName = dangerouslyCreateJsStr(UNARY_OPERATOR_MAP[ast.value]);
6865
7157
  const operand = compileAST(ast.operand, ast.value === "#").assignResultToVariable();
6866
7158
  code.append(operand);
6867
- return code.return(`ctx['${fnName}'](${operand.returnExpression})`);
7159
+ return code.return(jsStr`ctx['${fnName}'](${operand.returnExpression})`);
6868
7160
  }
6869
7161
  case "BIN_OPERATION": {
6870
- const fnName = OPERATOR_MAP[ast.value];
7162
+ if (!Object.hasOwn(OPERATOR_MAP, ast.value)) throw new Error(`Unknown operator: "${ast.value}"`);
7163
+ const fnName = dangerouslyCreateJsStr(OPERATOR_MAP[ast.value]);
6871
7164
  const left = compileAST(ast.left, false).assignResultToVariable();
6872
7165
  const right = compileAST(ast.right, false).assignResultToVariable();
6873
7166
  code.append(left);
6874
7167
  code.append(right);
6875
- return code.return(`ctx['${fnName}'](${left.returnExpression}, ${right.returnExpression})`);
7168
+ return code.return(jsStr`ctx['${fnName}'](${left.returnExpression}, ${right.returnExpression})`);
6876
7169
  }
6877
7170
  case "SYMBOL":
6878
7171
  const symbolIndex = symbols.indexOf(ast.value);
6879
- return code.return(`getSymbolValue(this.symbols[${symbolIndex}], ${hasRange})`);
6880
- case "EMPTY": return code.return("undefined");
7172
+ return code.return(jsStr`getSymbolValue(this.symbols[${symbolIndex}], ${hasRange})`);
7173
+ case "EMPTY": return code.return(jsStr`undefined`);
6881
7174
  }
6882
7175
  }
6883
7176
  }
@@ -11554,220 +11847,6 @@ stores.inject(MyMetaStore, storeInstance);
11554
11847
  }
11555
11848
  };
11556
11849
 
11557
- //#endregion
11558
- //#region src/components/helpers/dom_helpers.ts
11559
- const macRegex = /Mac/i;
11560
- let zoomCssDoesNotAffectBoundingRect = false;
11561
- function defineZoomCssImpactOnBoundingRect() {
11562
- const div = document.createElement("div");
11563
- div.setAttribute("style", `width:10px;height:1px;zoom:2;position:absolute;z-index:-10000`);
11564
- document.body.appendChild(div);
11565
- zoomCssDoesNotAffectBoundingRect = div.getBoundingClientRect().width !== 20;
11566
- document.body.removeChild(div);
11567
- }
11568
- (0, _odoo_owl.whenReady)(defineZoomCssImpactOnBoundingRect);
11569
- const MODIFIER_KEYS = [
11570
- "Shift",
11571
- "Control",
11572
- "Alt",
11573
- "Meta"
11574
- ];
11575
- /**
11576
- * Return true if the event was triggered from
11577
- * a child element.
11578
- */
11579
- function isChildEvent(parent, ev) {
11580
- if (!parent) return false;
11581
- return !!ev.target && parent.contains(ev.target);
11582
- }
11583
- function gridOverlayPosition(zoom = 1) {
11584
- const spreadsheetElement = document.querySelector(".o-grid-overlay");
11585
- const result = spreadsheetElement && zoomCorrectedElementRect(spreadsheetElement, zoom);
11586
- if (!result) throw new Error("Can't find spreadsheet position");
11587
- return result;
11588
- }
11589
- function zoomCorrectedElementRect(el, zoomLevel) {
11590
- const zoomedElement = el.closest(".o-zoomable");
11591
- let targetEl;
11592
- let zoom = 1;
11593
- if (zoomedElement) {
11594
- targetEl = zoomedElement;
11595
- zoom = zoomCssDoesNotAffectBoundingRect ? zoomLevel : 1;
11596
- } else targetEl = el;
11597
- const rect = targetEl.getBoundingClientRect();
11598
- return {
11599
- x: rect.x * zoom,
11600
- y: rect.y * zoom,
11601
- width: rect.width * zoom,
11602
- height: rect.height * zoom
11603
- };
11604
- }
11605
- function getRefBoundingRect(ref) {
11606
- if (!ref.el) return {
11607
- x: 0,
11608
- y: 0,
11609
- width: 0,
11610
- height: 0
11611
- };
11612
- return getBoundingRectAsPOJO(ref.el);
11613
- }
11614
- function getBoundingRectAsPOJO(el) {
11615
- const rect = el.getBoundingClientRect();
11616
- return {
11617
- x: rect.x,
11618
- y: rect.y,
11619
- width: rect.width,
11620
- height: rect.height
11621
- };
11622
- }
11623
- /**
11624
- * Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
11625
- */
11626
- function* iterateChildren(el) {
11627
- yield el;
11628
- if (el.hasChildNodes()) for (const child of el.childNodes) yield* iterateChildren(child);
11629
- }
11630
- function getOpenedMenus() {
11631
- return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
11632
- }
11633
- function getCurrentSelection(el) {
11634
- const { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
11635
- return {
11636
- start: findSelectionIndex(el, startElement, startSelectionOffset),
11637
- end: findSelectionIndex(el, endElement, endSelectionOffset)
11638
- };
11639
- }
11640
- function getStartAndEndSelection(el) {
11641
- const selection = document.getSelection();
11642
- return {
11643
- startElement: selection.anchorNode || el,
11644
- startSelectionOffset: selection.anchorOffset,
11645
- endElement: selection.focusNode || el,
11646
- endSelectionOffset: selection.focusOffset
11647
- };
11648
- }
11649
- /**
11650
- * Computes the text 'index' inside this.el based on the currently selected node and its offset.
11651
- * The selected node is either a Text node or an Element node.
11652
- *
11653
- * case 1 -Text node:
11654
- * the offset is the number of characters from the start of the node. We have to add this offset to the
11655
- * content length of all previous nodes.
11656
- *
11657
- * case 2 - Element node:
11658
- * the offset is the number of child nodes before the selected node. We have to add the content length of
11659
- * all the nodes prior to the selected node as well as the content of the child node before the offset.
11660
- *
11661
- * See the MDN documentation for more details.
11662
- * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
11663
- * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
11664
- *
11665
- */
11666
- function findSelectionIndex(el, nodeToFind, nodeOffset) {
11667
- let usedCharacters = 0;
11668
- const it = iterateChildren(el);
11669
- let current = it.next();
11670
- let isFirstParagraph = true;
11671
- while (!current.done && current.value !== nodeToFind) {
11672
- if (!current.value.hasChildNodes()) {
11673
- if (current.value.textContent) usedCharacters += current.value.textContent.length;
11674
- }
11675
- if (current.value.nodeName === "P" || current.value.nodeName === "DIV" && current.value !== el) if (isFirstParagraph) isFirstParagraph = false;
11676
- else usedCharacters++;
11677
- current = it.next();
11678
- }
11679
- if (current.value !== nodeToFind)
11680
- /** This situation can happen if the code is called while the selection is not currently on the element.
11681
- * In this case, we return 0 because we don't know the size of the text before the selection.
11682
- *
11683
- * A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
11684
- */
11685
- return 0;
11686
- else if (!current.value.hasChildNodes()) usedCharacters += nodeOffset;
11687
- else {
11688
- const children = [...current.value.childNodes].slice(0, nodeOffset);
11689
- usedCharacters += children.reduce((acc, child, index) => {
11690
- if (child.textContent !== null) {
11691
- let chars = child.textContent.length;
11692
- if (child.nodeName === "P" && index !== children.length - 1) chars++;
11693
- return acc + chars;
11694
- } else return acc;
11695
- }, 0);
11696
- }
11697
- if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") usedCharacters++;
11698
- return usedCharacters;
11699
- }
11700
- const letterRegex = /^[a-zA-Z]$/;
11701
- /**
11702
- * Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
11703
- *
11704
- * @argument ev - The keyboard event to transform
11705
- * @argument mode - Use either ev.key of ev.code to get the string shortcut
11706
- *
11707
- * @example
11708
- * event : { ctrlKey: true, key: "a" } => "Ctrl+A"
11709
- * event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
11710
- */
11711
- function keyboardEventToShortcutString(ev, mode = "key") {
11712
- let keyDownString = "";
11713
- if (!MODIFIER_KEYS.includes(ev.key)) {
11714
- if (isCtrlKey(ev)) keyDownString += "Ctrl+";
11715
- if (ev.altKey) keyDownString += "Alt+";
11716
- if (ev.shiftKey) keyDownString += "Shift+";
11717
- }
11718
- const key = mode === "key" ? ev.key : ev.code;
11719
- keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
11720
- return keyDownString;
11721
- }
11722
- function isMacOS() {
11723
- return Boolean(macRegex.test(navigator.userAgent));
11724
- }
11725
- /**
11726
- * @param {KeyboardEvent | MouseEvent} ev
11727
- * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
11728
- * On Mac, this is the "meta" or "command" key.
11729
- */
11730
- function isCtrlKey(ev) {
11731
- return isMacOS() || isIOS() ? ev.metaKey : ev.ctrlKey;
11732
- }
11733
- /**
11734
- * @param {MouseEvent} ev - The mouse event.
11735
- * @returns {boolean} Returns true if the event was triggered by a middle-click
11736
- * or a Ctrl + Click (Cmd + Click on Mac).
11737
- */
11738
- function isMiddleClickOrCtrlClick(ev) {
11739
- return ev.button === 1 || isCtrlKey(ev) && ev.button === 0;
11740
- }
11741
- function downloadFile(dataUrl, fileName) {
11742
- const a = document.createElement("a");
11743
- a.href = dataUrl;
11744
- a.download = fileName;
11745
- document.body.appendChild(a);
11746
- a.click();
11747
- document.body.removeChild(a);
11748
- }
11749
- /**
11750
- * Detects if the current browser is Firefox
11751
- */
11752
- function isBrowserFirefox() {
11753
- return /Firefox/i.test(navigator.userAgent);
11754
- }
11755
- function maxTouchPoints() {
11756
- return navigator.maxTouchPoints || 1;
11757
- }
11758
- function isAndroid() {
11759
- return /Android/i.test(navigator.userAgent);
11760
- }
11761
- function isIOS() {
11762
- return /(iPad|iPhone|iPod)/i.test(navigator.userAgent) || navigator.platform === "MacIntel" && maxTouchPoints() > 1;
11763
- }
11764
- function isOtherMobileOS() {
11765
- return /(webOS|BlackBerry|Windows Phone)/i.test(navigator.userAgent);
11766
- }
11767
- function isMobileOS() {
11768
- return isAndroid() || isIOS() || isOtherMobileOS();
11769
- }
11770
-
11771
11850
  //#endregion
11772
11851
  //#region src/components/helpers/zoom.ts
11773
11852
  /**
@@ -11852,6 +11931,9 @@ stores.inject(MyMetaStore, storeInstance);
11852
11931
  window.devicePixelRatio
11853
11932
  ];
11854
11933
  });
11934
+ const resizeObserver = new ResizeObserver(() => this.createChart());
11935
+ (0, _odoo_owl.onMounted)(() => resizeObserver.observe(this.canvas.el));
11936
+ (0, _odoo_owl.onWillUnmount)(() => resizeObserver.disconnect());
11855
11937
  }
11856
11938
  createChart() {
11857
11939
  const canvas = this.canvas.el;
@@ -13543,6 +13625,15 @@ stores.inject(MyMetaStore, storeInstance);
13543
13625
  window.devicePixelRatio
13544
13626
  ];
13545
13627
  });
13628
+ const resizeObserver = new ResizeObserver(() => {
13629
+ if (animation) {
13630
+ animation.stop();
13631
+ animation = null;
13632
+ }
13633
+ drawGaugeChart(this.canvasEl, this.runtime, this.env.model.getters.getViewportZoomLevel());
13634
+ });
13635
+ (0, _odoo_owl.onMounted)(() => resizeObserver.observe(this.canvas.el));
13636
+ (0, _odoo_owl.onWillUnmount)(() => resizeObserver.disconnect());
13546
13637
  }
13547
13638
  drawGaugeWithAnimation() {
13548
13639
  drawGaugeChart(this.canvasEl, {
@@ -23477,7 +23568,7 @@ stores.inject(MyMetaStore, storeInstance);
23477
23568
  };
23478
23569
  const insertTable = {
23479
23570
  name: () => _t("Table"),
23480
- description: "Alt+T",
23571
+ shortcut: "Alt+T",
23481
23572
  execute: INSERT_TABLE,
23482
23573
  isVisible: (env) => IS_SELECTION_CONTINUOUS(env) && !env.model.getters.getFirstTableInSelection(),
23483
23574
  isEnabled: (env) => !env.isSmall,
@@ -23526,7 +23617,7 @@ stores.inject(MyMetaStore, storeInstance);
23526
23617
  };
23527
23618
  const insertLink = {
23528
23619
  name: _t("Link"),
23529
- description: "Ctrl+Shift+K",
23620
+ shortcut: "Ctrl+Shift+K",
23530
23621
  execute: INSERT_LINK,
23531
23622
  icon: "o-spreadsheet-Icon.INSERT_LINK"
23532
23623
  };
@@ -23585,7 +23676,7 @@ stores.inject(MyMetaStore, storeInstance);
23585
23676
  };
23586
23677
  const insertSheet = {
23587
23678
  name: _t("Insert sheet"),
23588
- description: "Shift+F11",
23679
+ shortcut: "Shift+F11",
23589
23680
  execute: (env) => {
23590
23681
  const activeSheetId = env.model.getters.getActiveSheetId();
23591
23682
  const position = env.model.getters.getSheetIds().indexOf(activeSheetId) + 1;
@@ -27734,6 +27825,74 @@ stores.inject(MyMetaStore, storeInstance);
27734
27825
  } };
27735
27826
  }
27736
27827
 
27828
+ //#endregion
27829
+ //#region src/helpers/figures/charts/runtime/chart_highlight.ts
27830
+ const HIGHLIGHT_TRANSPARENCY = .2;
27831
+ function highlightComboChartItem(item, dataSets) {
27832
+ const index = item.datasetIndex;
27833
+ for (let i = 0; i < dataSets.length; i++) {
27834
+ if (i === index) continue;
27835
+ const dataset = dataSets[i];
27836
+ for (const key of ["borderColor", "backgroundColor"]) {
27837
+ if (!(key in dataset)) continue;
27838
+ dataset[key] = setColorAlpha(dataset[key], i === index ? 1 : HIGHLIGHT_TRANSPARENCY);
27839
+ }
27840
+ }
27841
+ }
27842
+ function resetComboChartHighlights(dataSets) {
27843
+ for (const dataset of dataSets) for (const key of ["borderColor", "backgroundColor"]) {
27844
+ if (!(key in dataset)) continue;
27845
+ dataset[key] = setColorAlpha(dataset[key], 1);
27846
+ }
27847
+ }
27848
+ function highlightLineChartItem(item, dataSets) {
27849
+ const index = item.datasetIndex;
27850
+ for (let i = 0; i < dataSets.length; i++) {
27851
+ const dataset = dataSets[i];
27852
+ const color = setColorAlpha(dataset.borderColor, i === index ? 1 : HIGHLIGHT_TRANSPARENCY);
27853
+ dataset.borderColor = color;
27854
+ dataset.pointBackgroundColor = color;
27855
+ dataset.backgroundColor = setColorAlpha(dataset.backgroundColor, LINE_FILL_TRANSPARENCY * (i === index ? 1 : HIGHLIGHT_TRANSPARENCY));
27856
+ }
27857
+ }
27858
+ function resetLineChartHighlights(dataSets) {
27859
+ for (const dataset of dataSets) {
27860
+ const color = setColorAlpha(dataset.borderColor, 1);
27861
+ dataset.borderColor = color;
27862
+ dataset.pointBackgroundColor = color;
27863
+ dataset.backgroundColor = setColorAlpha(dataset.backgroundColor, LINE_FILL_TRANSPARENCY);
27864
+ }
27865
+ }
27866
+ function toggleLineBarDataVisibility(chart, item) {
27867
+ const index = item.datasetIndex;
27868
+ if (index === void 0) return;
27869
+ if (chart.isDatasetVisible(index)) chart.hide(index);
27870
+ else chart.show(index);
27871
+ }
27872
+ function highlightPieChartItem(item, dataSets) {
27873
+ for (const dataset of dataSets) {
27874
+ const backgroundColors = dataset.backgroundColor;
27875
+ if (!backgroundColors) return;
27876
+ backgroundColors.forEach((color, i, colors) => {
27877
+ colors[i] = setColorAlpha(color, i === item.index ? 1 : HIGHLIGHT_TRANSPARENCY);
27878
+ });
27879
+ }
27880
+ }
27881
+ function resetPieChartHighlights(dataSets) {
27882
+ for (const dataset of dataSets) {
27883
+ const backgroundColors = dataset.backgroundColor;
27884
+ if (!backgroundColors) return;
27885
+ backgroundColors.forEach((color, i, colors) => {
27886
+ colors[i] = setColorAlpha(color, 1);
27887
+ });
27888
+ }
27889
+ }
27890
+ function togglePieDataVisibility(chart, item) {
27891
+ const index = item.index;
27892
+ if (index === void 0) return;
27893
+ chart.toggleDataVisibility(index);
27894
+ }
27895
+
27737
27896
  //#endregion
27738
27897
  //#region src/helpers/figures/charts/runtime/chartjs_legend.ts
27739
27898
  function getLegendDisplayOptions({ legendPosition }) {
@@ -27744,7 +27903,20 @@ stores.inject(MyMetaStore, storeInstance);
27744
27903
  }
27745
27904
  function getBarChartLegend(definition, args) {
27746
27905
  return {
27747
- ...INTERACTIVE_LEGEND_CONFIG,
27906
+ ...getInteractiveLegendConfig({
27907
+ highlightItem: highlightComboChartItem,
27908
+ unHighlightItems: resetComboChartHighlights,
27909
+ toggleDataVisibility: toggleLineBarDataVisibility
27910
+ }),
27911
+ ...getLegendDisplayOptions(definition),
27912
+ ...getCustomLegendLabels(chartFontColor(definition.background), {
27913
+ pointStyle: "rect",
27914
+ lineWidth: 3
27915
+ })
27916
+ };
27917
+ }
27918
+ function getPyramidChartLegend(definition, args) {
27919
+ return {
27748
27920
  ...getLegendDisplayOptions(definition),
27749
27921
  ...getCustomLegendLabels(chartFontColor(definition.background), {
27750
27922
  pointStyle: "rect",
@@ -27757,7 +27929,11 @@ stores.inject(MyMetaStore, storeInstance);
27757
27929
  const pointStyle = filled ? "rect" : "line";
27758
27930
  const lineWidth = filled ? 2 : 3;
27759
27931
  return {
27760
- ...INTERACTIVE_LEGEND_CONFIG,
27932
+ ...getInteractiveLegendConfig({
27933
+ highlightItem: highlightLineChartItem,
27934
+ unHighlightItems: resetLineChartHighlights,
27935
+ toggleDataVisibility: toggleLineBarDataVisibility
27936
+ }),
27761
27937
  ...getLegendDisplayOptions(definition),
27762
27938
  ...getCustomLegendLabels(chartFontColor(definition.background), {
27763
27939
  pointStyle,
@@ -27770,6 +27946,11 @@ stores.inject(MyMetaStore, storeInstance);
27770
27946
  const colors = getPieColors(new ColorGenerator(Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0)), definition.slicesColors), dataSetsValues);
27771
27947
  const fontColor = chartFontColor(definition.background);
27772
27948
  return {
27949
+ ...getInteractiveLegendConfig({
27950
+ highlightItem: highlightPieChartItem,
27951
+ unHighlightItems: resetPieChartHighlights,
27952
+ toggleDataVisibility: togglePieDataVisibility
27953
+ }),
27773
27954
  ...getLegendDisplayOptions(definition),
27774
27955
  labels: {
27775
27956
  usePointStyle: true,
@@ -27779,7 +27960,9 @@ stores.inject(MyMetaStore, storeInstance);
27779
27960
  fillStyle: colors[index],
27780
27961
  pointStyle: "rect",
27781
27962
  lineWidth: 2,
27782
- fontColor
27963
+ fontColor,
27964
+ index,
27965
+ hidden: !c.getDataVisibility?.(index)
27783
27966
  })) || []).filter((label) => label.text),
27784
27967
  filter: (legendItem, data) => {
27785
27968
  return "datasetIndex" in legendItem ? !data.datasets[legendItem.datasetIndex].hidden : true;
@@ -27789,9 +27972,13 @@ stores.inject(MyMetaStore, storeInstance);
27789
27972
  }
27790
27973
  function getScatterChartLegend(definition, args) {
27791
27974
  return {
27792
- ...INTERACTIVE_LEGEND_CONFIG,
27975
+ ...getInteractiveLegendConfig({
27976
+ highlightItem: highlightLineChartItem,
27977
+ unHighlightItems: resetLineChartHighlights,
27978
+ toggleDataVisibility: toggleLineBarDataVisibility
27979
+ }),
27793
27980
  ...getLegendDisplayOptions(definition),
27794
- ...getCustomLegendLabels(chartFontColor(definition.background), {
27981
+ ...getScatterPlotLegendLabels(chartFontColor(definition.background), {
27795
27982
  pointStyle: "circle",
27796
27983
  strokeStyle: definition.background || "#ffffff",
27797
27984
  lineWidth: 8
@@ -27812,7 +27999,11 @@ stores.inject(MyMetaStore, storeInstance);
27812
27999
  }
27813
28000
  function getComboChartLegend(definition, args) {
27814
28001
  return {
27815
- ...INTERACTIVE_LEGEND_CONFIG,
28002
+ ...getInteractiveLegendConfig({
28003
+ highlightItem: highlightComboChartItem,
28004
+ unHighlightItems: resetComboChartHighlights,
28005
+ toggleDataVisibility: toggleLineBarDataVisibility
28006
+ }),
27816
28007
  ...getLegendDisplayOptions(definition),
27817
28008
  ...getCustomLegendLabels(chartFontColor(definition.background), { lineWidth: 3 })
27818
28009
  };
@@ -27861,7 +28052,11 @@ stores.inject(MyMetaStore, storeInstance);
27861
28052
  const pointStyle = fill ? "rect" : "line";
27862
28053
  const lineWidth = fill ? 2 : 3;
27863
28054
  return {
27864
- ...INTERACTIVE_LEGEND_CONFIG,
28055
+ ...getInteractiveLegendConfig({
28056
+ highlightItem: highlightLineChartItem,
28057
+ unHighlightItems: resetLineChartHighlights,
28058
+ toggleDataVisibility: toggleLineBarDataVisibility
28059
+ }),
27865
28060
  ...getLegendDisplayOptions(definition),
27866
28061
  ...getCustomLegendLabels(chartFontColor(definition.background), {
27867
28062
  pointStyle,
@@ -27891,29 +28086,44 @@ stores.inject(MyMetaStore, storeInstance);
27891
28086
  }
27892
28087
  };
27893
28088
  }
27894
- const INTERACTIVE_LEGEND_CONFIG = {
27895
- onHover: (event) => {
27896
- const target = event.native?.target;
27897
- if (!target) return;
27898
- target.style.cursor = "pointer";
27899
- },
27900
- onLeave: (event) => {
27901
- const target = event.native?.target;
27902
- if (!target) return;
27903
- target.style.cursor = "default";
27904
- },
27905
- onClick: (event, legendItem, legend) => {
27906
- if (event.type !== "click") return;
27907
- const index = legendItem.datasetIndex;
27908
- if (!legend.legendItems || index === void 0) return;
27909
- if (legend.chart.isDatasetVisible(index)) legend.chart.hide(index);
27910
- else legend.chart.show(index);
27911
- event.native.preventDefault();
27912
- event.native.stopPropagation();
27913
- }
27914
- };
28089
+ function getInteractiveLegendConfig({ highlightItem, unHighlightItems, toggleDataVisibility }) {
28090
+ return {
28091
+ onHover: (event, item, legend) => {
28092
+ if (!item.hidden) {
28093
+ const datasets = legend.chart.data.datasets;
28094
+ highlightItem(item, datasets);
28095
+ legend.chart.update();
28096
+ }
28097
+ const target = event.native?.target;
28098
+ if (target && target instanceof HTMLElement) target.style.cursor = "pointer";
28099
+ },
28100
+ onLeave: (event, item, legend) => {
28101
+ if (!item.hidden) {
28102
+ const datasets = legend.chart.data.datasets;
28103
+ unHighlightItems(datasets);
28104
+ legend.chart.update();
28105
+ }
28106
+ const target = event.native?.target;
28107
+ if (target && target instanceof HTMLElement) target.style.cursor = "default";
28108
+ },
28109
+ onClick: (event, item, legend) => {
28110
+ if (event.type !== "click" || !legend.legendItems) return;
28111
+ if (legend.chart.options.animation) legend.chart.options.animation = false;
28112
+ toggleDataVisibility(legend.chart, item);
28113
+ if (!item.hidden) unHighlightItems(legend.chart.data.datasets);
28114
+ else highlightItem(item, legend.chart.data.datasets);
28115
+ legend.chart.update();
28116
+ event.native.preventDefault();
28117
+ event.native.stopPropagation();
28118
+ }
28119
+ };
28120
+ }
27915
28121
  const INTERACTIVE_LEGEND_CONFIG_FOR_BUBBLE_CHART = {
27916
- ...INTERACTIVE_LEGEND_CONFIG,
28122
+ ...getInteractiveLegendConfig({
28123
+ highlightItem: highlightLineChartItem,
28124
+ unHighlightItems: resetLineChartHighlights,
28125
+ toggleDataVisibility: toggleLineBarDataVisibility
28126
+ }),
27917
28127
  onClick: (event, legendItem, legend) => {
27918
28128
  if (event.type !== "click") return;
27919
28129
  const index = legendItem.datasetIndex;
@@ -27980,6 +28190,37 @@ stores.inject(MyMetaStore, storeInstance);
27980
28190
  filter: (legendItem, data) => true
27981
28191
  } };
27982
28192
  }
28193
+ function getScatterPlotLegendLabels(fontColor, legendLabelConfig) {
28194
+ return { labels: {
28195
+ color: fontColor,
28196
+ usePointStyle: true,
28197
+ generateLabels: (chart) => chart.data.datasets.map((dataset, index) => {
28198
+ if (isTrendLineAxis(dataset["xAxisID"])) return {
28199
+ text: truncateLabel(dataset.label),
28200
+ fontColor,
28201
+ strokeStyle: dataset.borderColor,
28202
+ hidden: !chart.isDatasetVisible(index),
28203
+ pointStyle: "line",
28204
+ datasetIndex: index,
28205
+ lineWidth: 3
28206
+ };
28207
+ return {
28208
+ text: truncateLabel(dataset.label),
28209
+ fontColor,
28210
+ strokeStyle: dataset.borderColor,
28211
+ fillStyle: dataset["pointBackgroundColor"],
28212
+ hidden: !chart.isDatasetVisible(index),
28213
+ pointStyle: "circle",
28214
+ datasetIndex: index,
28215
+ lineWidth: 8,
28216
+ ...legendLabelConfig
28217
+ };
28218
+ }).filter((label) => label.text),
28219
+ filter: (legendItem, data) => {
28220
+ return "datasetIndex" in legendItem ? !data.datasets[legendItem.datasetIndex].hidden : true;
28221
+ }
28222
+ } };
28223
+ }
27983
28224
 
27984
28225
  //#endregion
27985
28226
  //#region src/types/chart/sunburst_chart.ts
@@ -29619,11 +29860,12 @@ stores.inject(MyMetaStore, storeInstance);
29619
29860
  type: "range",
29620
29861
  dataSets: [],
29621
29862
  dataSetsHaveTitle: false,
29863
+ labelRange: context.auxiliaryRange,
29622
29864
  ...context.dataSource
29623
29865
  };
29624
29866
  },
29625
29867
  fromHierarchicalContextCreation(context) {
29626
- if (context.dataSource?.type !== "range" || context.hierarchicalDataSource?.type !== "range") return {
29868
+ if (context.dataSource?.type !== "range" || context.hierarchicalDataSource !== void 0 && context.hierarchicalDataSource.type !== "range") return {
29627
29869
  type: "range",
29628
29870
  dataSets: [],
29629
29871
  dataSetsHaveTitle: false
@@ -37615,7 +37857,7 @@ stores.inject(MyMetaStore, storeInstance);
37615
37857
  col,
37616
37858
  row
37617
37859
  };
37618
- if (pivotId === getters.getPivotIdFromPosition(position)) positions.push(position);
37860
+ if (getters.getPivotIdsFromPosition(position).includes(pivotId)) positions.push(position);
37619
37861
  }
37620
37862
  return positions;
37621
37863
  }
@@ -39689,6 +39931,9 @@ stores.inject(MyMetaStore, storeInstance);
39689
39931
  function getFirstPivotFunction(compiledFormula, getters) {
39690
39932
  return compiledFormula.getFunctionsFromTokens(PIVOT_FUNCTIONS, getters)[0];
39691
39933
  }
39934
+ function getPivotFunctions(compiledFormula, getters) {
39935
+ return compiledFormula.getFunctionsFromTokens(PIVOT_FUNCTIONS, getters);
39936
+ }
39692
39937
  /**
39693
39938
  * Parse a spreadsheet formula and detect the number of PIVOT functions that are
39694
39939
  * present in the given formula.
@@ -49292,6 +49537,7 @@ stores.inject(MyMetaStore, storeInstance);
49292
49537
  getPieChartLegend: () => getPieChartLegend,
49293
49538
  getPieChartTooltip: () => getPieChartTooltip,
49294
49539
  getPyramidChartData: () => getPyramidChartData,
49540
+ getPyramidChartLegend: () => getPyramidChartLegend,
49295
49541
  getPyramidChartScales: () => getPyramidChartScales,
49296
49542
  getPyramidChartShowValues: () => getPyramidChartShowValues,
49297
49543
  getPyramidChartTooltip: () => getPyramidChartTooltip,
@@ -57971,6 +58217,7 @@ stores.inject(MyMetaStore, storeInstance);
57971
58217
  "getFirstPivotFunction",
57972
58218
  "getPivotCellSortDirection",
57973
58219
  "getPivotIdFromPosition",
58220
+ "getPivotIdsFromPosition",
57974
58221
  "getPivotCellFromPosition",
57975
58222
  "generateNewCalculatedMeasureName",
57976
58223
  "isPivotUnused",
@@ -58030,37 +58277,52 @@ stores.inject(MyMetaStore, storeInstance);
58030
58277
  }
58031
58278
  }
58032
58279
  /**
58033
- * Get the id of the pivot at the given position. Returns undefined if there
58280
+ * Get the id of the first pivot in the formula at the given position. Returns undefined if there
58034
58281
  * is no pivot at this position
58035
58282
  */
58036
58283
  getPivotIdFromPosition(position) {
58284
+ return this.getPivotIdsFromPosition(position)[0];
58285
+ }
58286
+ /**
58287
+ * Get all of the ids of the pivot present in the formula at the given position.
58288
+ */
58289
+ getPivotIdsFromPosition(position) {
58037
58290
  const cell = this.getters.getCorrespondingFormulaCell(position);
58038
- if (cell && cell.isFormula) {
58039
- const pivotFunction = this.getFirstPivotFunction(position.sheetId, cell.compiledFormula);
58040
- if (pivotFunction) {
58041
- const pivotId = pivotFunction.args[0]?.toString();
58042
- return pivotId && this.getters.getPivotId(pivotId);
58043
- }
58044
- }
58291
+ if (cell && cell.isFormula) return this.getPivotIdsFromFormula(position.sheetId, cell.compiledFormula);
58292
+ return [];
58293
+ }
58294
+ getPivotIdsFromFormula(sheetId, formula) {
58295
+ return this.getPivotFunctions(sheetId, formula).map((pivotFunction) => {
58296
+ const pivotId = pivotFunction.args[0]?.toString();
58297
+ return pivotId && this.getters.getPivotId(pivotId);
58298
+ }).filter(isDefined);
58045
58299
  }
58046
58300
  isSpillPivotFormula(position) {
58047
58301
  const cell = this.getters.getCorrespondingFormulaCell(position);
58048
58302
  if (cell && cell.isFormula) return this.getFirstPivotFunction(position.sheetId, cell.compiledFormula)?.functionName === "PIVOT";
58049
58303
  return false;
58050
58304
  }
58051
- getFirstPivotFunction(sheetId, compiledFormula) {
58052
- const pivotFunction = getFirstPivotFunction(compiledFormula, this.getters);
58053
- if (!pivotFunction) return;
58054
- const { functionName, args } = pivotFunction;
58055
- return {
58056
- functionName,
58057
- args: args.map((argAst) => {
58305
+ getPivotFunctions(sheetId, formula) {
58306
+ const pivotFunctions = getPivotFunctions(formula, this.getters);
58307
+ if (!pivotFunctions.length) return [];
58308
+ const evaluatedPivotFunctions = [];
58309
+ for (const pivotFunction of pivotFunctions) {
58310
+ const { functionName, args } = pivotFunction;
58311
+ const evaluatedArgs = args.map((argAst) => {
58058
58312
  if (argAst.type === "EMPTY") return;
58059
58313
  else if (argAst.type === "STRING" || argAst.type === "BOOLEAN" || argAst.type === "NUMBER") return argAst.value;
58060
58314
  const argsString = astToFormula(argAst);
58061
58315
  return this.getters.evaluateFormula(sheetId, argsString);
58062
- })
58063
- };
58316
+ });
58317
+ evaluatedPivotFunctions.push({
58318
+ functionName,
58319
+ args: evaluatedArgs
58320
+ });
58321
+ }
58322
+ return evaluatedPivotFunctions;
58323
+ }
58324
+ getFirstPivotFunction(sheetId, formula) {
58325
+ return this.getPivotFunctions(sheetId, formula)[0];
58064
58326
  }
58065
58327
  /**
58066
58328
  * Returns the domain args of a pivot formula from a position.
@@ -58166,8 +58428,8 @@ stores.inject(MyMetaStore, storeInstance);
58166
58428
  const unusedPivots = new Set(this.getters.getPivotIds());
58167
58429
  for (const sheetId of this.getters.getSheetIds()) for (const cell of this.getters.getCells(sheetId)) {
58168
58430
  const position = this.getters.getCellPosition(cell.id);
58169
- const pivotId = this.getPivotIdFromPosition(position);
58170
- if (pivotId) {
58431
+ const pivotIds = this.getPivotIdsFromPosition(position);
58432
+ for (const pivotId of pivotIds) {
58171
58433
  unusedPivots.delete(pivotId);
58172
58434
  if (!unusedPivots.size) {
58173
58435
  this.unusedPivotsInFormulas = [];
@@ -58175,6 +58437,21 @@ stores.inject(MyMetaStore, storeInstance);
58175
58437
  }
58176
58438
  }
58177
58439
  }
58440
+ for (const pivotId of this.getters.getPivotIds()) {
58441
+ const pivot = this.getters.getPivot(pivotId);
58442
+ for (const measure of pivot.definition.measures) if (measure.computedBy) {
58443
+ const { sheetId } = measure.computedBy;
58444
+ const formula = this.getters.getMeasureCompiledFormula(pivotId, measure);
58445
+ const relatedPivotIds = this.getPivotIdsFromFormula(sheetId, formula);
58446
+ for (const relatedPivotId of relatedPivotIds) {
58447
+ unusedPivots.delete(relatedPivotId);
58448
+ if (!unusedPivots.size) {
58449
+ this.unusedPivotsInFormulas = [];
58450
+ return [];
58451
+ }
58452
+ }
58453
+ }
58454
+ }
58178
58455
  this.unusedPivotsInFormulas = [...unusedPivots];
58179
58456
  return this.unusedPivotsInFormulas;
58180
58457
  }
@@ -62851,12 +63128,13 @@ stores.inject(MyMetaStore, storeInstance);
62851
63128
  const chartId = this.getChartIdFromFigureId(chartFigureId);
62852
63129
  if (!chartId) return;
62853
63130
  const carousel = this.getters.getCarousel(figureId);
63131
+ const newItem = {
63132
+ type: "chart",
63133
+ chartId
63134
+ };
62854
63135
  const definition = {
62855
63136
  ...carousel,
62856
- items: [...carousel.items, {
62857
- type: "chart",
62858
- chartId
62859
- }]
63137
+ items: [...carousel.items, newItem]
62860
63138
  };
62861
63139
  this.dispatch("UPDATE_CAROUSEL", {
62862
63140
  sheetId,
@@ -62873,6 +63151,11 @@ stores.inject(MyMetaStore, storeInstance);
62873
63151
  sheetId,
62874
63152
  figureId: chartFigureId
62875
63153
  });
63154
+ this.dispatch("UPDATE_CAROUSEL_ACTIVE_ITEM", {
63155
+ figureId,
63156
+ sheetId,
63157
+ item: newItem
63158
+ });
62876
63159
  }
62877
63160
  duplicateCarouselChart({ carouselId, chartId, sheetId, duplicatedChartId }) {
62878
63161
  const chart = this.getters.getChart(chartId);
@@ -77214,7 +77497,7 @@ stores.inject(MyMetaStore, storeInstance);
77214
77497
  scales: getPyramidChartScales(definition, chartData),
77215
77498
  plugins: {
77216
77499
  title: getChartTitle(definition, getters),
77217
- legend: getBarChartLegend(definition, chartData),
77500
+ legend: getPyramidChartLegend(definition, chartData),
77218
77501
  tooltip: getPyramidChartTooltip(definition, chartData),
77219
77502
  chartShowValuesPlugin: getPyramidChartShowValues(definition, chartData),
77220
77503
  background: { color: chartData.background }
@@ -85691,7 +85974,8 @@ stores.inject(MyMetaStore, storeInstance);
85691
85974
  FIGURE_ID_SPLITTER: "??",
85692
85975
  GRID_ICON_EDGE_LENGTH: 17,
85693
85976
  GRID_ICON_MARGIN: 2,
85694
- CHART_TYPES
85977
+ CHART_TYPES,
85978
+ DARK_MODE_FILTER_STRING
85695
85979
  };
85696
85980
  const chartHelpers = {
85697
85981
  ...helpers_index_exports$1,
@@ -85792,8 +86076,8 @@ exports.stores = stores;
85792
86076
  exports.tokenColors = tokenColors;
85793
86077
  exports.tokenize = tokenize;
85794
86078
 
85795
- __info__.version = "19.4.0-alpha.6";
85796
- __info__.date = "2026-05-15T07:08:41.621Z";
85797
- __info__.hash = "0207b9d";
86079
+ __info__.version = "19.4.0-alpha.7";
86080
+ __info__.date = "2026-05-20T14:40:27.236Z";
86081
+ __info__.hash = "7ee566f";
85798
86082
 
85799
86083
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);