@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
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
@@ -140,17 +140,234 @@ if (typeof globalThis.document === "undefined") {
140
140
  };
141
141
  }
142
142
 
143
+ //#endregion
144
+ //#region src/components/helpers/dom_helpers.ts
145
+ const macRegex = /Mac/i;
146
+ let zoomCssDoesNotAffectBoundingRect = false;
147
+ function defineZoomCssImpactOnBoundingRect() {
148
+ const div = document.createElement("div");
149
+ div.setAttribute("style", `width:10px;height:1px;zoom:2;position:absolute;z-index:-10000`);
150
+ document.body.appendChild(div);
151
+ zoomCssDoesNotAffectBoundingRect = div.getBoundingClientRect().width !== 20;
152
+ document.body.removeChild(div);
153
+ }
154
+ (0, _odoo_owl.whenReady)(defineZoomCssImpactOnBoundingRect);
155
+ const MODIFIER_KEYS = [
156
+ "Shift",
157
+ "Control",
158
+ "Alt",
159
+ "Meta"
160
+ ];
161
+ /**
162
+ * Return true if the event was triggered from
163
+ * a child element.
164
+ */
165
+ function isChildEvent(parent, ev) {
166
+ if (!parent) return false;
167
+ return !!ev.target && parent.contains(ev.target);
168
+ }
169
+ function gridOverlayPosition(zoom = 1) {
170
+ const spreadsheetElement = document.querySelector(".o-grid-overlay");
171
+ const result = spreadsheetElement && zoomCorrectedElementRect(spreadsheetElement, zoom);
172
+ if (!result) throw new Error("Can't find spreadsheet position");
173
+ return result;
174
+ }
175
+ function zoomCorrectedElementRect(el, zoomLevel) {
176
+ const zoomedElement = el.closest(".o-zoomable");
177
+ let targetEl;
178
+ let zoom = 1;
179
+ if (zoomedElement) {
180
+ targetEl = zoomedElement;
181
+ zoom = zoomCssDoesNotAffectBoundingRect ? zoomLevel : 1;
182
+ } else targetEl = el;
183
+ const rect = targetEl.getBoundingClientRect();
184
+ return {
185
+ x: rect.x * zoom,
186
+ y: rect.y * zoom,
187
+ width: rect.width * zoom,
188
+ height: rect.height * zoom
189
+ };
190
+ }
191
+ function getRefBoundingRect(ref) {
192
+ if (!ref.el) return {
193
+ x: 0,
194
+ y: 0,
195
+ width: 0,
196
+ height: 0
197
+ };
198
+ return getBoundingRectAsPOJO(ref.el);
199
+ }
200
+ function getBoundingRectAsPOJO(el) {
201
+ const rect = el.getBoundingClientRect();
202
+ return {
203
+ x: rect.x,
204
+ y: rect.y,
205
+ width: rect.width,
206
+ height: rect.height
207
+ };
208
+ }
209
+ /**
210
+ * Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
211
+ */
212
+ function* iterateChildren(el) {
213
+ yield el;
214
+ if (el.hasChildNodes()) for (const child of el.childNodes) yield* iterateChildren(child);
215
+ }
216
+ function getOpenedMenus() {
217
+ return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
218
+ }
219
+ function getCurrentSelection(el) {
220
+ const { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
221
+ return {
222
+ start: findSelectionIndex(el, startElement, startSelectionOffset),
223
+ end: findSelectionIndex(el, endElement, endSelectionOffset)
224
+ };
225
+ }
226
+ function getStartAndEndSelection(el) {
227
+ const selection = document.getSelection();
228
+ return {
229
+ startElement: selection.anchorNode || el,
230
+ startSelectionOffset: selection.anchorOffset,
231
+ endElement: selection.focusNode || el,
232
+ endSelectionOffset: selection.focusOffset
233
+ };
234
+ }
235
+ /**
236
+ * Computes the text 'index' inside this.el based on the currently selected node and its offset.
237
+ * The selected node is either a Text node or an Element node.
238
+ *
239
+ * case 1 -Text node:
240
+ * the offset is the number of characters from the start of the node. We have to add this offset to the
241
+ * content length of all previous nodes.
242
+ *
243
+ * case 2 - Element node:
244
+ * the offset is the number of child nodes before the selected node. We have to add the content length of
245
+ * all the nodes prior to the selected node as well as the content of the child node before the offset.
246
+ *
247
+ * See the MDN documentation for more details.
248
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
249
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
250
+ *
251
+ */
252
+ function findSelectionIndex(el, nodeToFind, nodeOffset) {
253
+ let usedCharacters = 0;
254
+ const it = iterateChildren(el);
255
+ let current = it.next();
256
+ let isFirstParagraph = true;
257
+ while (!current.done && current.value !== nodeToFind) {
258
+ if (!current.value.hasChildNodes()) {
259
+ if (current.value.textContent) usedCharacters += current.value.textContent.length;
260
+ }
261
+ if (current.value.nodeName === "P" || current.value.nodeName === "DIV" && current.value !== el) if (isFirstParagraph) isFirstParagraph = false;
262
+ else usedCharacters++;
263
+ current = it.next();
264
+ }
265
+ if (current.value !== nodeToFind)
266
+ /** This situation can happen if the code is called while the selection is not currently on the element.
267
+ * In this case, we return 0 because we don't know the size of the text before the selection.
268
+ *
269
+ * A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
270
+ */
271
+ return 0;
272
+ else if (!current.value.hasChildNodes()) usedCharacters += nodeOffset;
273
+ else {
274
+ const children = [...current.value.childNodes].slice(0, nodeOffset);
275
+ usedCharacters += children.reduce((acc, child, index) => {
276
+ if (child.textContent !== null) {
277
+ let chars = child.textContent.length;
278
+ if (child.nodeName === "P" && index !== children.length - 1) chars++;
279
+ return acc + chars;
280
+ } else return acc;
281
+ }, 0);
282
+ }
283
+ if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") usedCharacters++;
284
+ return usedCharacters;
285
+ }
286
+ const letterRegex = /^[a-zA-Z]$/;
287
+ /**
288
+ * Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
289
+ *
290
+ * @argument ev - The keyboard event to transform
291
+ * @argument mode - Use either ev.key of ev.code to get the string shortcut
292
+ *
293
+ * @example
294
+ * event : { ctrlKey: true, key: "a" } => "Ctrl+A"
295
+ * event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
296
+ */
297
+ function keyboardEventToShortcutString(ev, mode = "key") {
298
+ let keyDownString = "";
299
+ if (!MODIFIER_KEYS.includes(ev.key)) {
300
+ if (isCtrlKey(ev)) keyDownString += "Ctrl+";
301
+ if (ev.altKey) keyDownString += "Alt+";
302
+ if (ev.shiftKey) keyDownString += "Shift+";
303
+ }
304
+ const key = mode === "key" ? ev.key : ev.code;
305
+ keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
306
+ return keyDownString;
307
+ }
308
+ function isMacOS() {
309
+ return Boolean(macRegex.test(navigator.userAgent));
310
+ }
311
+ /**
312
+ * @param {KeyboardEvent | MouseEvent} ev
313
+ * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
314
+ * On Mac, this is the "meta" or "command" key.
315
+ */
316
+ function isCtrlKey(ev) {
317
+ return isMacOS() || isIOS() ? ev.metaKey : ev.ctrlKey;
318
+ }
319
+ /**
320
+ * @param {MouseEvent} ev - The mouse event.
321
+ * @returns {boolean} Returns true if the event was triggered by a middle-click
322
+ * or a Ctrl + Click (Cmd + Click on Mac).
323
+ */
324
+ function isMiddleClickOrCtrlClick(ev) {
325
+ return ev.button === 1 || isCtrlKey(ev) && ev.button === 0;
326
+ }
327
+ function downloadFile(dataUrl, fileName) {
328
+ const a = document.createElement("a");
329
+ a.href = dataUrl;
330
+ a.download = fileName;
331
+ document.body.appendChild(a);
332
+ a.click();
333
+ document.body.removeChild(a);
334
+ }
335
+ /**
336
+ * Detects if the current browser is Firefox
337
+ */
338
+ function isBrowserFirefox() {
339
+ return /Firefox/i.test(navigator.userAgent);
340
+ }
341
+ function maxTouchPoints() {
342
+ return navigator.maxTouchPoints || 1;
343
+ }
344
+ function isAndroid() {
345
+ return /Android/i.test(navigator.userAgent);
346
+ }
347
+ function isIOS() {
348
+ return /(iPad|iPhone|iPod)/i.test(navigator.userAgent) || navigator.platform === "MacIntel" && maxTouchPoints() > 1;
349
+ }
350
+ function isOtherMobileOS() {
351
+ return /(webOS|BlackBerry|Windows Phone)/i.test(navigator.userAgent);
352
+ }
353
+ function isMobileOS() {
354
+ return isAndroid() || isIOS() || isOtherMobileOS();
355
+ }
356
+
143
357
  //#endregion
144
358
  //#region src/actions/action.ts
145
359
  function createActions(menuItems) {
146
360
  return menuItems.map(createAction).sort((a, b) => a.sequence - b.sequence);
147
361
  }
362
+ function adaptShortcutMacOs(shortcut) {
363
+ return shortcut.replace("Ctrl", "⌘").replace("Alt", "⌃");
364
+ }
148
365
  let nextItemId = 1;
149
366
  function createAction(item) {
150
367
  const name = item.name;
151
368
  const children = item.children;
152
369
  const description = item.description;
153
- const shortcut = item.shortcut;
370
+ const shortcut = item.shortcut && isMacOS() ? adaptShortcutMacOs(item.shortcut) : item.shortcut;
154
371
  const icon = item.icon;
155
372
  const secondaryIcon = item.secondaryIcon;
156
373
  const itemId = item.id || nextItemId++;
@@ -5032,36 +5249,77 @@ function applyVectorization(context, descr, args, acceptToVectorize = void 0) {
5032
5249
  } else args[i] = arg[0][0];
5033
5250
  }
5034
5251
  }
5035
- if (countVectorizedCol === 1 && countVectorizedRow === 1) return errorHandlingCompute(descr, context, args);
5036
- const getArgOffset = (i, j) => args.map((arg, index) => {
5037
- switch (vectorArgsType?.[index]) {
5038
- case "matrix": return arg[i][j];
5039
- case "horizontal": return arg[i][0];
5040
- case "vertical": return arg[0][j];
5041
- case void 0: return arg;
5252
+ const argsToFocus = argTargeting(descr, args.length);
5253
+ const argDefinitions = new Array(args.length);
5254
+ for (let k = 0; k < args.length; k++) argDefinitions[k] = descr.args[argsToFocus[k].index];
5255
+ if (countVectorizedCol === 1 && countVectorizedRow === 1) return errorHandlingCompute(descr, context, args, argDefinitions);
5256
+ const argsBuffer = new Array(args.length);
5257
+ const argGetters = [];
5258
+ const vectorizedIndices = [];
5259
+ for (let k = 0; k < args.length; k++) {
5260
+ const arg = args[k];
5261
+ switch (vectorArgsType?.[k]) {
5262
+ case "matrix":
5263
+ argGetters.push((i, j) => arg[i][j]);
5264
+ vectorizedIndices.push(k);
5265
+ break;
5266
+ case "horizontal":
5267
+ argGetters.push((i) => arg[i][0]);
5268
+ vectorizedIndices.push(k);
5269
+ break;
5270
+ case "vertical":
5271
+ argGetters.push((_i, j) => arg[0][j]);
5272
+ vectorizedIndices.push(k);
5273
+ break;
5274
+ case void 0:
5275
+ argsBuffer[k] = arg;
5276
+ break;
5042
5277
  }
5043
- });
5044
- return generateMatrix(countVectorizedCol, countVectorizedRow, (col, row) => {
5045
- if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) return new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
5046
- const singleCellComputeResult = errorHandlingCompute(descr, context, getArgOffset(col, row));
5047
- return isMatrix(singleCellComputeResult) ? singleCellComputeResult[0][0] : singleCellComputeResult;
5048
- });
5278
+ }
5279
+ const nbVectorized = vectorizedIndices.length;
5280
+ const result = new Array(countVectorizedCol);
5281
+ for (let col = 0; col < countVectorizedCol; col++) {
5282
+ const column = new Array(countVectorizedRow);
5283
+ result[col] = column;
5284
+ for (let row = 0; row < countVectorizedRow; row++) {
5285
+ if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) {
5286
+ column[row] = new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
5287
+ continue;
5288
+ }
5289
+ for (let k = 0; k < nbVectorized; k++) argsBuffer[vectorizedIndices[k]] = argGetters[k](col, row);
5290
+ const singleCellComputeResult = errorHandlingCompute(descr, context, argsBuffer, argDefinitions);
5291
+ column[row] = isMatrix(singleCellComputeResult) ? singleCellComputeResult[0][0] : singleCellComputeResult;
5292
+ }
5293
+ }
5294
+ return result;
5049
5295
  }
5050
5296
  function computeFunctionToObject(descr, context, args) {
5051
5297
  if (context.debug) {
5052
5298
  debugger;
5053
5299
  context.debug = false;
5054
5300
  }
5055
- const result = descr.compute.apply(context, args);
5301
+ const compute = descr.compute;
5302
+ let result;
5303
+ switch (args.length) {
5304
+ case 1:
5305
+ result = compute.call(context, args[0]);
5306
+ break;
5307
+ case 2:
5308
+ result = compute.call(context, args[0], args[1]);
5309
+ break;
5310
+ case 3:
5311
+ result = compute.call(context, args[0], args[1], args[2]);
5312
+ break;
5313
+ default: result = compute.apply(context, args);
5314
+ }
5056
5315
  if (!isMatrix(result)) return isFunctionResultObject(result) ? result : { value: result };
5057
5316
  if (isFunctionResultObject(result[0][0])) return result;
5058
5317
  return matrixMap(result, (row) => ({ value: row }));
5059
5318
  }
5060
- function errorHandlingCompute(descr, context, args) {
5061
- const argsToFocus = argTargeting(descr, args.length);
5319
+ function errorHandlingCompute(descr, context, args, argDefinitions) {
5062
5320
  for (let i = 0; i < args.length; i++) {
5063
5321
  const arg = args[i];
5064
- if (!descr.args[argsToFocus[i].index].acceptErrors && !isMatrix(arg) && isEvaluationError(arg?.value)) return arg;
5322
+ if (!argDefinitions[i].acceptErrors && !isMatrix(arg) && isEvaluationError(arg?.value)) return arg;
5065
5323
  }
5066
5324
  try {
5067
5325
  return computeFunctionToObject(descr, context, args);
@@ -6260,8 +6518,13 @@ function getApplyRangeChangeAddColRow(cmd) {
6260
6518
  changeType: "NONE",
6261
6519
  range
6262
6520
  };
6521
+ const isUnboundedAtEnd = range.unboundedZone[end] === void 0;
6522
+ if (isUnboundedAtEnd && !range.unboundedZone.hasHeader) return {
6523
+ changeType: "RESIZE",
6524
+ range: createAdaptedRange(range, dimension, "RESIZE", cmd.quantity)
6525
+ };
6263
6526
  if (cmd.position === "after") {
6264
- if (range.zone[start] <= cmd.base && cmd.base < range.zone[end]) return {
6527
+ if (range.zone[start] <= cmd.base && (cmd.base < range.zone[end] || isUnboundedAtEnd)) return {
6265
6528
  changeType: "RESIZE",
6266
6529
  range: createAdaptedRange(range, dimension, "RESIZE", cmd.quantity)
6267
6530
  };
@@ -6507,40 +6770,65 @@ function getRange(sheetXC, sheetId) {
6507
6770
 
6508
6771
  //#endregion
6509
6772
  //#region src/formulas/code_builder.ts
6773
+ var JsString = class extends String {};
6774
+ /**
6775
+ * Creates a JsString from a raw string, bypassing the template string interpolation checks.
6776
+ * This can lead to security vulnerabilities if the string is not trusted!
6777
+ */
6778
+ function dangerouslyCreateJsStr(trustedStr) {
6779
+ return new JsString(trustedStr);
6780
+ }
6781
+ /**
6782
+ * Creates a JsString from a template string, ensuring that all interpolated values are safe.
6783
+ */
6784
+ function jsStr(strings, ...values) {
6785
+ let str = "";
6786
+ for (let i = 0; i < strings.length; i++) {
6787
+ const value = values[i];
6788
+ if (i >= values.length) str += strings[i];
6789
+ else if (isSafeJsValue(value)) str += strings[i] + value;
6790
+ else if (Array.isArray(value) && value.every(isSafeJsValue)) str += strings[i] + value.map((v) => v.toString()).join(",");
6791
+ else throw new Error(`Invalid interpolated value at index ${i}: ${value}`);
6792
+ }
6793
+ return dangerouslyCreateJsStr(str);
6794
+ }
6795
+ function isSafeJsValue(value) {
6796
+ return value instanceof JsString || typeof value === "number" || typeof value === "boolean";
6797
+ }
6510
6798
  var FunctionCodeBuilder = class {
6511
6799
  scope;
6512
- code = "";
6800
+ code = [];
6513
6801
  constructor(scope = new Scope()) {
6514
6802
  this.scope = scope;
6515
6803
  }
6516
6804
  append(...lines) {
6517
- this.code += lines.map((line) => line.toString()).join("\n") + "\n";
6805
+ for (const line of lines) if (line instanceof FunctionCodeImpl) this.code.push(...line.code);
6806
+ else if (line instanceof JsString) this.code.push(line);
6807
+ else throw new Error(`Invalid line: ${line}`);
6518
6808
  }
6519
6809
  return(expression) {
6810
+ if (!isSafeJsValue(expression)) throw new Error(`Expected JsString, got ${expression}`);
6520
6811
  return new FunctionCodeImpl(this.scope, this.code, expression);
6521
6812
  }
6522
6813
  toString() {
6523
- return indentCode(this.code);
6814
+ return indentCode(this.code.join("\n"));
6524
6815
  }
6525
6816
  };
6526
6817
  var FunctionCodeImpl = class {
6527
6818
  scope;
6528
- returnExpression;
6529
6819
  code;
6820
+ returnExpression;
6530
6821
  constructor(scope, code, returnExpression) {
6531
6822
  this.scope = scope;
6823
+ this.code = code;
6532
6824
  this.returnExpression = returnExpression;
6533
- this.code = indentCode(code);
6534
- }
6535
- toString() {
6536
- return this.code;
6537
6825
  }
6538
6826
  assignResultToVariable() {
6539
6827
  if (this.scope.isAlreadyDeclared(this.returnExpression)) return this;
6540
6828
  const variableName = this.scope.nextVariableName();
6541
6829
  const code = new FunctionCodeBuilder(this.scope);
6542
- code.append(this.code);
6543
- code.append(`const ${variableName} = ${this.returnExpression};`);
6830
+ code.append(...this.code);
6831
+ code.append(jsStr`const ${variableName} = ${this.returnExpression};`);
6544
6832
  return code.return(variableName);
6545
6833
  }
6546
6834
  };
@@ -6548,12 +6836,12 @@ var Scope = class {
6548
6836
  nextId = 1;
6549
6837
  declaredVariables = /* @__PURE__ */ new Set();
6550
6838
  nextVariableName() {
6551
- const name = `_${this.nextId++}`;
6552
- this.declaredVariables.add(name);
6839
+ const name = jsStr`_${this.nextId++}`;
6840
+ this.declaredVariables.add(name.toString());
6553
6841
  return name;
6554
6842
  }
6555
6843
  isAlreadyDeclared(name) {
6556
- return this.declaredVariables.has(name);
6844
+ return this.declaredVariables.has(name.toString());
6557
6845
  }
6558
6846
  };
6559
6847
  /**
@@ -6770,6 +7058,7 @@ function compileTokens(tokens) {
6770
7058
  try {
6771
7059
  return compileTokensOrThrow(tokens);
6772
7060
  } catch (error) {
7061
+ if (!(error instanceof EvaluationError)) throw error;
6773
7062
  return {
6774
7063
  tokens,
6775
7064
  literalValues: {
@@ -6800,7 +7089,7 @@ function compileTokensOrThrow(tokens) {
6800
7089
  const compiledAST = compileAST(ast);
6801
7090
  const code = new FunctionCodeBuilder();
6802
7091
  code.append(compiledAST);
6803
- code.append(`return ${compiledAST.returnExpression};`);
7092
+ code.append(jsStr`return ${compiledAST.returnExpression};`);
6804
7093
  functionCache[cacheKey] = new Function("deps", "ref", "range", "getSymbolValue", "ctx", code.toString());
6805
7094
  /**
6806
7095
  * This function compile the function arguments. It is mostly straightforward,
@@ -6833,19 +7122,21 @@ function compileTokensOrThrow(tokens) {
6833
7122
  function compileAST(ast, hasRange = false) {
6834
7123
  const code = new FunctionCodeBuilder(scope);
6835
7124
  if (ast.debug) {
6836
- code.append("debugger;");
6837
- code.append(`ctx["debug"] = true;`);
7125
+ code.append(jsStr`debugger;`);
7126
+ code.append(jsStr`ctx["debug"] = true;`);
6838
7127
  }
6839
7128
  switch (ast.type) {
6840
- case "BOOLEAN": return code.return(`{ value: ${ast.value} }`);
6841
- case "NUMBER": return code.return(`this.literalValues.numbers[${numberCount++}]`);
6842
- case "STRING": return code.return(`this.literalValues.strings[${stringCount++}]`);
6843
- case "REFERENCE": return code.return(`${ast.value.includes(":") || hasRange ? `range` : `ref`}(deps[${dependencyCount++}])`);
7129
+ case "BOOLEAN": return code.return(jsStr`{ value: ${ast.value} }`);
7130
+ case "NUMBER": return code.return(jsStr`this.literalValues.numbers[${numberCount++}]`);
7131
+ case "STRING": return code.return(jsStr`this.literalValues.strings[${stringCount++}]`);
7132
+ case "REFERENCE": return code.return(jsStr`${ast.value.includes(":") || hasRange ? jsStr`range` : jsStr`ref`}(deps[${dependencyCount++}])`);
6844
7133
  case "FUNCALL":
6845
7134
  const args = compileFunctionArgs(ast).map((arg) => arg.assignResultToVariable());
6846
7135
  code.append(...args);
6847
7136
  const fnName = ast.value.toUpperCase();
6848
- return code.return(`ctx['${fnName}'](${args.map((arg) => arg.returnExpression)})`);
7137
+ if (!Object.hasOwn(functions$1, fnName)) throw new Error(`Unknown function: "${fnName}"`);
7138
+ const jsFnName = dangerouslyCreateJsStr(fnName);
7139
+ return code.return(jsStr`ctx['${jsFnName}'](${args.map((arg) => arg.returnExpression)})`);
6849
7140
  case "ARRAY": return compileAST({
6850
7141
  type: "FUNCALL",
6851
7142
  value: "ARRAY.LITERAL",
@@ -6860,23 +7151,25 @@ function compileTokensOrThrow(tokens) {
6860
7151
  tokenEndIndex: 0
6861
7152
  });
6862
7153
  case "UNARY_OPERATION": {
6863
- const fnName = UNARY_OPERATOR_MAP[ast.value];
7154
+ if (!Object.hasOwn(UNARY_OPERATOR_MAP, ast.value)) throw new Error(`Unknown operator: "${ast.value}"`);
7155
+ const fnName = dangerouslyCreateJsStr(UNARY_OPERATOR_MAP[ast.value]);
6864
7156
  const operand = compileAST(ast.operand, ast.value === "#").assignResultToVariable();
6865
7157
  code.append(operand);
6866
- return code.return(`ctx['${fnName}'](${operand.returnExpression})`);
7158
+ return code.return(jsStr`ctx['${fnName}'](${operand.returnExpression})`);
6867
7159
  }
6868
7160
  case "BIN_OPERATION": {
6869
- const fnName = OPERATOR_MAP[ast.value];
7161
+ if (!Object.hasOwn(OPERATOR_MAP, ast.value)) throw new Error(`Unknown operator: "${ast.value}"`);
7162
+ const fnName = dangerouslyCreateJsStr(OPERATOR_MAP[ast.value]);
6870
7163
  const left = compileAST(ast.left, false).assignResultToVariable();
6871
7164
  const right = compileAST(ast.right, false).assignResultToVariable();
6872
7165
  code.append(left);
6873
7166
  code.append(right);
6874
- return code.return(`ctx['${fnName}'](${left.returnExpression}, ${right.returnExpression})`);
7167
+ return code.return(jsStr`ctx['${fnName}'](${left.returnExpression}, ${right.returnExpression})`);
6875
7168
  }
6876
7169
  case "SYMBOL":
6877
7170
  const symbolIndex = symbols.indexOf(ast.value);
6878
- return code.return(`getSymbolValue(this.symbols[${symbolIndex}], ${hasRange})`);
6879
- case "EMPTY": return code.return("undefined");
7171
+ return code.return(jsStr`getSymbolValue(this.symbols[${symbolIndex}], ${hasRange})`);
7172
+ case "EMPTY": return code.return(jsStr`undefined`);
6880
7173
  }
6881
7174
  }
6882
7175
  }
@@ -11553,220 +11846,6 @@ var ScorecardChartConfigBuilder = class {
11553
11846
  }
11554
11847
  };
11555
11848
 
11556
- //#endregion
11557
- //#region src/components/helpers/dom_helpers.ts
11558
- const macRegex = /Mac/i;
11559
- let zoomCssDoesNotAffectBoundingRect = false;
11560
- function defineZoomCssImpactOnBoundingRect() {
11561
- const div = document.createElement("div");
11562
- div.setAttribute("style", `width:10px;height:1px;zoom:2;position:absolute;z-index:-10000`);
11563
- document.body.appendChild(div);
11564
- zoomCssDoesNotAffectBoundingRect = div.getBoundingClientRect().width !== 20;
11565
- document.body.removeChild(div);
11566
- }
11567
- (0, _odoo_owl.whenReady)(defineZoomCssImpactOnBoundingRect);
11568
- const MODIFIER_KEYS = [
11569
- "Shift",
11570
- "Control",
11571
- "Alt",
11572
- "Meta"
11573
- ];
11574
- /**
11575
- * Return true if the event was triggered from
11576
- * a child element.
11577
- */
11578
- function isChildEvent(parent, ev) {
11579
- if (!parent) return false;
11580
- return !!ev.target && parent.contains(ev.target);
11581
- }
11582
- function gridOverlayPosition(zoom = 1) {
11583
- const spreadsheetElement = document.querySelector(".o-grid-overlay");
11584
- const result = spreadsheetElement && zoomCorrectedElementRect(spreadsheetElement, zoom);
11585
- if (!result) throw new Error("Can't find spreadsheet position");
11586
- return result;
11587
- }
11588
- function zoomCorrectedElementRect(el, zoomLevel) {
11589
- const zoomedElement = el.closest(".o-zoomable");
11590
- let targetEl;
11591
- let zoom = 1;
11592
- if (zoomedElement) {
11593
- targetEl = zoomedElement;
11594
- zoom = zoomCssDoesNotAffectBoundingRect ? zoomLevel : 1;
11595
- } else targetEl = el;
11596
- const rect = targetEl.getBoundingClientRect();
11597
- return {
11598
- x: rect.x * zoom,
11599
- y: rect.y * zoom,
11600
- width: rect.width * zoom,
11601
- height: rect.height * zoom
11602
- };
11603
- }
11604
- function getRefBoundingRect(ref) {
11605
- if (!ref.el) return {
11606
- x: 0,
11607
- y: 0,
11608
- width: 0,
11609
- height: 0
11610
- };
11611
- return getBoundingRectAsPOJO(ref.el);
11612
- }
11613
- function getBoundingRectAsPOJO(el) {
11614
- const rect = el.getBoundingClientRect();
11615
- return {
11616
- x: rect.x,
11617
- y: rect.y,
11618
- width: rect.width,
11619
- height: rect.height
11620
- };
11621
- }
11622
- /**
11623
- * Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
11624
- */
11625
- function* iterateChildren(el) {
11626
- yield el;
11627
- if (el.hasChildNodes()) for (const child of el.childNodes) yield* iterateChildren(child);
11628
- }
11629
- function getOpenedMenus() {
11630
- return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
11631
- }
11632
- function getCurrentSelection(el) {
11633
- const { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
11634
- return {
11635
- start: findSelectionIndex(el, startElement, startSelectionOffset),
11636
- end: findSelectionIndex(el, endElement, endSelectionOffset)
11637
- };
11638
- }
11639
- function getStartAndEndSelection(el) {
11640
- const selection = document.getSelection();
11641
- return {
11642
- startElement: selection.anchorNode || el,
11643
- startSelectionOffset: selection.anchorOffset,
11644
- endElement: selection.focusNode || el,
11645
- endSelectionOffset: selection.focusOffset
11646
- };
11647
- }
11648
- /**
11649
- * Computes the text 'index' inside this.el based on the currently selected node and its offset.
11650
- * The selected node is either a Text node or an Element node.
11651
- *
11652
- * case 1 -Text node:
11653
- * the offset is the number of characters from the start of the node. We have to add this offset to the
11654
- * content length of all previous nodes.
11655
- *
11656
- * case 2 - Element node:
11657
- * the offset is the number of child nodes before the selected node. We have to add the content length of
11658
- * all the nodes prior to the selected node as well as the content of the child node before the offset.
11659
- *
11660
- * See the MDN documentation for more details.
11661
- * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
11662
- * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
11663
- *
11664
- */
11665
- function findSelectionIndex(el, nodeToFind, nodeOffset) {
11666
- let usedCharacters = 0;
11667
- const it = iterateChildren(el);
11668
- let current = it.next();
11669
- let isFirstParagraph = true;
11670
- while (!current.done && current.value !== nodeToFind) {
11671
- if (!current.value.hasChildNodes()) {
11672
- if (current.value.textContent) usedCharacters += current.value.textContent.length;
11673
- }
11674
- if (current.value.nodeName === "P" || current.value.nodeName === "DIV" && current.value !== el) if (isFirstParagraph) isFirstParagraph = false;
11675
- else usedCharacters++;
11676
- current = it.next();
11677
- }
11678
- if (current.value !== nodeToFind)
11679
- /** This situation can happen if the code is called while the selection is not currently on the element.
11680
- * In this case, we return 0 because we don't know the size of the text before the selection.
11681
- *
11682
- * A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
11683
- */
11684
- return 0;
11685
- else if (!current.value.hasChildNodes()) usedCharacters += nodeOffset;
11686
- else {
11687
- const children = [...current.value.childNodes].slice(0, nodeOffset);
11688
- usedCharacters += children.reduce((acc, child, index) => {
11689
- if (child.textContent !== null) {
11690
- let chars = child.textContent.length;
11691
- if (child.nodeName === "P" && index !== children.length - 1) chars++;
11692
- return acc + chars;
11693
- } else return acc;
11694
- }, 0);
11695
- }
11696
- if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") usedCharacters++;
11697
- return usedCharacters;
11698
- }
11699
- const letterRegex = /^[a-zA-Z]$/;
11700
- /**
11701
- * Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
11702
- *
11703
- * @argument ev - The keyboard event to transform
11704
- * @argument mode - Use either ev.key of ev.code to get the string shortcut
11705
- *
11706
- * @example
11707
- * event : { ctrlKey: true, key: "a" } => "Ctrl+A"
11708
- * event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
11709
- */
11710
- function keyboardEventToShortcutString(ev, mode = "key") {
11711
- let keyDownString = "";
11712
- if (!MODIFIER_KEYS.includes(ev.key)) {
11713
- if (isCtrlKey(ev)) keyDownString += "Ctrl+";
11714
- if (ev.altKey) keyDownString += "Alt+";
11715
- if (ev.shiftKey) keyDownString += "Shift+";
11716
- }
11717
- const key = mode === "key" ? ev.key : ev.code;
11718
- keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
11719
- return keyDownString;
11720
- }
11721
- function isMacOS() {
11722
- return Boolean(macRegex.test(navigator.userAgent));
11723
- }
11724
- /**
11725
- * @param {KeyboardEvent | MouseEvent} ev
11726
- * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
11727
- * On Mac, this is the "meta" or "command" key.
11728
- */
11729
- function isCtrlKey(ev) {
11730
- return isMacOS() || isIOS() ? ev.metaKey : ev.ctrlKey;
11731
- }
11732
- /**
11733
- * @param {MouseEvent} ev - The mouse event.
11734
- * @returns {boolean} Returns true if the event was triggered by a middle-click
11735
- * or a Ctrl + Click (Cmd + Click on Mac).
11736
- */
11737
- function isMiddleClickOrCtrlClick(ev) {
11738
- return ev.button === 1 || isCtrlKey(ev) && ev.button === 0;
11739
- }
11740
- function downloadFile(dataUrl, fileName) {
11741
- const a = document.createElement("a");
11742
- a.href = dataUrl;
11743
- a.download = fileName;
11744
- document.body.appendChild(a);
11745
- a.click();
11746
- document.body.removeChild(a);
11747
- }
11748
- /**
11749
- * Detects if the current browser is Firefox
11750
- */
11751
- function isBrowserFirefox() {
11752
- return /Firefox/i.test(navigator.userAgent);
11753
- }
11754
- function maxTouchPoints() {
11755
- return navigator.maxTouchPoints || 1;
11756
- }
11757
- function isAndroid() {
11758
- return /Android/i.test(navigator.userAgent);
11759
- }
11760
- function isIOS() {
11761
- return /(iPad|iPhone|iPod)/i.test(navigator.userAgent) || navigator.platform === "MacIntel" && maxTouchPoints() > 1;
11762
- }
11763
- function isOtherMobileOS() {
11764
- return /(webOS|BlackBerry|Windows Phone)/i.test(navigator.userAgent);
11765
- }
11766
- function isMobileOS() {
11767
- return isAndroid() || isIOS() || isOtherMobileOS();
11768
- }
11769
-
11770
11849
  //#endregion
11771
11850
  //#region src/components/helpers/zoom.ts
11772
11851
  /**
@@ -11851,6 +11930,9 @@ var ScorecardChart = class extends _odoo_owl.Component {
11851
11930
  window.devicePixelRatio
11852
11931
  ];
11853
11932
  });
11933
+ const resizeObserver = new ResizeObserver(() => this.createChart());
11934
+ (0, _odoo_owl.onMounted)(() => resizeObserver.observe(this.canvas.el));
11935
+ (0, _odoo_owl.onWillUnmount)(() => resizeObserver.disconnect());
11854
11936
  }
11855
11937
  createChart() {
11856
11938
  const canvas = this.canvas.el;
@@ -13542,6 +13624,15 @@ var GaugeChartComponent = class extends _odoo_owl.Component {
13542
13624
  window.devicePixelRatio
13543
13625
  ];
13544
13626
  });
13627
+ const resizeObserver = new ResizeObserver(() => {
13628
+ if (animation) {
13629
+ animation.stop();
13630
+ animation = null;
13631
+ }
13632
+ drawGaugeChart(this.canvasEl, this.runtime, this.env.model.getters.getViewportZoomLevel());
13633
+ });
13634
+ (0, _odoo_owl.onMounted)(() => resizeObserver.observe(this.canvas.el));
13635
+ (0, _odoo_owl.onWillUnmount)(() => resizeObserver.disconnect());
13545
13636
  }
13546
13637
  drawGaugeWithAnimation() {
13547
13638
  drawGaugeChart(this.canvasEl, {
@@ -23476,7 +23567,7 @@ const insertImage = {
23476
23567
  };
23477
23568
  const insertTable = {
23478
23569
  name: () => _t("Table"),
23479
- description: "Alt+T",
23570
+ shortcut: "Alt+T",
23480
23571
  execute: INSERT_TABLE,
23481
23572
  isVisible: (env) => IS_SELECTION_CONTINUOUS(env) && !env.model.getters.getFirstTableInSelection(),
23482
23573
  isEnabled: (env) => !env.isSmall,
@@ -23525,7 +23616,7 @@ const categoriesFunctionListMenuBuilder = () => {
23525
23616
  };
23526
23617
  const insertLink = {
23527
23618
  name: _t("Link"),
23528
- description: "Ctrl+Shift+K",
23619
+ shortcut: "Ctrl+Shift+K",
23529
23620
  execute: INSERT_LINK,
23530
23621
  icon: "o-spreadsheet-Icon.INSERT_LINK"
23531
23622
  };
@@ -23584,7 +23675,7 @@ const insertDropdown = {
23584
23675
  };
23585
23676
  const insertSheet = {
23586
23677
  name: _t("Insert sheet"),
23587
- description: "Shift+F11",
23678
+ shortcut: "Shift+F11",
23588
23679
  execute: (env) => {
23589
23680
  const activeSheetId = env.model.getters.getActiveSheetId();
23590
23681
  const position = env.model.getters.getSheetIds().indexOf(activeSheetId) + 1;
@@ -27733,6 +27824,74 @@ function getCalendarChartLayout(definition, args) {
27733
27824
  } };
27734
27825
  }
27735
27826
 
27827
+ //#endregion
27828
+ //#region src/helpers/figures/charts/runtime/chart_highlight.ts
27829
+ const HIGHLIGHT_TRANSPARENCY = .2;
27830
+ function highlightComboChartItem(item, dataSets) {
27831
+ const index = item.datasetIndex;
27832
+ for (let i = 0; i < dataSets.length; i++) {
27833
+ if (i === index) continue;
27834
+ const dataset = dataSets[i];
27835
+ for (const key of ["borderColor", "backgroundColor"]) {
27836
+ if (!(key in dataset)) continue;
27837
+ dataset[key] = setColorAlpha(dataset[key], i === index ? 1 : HIGHLIGHT_TRANSPARENCY);
27838
+ }
27839
+ }
27840
+ }
27841
+ function resetComboChartHighlights(dataSets) {
27842
+ for (const dataset of dataSets) for (const key of ["borderColor", "backgroundColor"]) {
27843
+ if (!(key in dataset)) continue;
27844
+ dataset[key] = setColorAlpha(dataset[key], 1);
27845
+ }
27846
+ }
27847
+ function highlightLineChartItem(item, dataSets) {
27848
+ const index = item.datasetIndex;
27849
+ for (let i = 0; i < dataSets.length; i++) {
27850
+ const dataset = dataSets[i];
27851
+ const color = setColorAlpha(dataset.borderColor, i === index ? 1 : HIGHLIGHT_TRANSPARENCY);
27852
+ dataset.borderColor = color;
27853
+ dataset.pointBackgroundColor = color;
27854
+ dataset.backgroundColor = setColorAlpha(dataset.backgroundColor, LINE_FILL_TRANSPARENCY * (i === index ? 1 : HIGHLIGHT_TRANSPARENCY));
27855
+ }
27856
+ }
27857
+ function resetLineChartHighlights(dataSets) {
27858
+ for (const dataset of dataSets) {
27859
+ const color = setColorAlpha(dataset.borderColor, 1);
27860
+ dataset.borderColor = color;
27861
+ dataset.pointBackgroundColor = color;
27862
+ dataset.backgroundColor = setColorAlpha(dataset.backgroundColor, LINE_FILL_TRANSPARENCY);
27863
+ }
27864
+ }
27865
+ function toggleLineBarDataVisibility(chart, item) {
27866
+ const index = item.datasetIndex;
27867
+ if (index === void 0) return;
27868
+ if (chart.isDatasetVisible(index)) chart.hide(index);
27869
+ else chart.show(index);
27870
+ }
27871
+ function highlightPieChartItem(item, dataSets) {
27872
+ for (const dataset of dataSets) {
27873
+ const backgroundColors = dataset.backgroundColor;
27874
+ if (!backgroundColors) return;
27875
+ backgroundColors.forEach((color, i, colors) => {
27876
+ colors[i] = setColorAlpha(color, i === item.index ? 1 : HIGHLIGHT_TRANSPARENCY);
27877
+ });
27878
+ }
27879
+ }
27880
+ function resetPieChartHighlights(dataSets) {
27881
+ for (const dataset of dataSets) {
27882
+ const backgroundColors = dataset.backgroundColor;
27883
+ if (!backgroundColors) return;
27884
+ backgroundColors.forEach((color, i, colors) => {
27885
+ colors[i] = setColorAlpha(color, 1);
27886
+ });
27887
+ }
27888
+ }
27889
+ function togglePieDataVisibility(chart, item) {
27890
+ const index = item.index;
27891
+ if (index === void 0) return;
27892
+ chart.toggleDataVisibility(index);
27893
+ }
27894
+
27736
27895
  //#endregion
27737
27896
  //#region src/helpers/figures/charts/runtime/chartjs_legend.ts
27738
27897
  function getLegendDisplayOptions({ legendPosition }) {
@@ -27743,7 +27902,20 @@ function getLegendDisplayOptions({ legendPosition }) {
27743
27902
  }
27744
27903
  function getBarChartLegend(definition, args) {
27745
27904
  return {
27746
- ...INTERACTIVE_LEGEND_CONFIG,
27905
+ ...getInteractiveLegendConfig({
27906
+ highlightItem: highlightComboChartItem,
27907
+ unHighlightItems: resetComboChartHighlights,
27908
+ toggleDataVisibility: toggleLineBarDataVisibility
27909
+ }),
27910
+ ...getLegendDisplayOptions(definition),
27911
+ ...getCustomLegendLabels(chartFontColor(definition.background), {
27912
+ pointStyle: "rect",
27913
+ lineWidth: 3
27914
+ })
27915
+ };
27916
+ }
27917
+ function getPyramidChartLegend(definition, args) {
27918
+ return {
27747
27919
  ...getLegendDisplayOptions(definition),
27748
27920
  ...getCustomLegendLabels(chartFontColor(definition.background), {
27749
27921
  pointStyle: "rect",
@@ -27756,7 +27928,11 @@ function getLineChartLegend(definition, args) {
27756
27928
  const pointStyle = filled ? "rect" : "line";
27757
27929
  const lineWidth = filled ? 2 : 3;
27758
27930
  return {
27759
- ...INTERACTIVE_LEGEND_CONFIG,
27931
+ ...getInteractiveLegendConfig({
27932
+ highlightItem: highlightLineChartItem,
27933
+ unHighlightItems: resetLineChartHighlights,
27934
+ toggleDataVisibility: toggleLineBarDataVisibility
27935
+ }),
27760
27936
  ...getLegendDisplayOptions(definition),
27761
27937
  ...getCustomLegendLabels(chartFontColor(definition.background), {
27762
27938
  pointStyle,
@@ -27769,6 +27945,11 @@ function getPieChartLegend(definition, args) {
27769
27945
  const colors = getPieColors(new ColorGenerator(Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0)), definition.slicesColors), dataSetsValues);
27770
27946
  const fontColor = chartFontColor(definition.background);
27771
27947
  return {
27948
+ ...getInteractiveLegendConfig({
27949
+ highlightItem: highlightPieChartItem,
27950
+ unHighlightItems: resetPieChartHighlights,
27951
+ toggleDataVisibility: togglePieDataVisibility
27952
+ }),
27772
27953
  ...getLegendDisplayOptions(definition),
27773
27954
  labels: {
27774
27955
  usePointStyle: true,
@@ -27778,7 +27959,9 @@ function getPieChartLegend(definition, args) {
27778
27959
  fillStyle: colors[index],
27779
27960
  pointStyle: "rect",
27780
27961
  lineWidth: 2,
27781
- fontColor
27962
+ fontColor,
27963
+ index,
27964
+ hidden: !c.getDataVisibility?.(index)
27782
27965
  })) || []).filter((label) => label.text),
27783
27966
  filter: (legendItem, data) => {
27784
27967
  return "datasetIndex" in legendItem ? !data.datasets[legendItem.datasetIndex].hidden : true;
@@ -27788,9 +27971,13 @@ function getPieChartLegend(definition, args) {
27788
27971
  }
27789
27972
  function getScatterChartLegend(definition, args) {
27790
27973
  return {
27791
- ...INTERACTIVE_LEGEND_CONFIG,
27974
+ ...getInteractiveLegendConfig({
27975
+ highlightItem: highlightLineChartItem,
27976
+ unHighlightItems: resetLineChartHighlights,
27977
+ toggleDataVisibility: toggleLineBarDataVisibility
27978
+ }),
27792
27979
  ...getLegendDisplayOptions(definition),
27793
- ...getCustomLegendLabels(chartFontColor(definition.background), {
27980
+ ...getScatterPlotLegendLabels(chartFontColor(definition.background), {
27794
27981
  pointStyle: "circle",
27795
27982
  strokeStyle: definition.background || "#ffffff",
27796
27983
  lineWidth: 8
@@ -27811,7 +27998,11 @@ function getBubbleChartLegend(definition, args) {
27811
27998
  }
27812
27999
  function getComboChartLegend(definition, args) {
27813
28000
  return {
27814
- ...INTERACTIVE_LEGEND_CONFIG,
28001
+ ...getInteractiveLegendConfig({
28002
+ highlightItem: highlightComboChartItem,
28003
+ unHighlightItems: resetComboChartHighlights,
28004
+ toggleDataVisibility: toggleLineBarDataVisibility
28005
+ }),
27815
28006
  ...getLegendDisplayOptions(definition),
27816
28007
  ...getCustomLegendLabels(chartFontColor(definition.background), { lineWidth: 3 })
27817
28008
  };
@@ -27860,7 +28051,11 @@ function getRadarChartLegend(definition, args) {
27860
28051
  const pointStyle = fill ? "rect" : "line";
27861
28052
  const lineWidth = fill ? 2 : 3;
27862
28053
  return {
27863
- ...INTERACTIVE_LEGEND_CONFIG,
28054
+ ...getInteractiveLegendConfig({
28055
+ highlightItem: highlightLineChartItem,
28056
+ unHighlightItems: resetLineChartHighlights,
28057
+ toggleDataVisibility: toggleLineBarDataVisibility
28058
+ }),
27864
28059
  ...getLegendDisplayOptions(definition),
27865
28060
  ...getCustomLegendLabels(chartFontColor(definition.background), {
27866
28061
  pointStyle,
@@ -27890,29 +28085,44 @@ function getSunburstChartLegend(definition, args) {
27890
28085
  }
27891
28086
  };
27892
28087
  }
27893
- const INTERACTIVE_LEGEND_CONFIG = {
27894
- onHover: (event) => {
27895
- const target = event.native?.target;
27896
- if (!target) return;
27897
- target.style.cursor = "pointer";
27898
- },
27899
- onLeave: (event) => {
27900
- const target = event.native?.target;
27901
- if (!target) return;
27902
- target.style.cursor = "default";
27903
- },
27904
- onClick: (event, legendItem, legend) => {
27905
- if (event.type !== "click") return;
27906
- const index = legendItem.datasetIndex;
27907
- if (!legend.legendItems || index === void 0) return;
27908
- if (legend.chart.isDatasetVisible(index)) legend.chart.hide(index);
27909
- else legend.chart.show(index);
27910
- event.native.preventDefault();
27911
- event.native.stopPropagation();
27912
- }
27913
- };
28088
+ function getInteractiveLegendConfig({ highlightItem, unHighlightItems, toggleDataVisibility }) {
28089
+ return {
28090
+ onHover: (event, item, legend) => {
28091
+ if (!item.hidden) {
28092
+ const datasets = legend.chart.data.datasets;
28093
+ highlightItem(item, datasets);
28094
+ legend.chart.update();
28095
+ }
28096
+ const target = event.native?.target;
28097
+ if (target && target instanceof HTMLElement) target.style.cursor = "pointer";
28098
+ },
28099
+ onLeave: (event, item, legend) => {
28100
+ if (!item.hidden) {
28101
+ const datasets = legend.chart.data.datasets;
28102
+ unHighlightItems(datasets);
28103
+ legend.chart.update();
28104
+ }
28105
+ const target = event.native?.target;
28106
+ if (target && target instanceof HTMLElement) target.style.cursor = "default";
28107
+ },
28108
+ onClick: (event, item, legend) => {
28109
+ if (event.type !== "click" || !legend.legendItems) return;
28110
+ if (legend.chart.options.animation) legend.chart.options.animation = false;
28111
+ toggleDataVisibility(legend.chart, item);
28112
+ if (!item.hidden) unHighlightItems(legend.chart.data.datasets);
28113
+ else highlightItem(item, legend.chart.data.datasets);
28114
+ legend.chart.update();
28115
+ event.native.preventDefault();
28116
+ event.native.stopPropagation();
28117
+ }
28118
+ };
28119
+ }
27914
28120
  const INTERACTIVE_LEGEND_CONFIG_FOR_BUBBLE_CHART = {
27915
- ...INTERACTIVE_LEGEND_CONFIG,
28121
+ ...getInteractiveLegendConfig({
28122
+ highlightItem: highlightLineChartItem,
28123
+ unHighlightItems: resetLineChartHighlights,
28124
+ toggleDataVisibility: toggleLineBarDataVisibility
28125
+ }),
27916
28126
  onClick: (event, legendItem, legend) => {
27917
28127
  if (event.type !== "click") return;
27918
28128
  const index = legendItem.datasetIndex;
@@ -27979,6 +28189,37 @@ function getBubbleChartLegendLabels(fontColor, legendLabelConfig, labels) {
27979
28189
  filter: (legendItem, data) => true
27980
28190
  } };
27981
28191
  }
28192
+ function getScatterPlotLegendLabels(fontColor, legendLabelConfig) {
28193
+ return { labels: {
28194
+ color: fontColor,
28195
+ usePointStyle: true,
28196
+ generateLabels: (chart) => chart.data.datasets.map((dataset, index) => {
28197
+ if (isTrendLineAxis(dataset["xAxisID"])) return {
28198
+ text: truncateLabel(dataset.label),
28199
+ fontColor,
28200
+ strokeStyle: dataset.borderColor,
28201
+ hidden: !chart.isDatasetVisible(index),
28202
+ pointStyle: "line",
28203
+ datasetIndex: index,
28204
+ lineWidth: 3
28205
+ };
28206
+ return {
28207
+ text: truncateLabel(dataset.label),
28208
+ fontColor,
28209
+ strokeStyle: dataset.borderColor,
28210
+ fillStyle: dataset["pointBackgroundColor"],
28211
+ hidden: !chart.isDatasetVisible(index),
28212
+ pointStyle: "circle",
28213
+ datasetIndex: index,
28214
+ lineWidth: 8,
28215
+ ...legendLabelConfig
28216
+ };
28217
+ }).filter((label) => label.text),
28218
+ filter: (legendItem, data) => {
28219
+ return "datasetIndex" in legendItem ? !data.datasets[legendItem.datasetIndex].hidden : true;
28220
+ }
28221
+ } };
28222
+ }
27982
28223
 
27983
28224
  //#endregion
27984
28225
  //#region src/types/chart/sunburst_chart.ts
@@ -29618,11 +29859,12 @@ const ChartRangeDataSourceHandler = {
29618
29859
  type: "range",
29619
29860
  dataSets: [],
29620
29861
  dataSetsHaveTitle: false,
29862
+ labelRange: context.auxiliaryRange,
29621
29863
  ...context.dataSource
29622
29864
  };
29623
29865
  },
29624
29866
  fromHierarchicalContextCreation(context) {
29625
- if (context.dataSource?.type !== "range" || context.hierarchicalDataSource?.type !== "range") return {
29867
+ if (context.dataSource?.type !== "range" || context.hierarchicalDataSource !== void 0 && context.hierarchicalDataSource.type !== "range") return {
29626
29868
  type: "range",
29627
29869
  dataSets: [],
29628
29870
  dataSetsHaveTitle: false
@@ -37614,7 +37856,7 @@ function getVisiblePivotCellPositions(getters, pivotId) {
37614
37856
  col,
37615
37857
  row
37616
37858
  };
37617
- if (pivotId === getters.getPivotIdFromPosition(position)) positions.push(position);
37859
+ if (getters.getPivotIdsFromPosition(position).includes(pivotId)) positions.push(position);
37618
37860
  }
37619
37861
  return positions;
37620
37862
  }
@@ -39688,6 +39930,9 @@ const PIVOT_FUNCTIONS = [
39688
39930
  function getFirstPivotFunction(compiledFormula, getters) {
39689
39931
  return compiledFormula.getFunctionsFromTokens(PIVOT_FUNCTIONS, getters)[0];
39690
39932
  }
39933
+ function getPivotFunctions(compiledFormula, getters) {
39934
+ return compiledFormula.getFunctionsFromTokens(PIVOT_FUNCTIONS, getters);
39935
+ }
39691
39936
  /**
39692
39937
  * Parse a spreadsheet formula and detect the number of PIVOT functions that are
39693
39938
  * present in the given formula.
@@ -49291,6 +49536,7 @@ var helpers_index_exports = /* @__PURE__ */ __exportAll({
49291
49536
  getPieChartLegend: () => getPieChartLegend,
49292
49537
  getPieChartTooltip: () => getPieChartTooltip,
49293
49538
  getPyramidChartData: () => getPyramidChartData,
49539
+ getPyramidChartLegend: () => getPyramidChartLegend,
49294
49540
  getPyramidChartScales: () => getPyramidChartScales,
49295
49541
  getPyramidChartShowValues: () => getPyramidChartShowValues,
49296
49542
  getPyramidChartTooltip: () => getPyramidChartTooltip,
@@ -58154,6 +58400,7 @@ var PivotUIPlugin = class extends CoreViewPlugin {
58154
58400
  "getFirstPivotFunction",
58155
58401
  "getPivotCellSortDirection",
58156
58402
  "getPivotIdFromPosition",
58403
+ "getPivotIdsFromPosition",
58157
58404
  "getPivotCellFromPosition",
58158
58405
  "generateNewCalculatedMeasureName",
58159
58406
  "isPivotUnused",
@@ -58213,37 +58460,52 @@ var PivotUIPlugin = class extends CoreViewPlugin {
58213
58460
  }
58214
58461
  }
58215
58462
  /**
58216
- * Get the id of the pivot at the given position. Returns undefined if there
58463
+ * Get the id of the first pivot in the formula at the given position. Returns undefined if there
58217
58464
  * is no pivot at this position
58218
58465
  */
58219
58466
  getPivotIdFromPosition(position) {
58467
+ return this.getPivotIdsFromPosition(position)[0];
58468
+ }
58469
+ /**
58470
+ * Get all of the ids of the pivot present in the formula at the given position.
58471
+ */
58472
+ getPivotIdsFromPosition(position) {
58220
58473
  const cell = this.getters.getCorrespondingFormulaCell(position);
58221
- if (cell && cell.isFormula) {
58222
- const pivotFunction = this.getFirstPivotFunction(position.sheetId, cell.compiledFormula);
58223
- if (pivotFunction) {
58224
- const pivotId = pivotFunction.args[0]?.toString();
58225
- return pivotId && this.getters.getPivotId(pivotId);
58226
- }
58227
- }
58474
+ if (cell && cell.isFormula) return this.getPivotIdsFromFormula(position.sheetId, cell.compiledFormula);
58475
+ return [];
58476
+ }
58477
+ getPivotIdsFromFormula(sheetId, formula) {
58478
+ return this.getPivotFunctions(sheetId, formula).map((pivotFunction) => {
58479
+ const pivotId = pivotFunction.args[0]?.toString();
58480
+ return pivotId && this.getters.getPivotId(pivotId);
58481
+ }).filter(isDefined);
58228
58482
  }
58229
58483
  isSpillPivotFormula(position) {
58230
58484
  const cell = this.getters.getCorrespondingFormulaCell(position);
58231
58485
  if (cell && cell.isFormula) return this.getFirstPivotFunction(position.sheetId, cell.compiledFormula)?.functionName === "PIVOT";
58232
58486
  return false;
58233
58487
  }
58234
- getFirstPivotFunction(sheetId, compiledFormula) {
58235
- const pivotFunction = getFirstPivotFunction(compiledFormula, this.getters);
58236
- if (!pivotFunction) return;
58237
- const { functionName, args } = pivotFunction;
58238
- return {
58239
- functionName,
58240
- args: args.map((argAst) => {
58488
+ getPivotFunctions(sheetId, formula) {
58489
+ const pivotFunctions = getPivotFunctions(formula, this.getters);
58490
+ if (!pivotFunctions.length) return [];
58491
+ const evaluatedPivotFunctions = [];
58492
+ for (const pivotFunction of pivotFunctions) {
58493
+ const { functionName, args } = pivotFunction;
58494
+ const evaluatedArgs = args.map((argAst) => {
58241
58495
  if (argAst.type === "EMPTY") return;
58242
58496
  else if (argAst.type === "STRING" || argAst.type === "BOOLEAN" || argAst.type === "NUMBER") return argAst.value;
58243
58497
  const argsString = astToFormula(argAst);
58244
58498
  return this.getters.evaluateFormula(sheetId, argsString);
58245
- })
58246
- };
58499
+ });
58500
+ evaluatedPivotFunctions.push({
58501
+ functionName,
58502
+ args: evaluatedArgs
58503
+ });
58504
+ }
58505
+ return evaluatedPivotFunctions;
58506
+ }
58507
+ getFirstPivotFunction(sheetId, formula) {
58508
+ return this.getPivotFunctions(sheetId, formula)[0];
58247
58509
  }
58248
58510
  /**
58249
58511
  * Returns the domain args of a pivot formula from a position.
@@ -58349,8 +58611,8 @@ var PivotUIPlugin = class extends CoreViewPlugin {
58349
58611
  const unusedPivots = new Set(this.getters.getPivotIds());
58350
58612
  for (const sheetId of this.getters.getSheetIds()) for (const cell of this.getters.getCells(sheetId)) {
58351
58613
  const position = this.getters.getCellPosition(cell.id);
58352
- const pivotId = this.getPivotIdFromPosition(position);
58353
- if (pivotId) {
58614
+ const pivotIds = this.getPivotIdsFromPosition(position);
58615
+ for (const pivotId of pivotIds) {
58354
58616
  unusedPivots.delete(pivotId);
58355
58617
  if (!unusedPivots.size) {
58356
58618
  this.unusedPivotsInFormulas = [];
@@ -58358,6 +58620,21 @@ var PivotUIPlugin = class extends CoreViewPlugin {
58358
58620
  }
58359
58621
  }
58360
58622
  }
58623
+ for (const pivotId of this.getters.getPivotIds()) {
58624
+ const pivot = this.getters.getPivot(pivotId);
58625
+ for (const measure of pivot.definition.measures) if (measure.computedBy) {
58626
+ const { sheetId } = measure.computedBy;
58627
+ const formula = this.getters.getMeasureCompiledFormula(pivotId, measure);
58628
+ const relatedPivotIds = this.getPivotIdsFromFormula(sheetId, formula);
58629
+ for (const relatedPivotId of relatedPivotIds) {
58630
+ unusedPivots.delete(relatedPivotId);
58631
+ if (!unusedPivots.size) {
58632
+ this.unusedPivotsInFormulas = [];
58633
+ return [];
58634
+ }
58635
+ }
58636
+ }
58637
+ }
58361
58638
  this.unusedPivotsInFormulas = [...unusedPivots];
58362
58639
  return this.unusedPivotsInFormulas;
58363
58640
  }
@@ -63034,12 +63311,13 @@ var CarouselUIPlugin = class extends UIPlugin {
63034
63311
  const chartId = this.getChartIdFromFigureId(chartFigureId);
63035
63312
  if (!chartId) return;
63036
63313
  const carousel = this.getters.getCarousel(figureId);
63314
+ const newItem = {
63315
+ type: "chart",
63316
+ chartId
63317
+ };
63037
63318
  const definition = {
63038
63319
  ...carousel,
63039
- items: [...carousel.items, {
63040
- type: "chart",
63041
- chartId
63042
- }]
63320
+ items: [...carousel.items, newItem]
63043
63321
  };
63044
63322
  this.dispatch("UPDATE_CAROUSEL", {
63045
63323
  sheetId,
@@ -63056,6 +63334,11 @@ var CarouselUIPlugin = class extends UIPlugin {
63056
63334
  sheetId,
63057
63335
  figureId: chartFigureId
63058
63336
  });
63337
+ this.dispatch("UPDATE_CAROUSEL_ACTIVE_ITEM", {
63338
+ figureId,
63339
+ sheetId,
63340
+ item: newItem
63341
+ });
63059
63342
  }
63060
63343
  duplicateCarouselChart({ carouselId, chartId, sheetId, duplicatedChartId }) {
63061
63344
  const chart = this.getters.getChart(chartId);
@@ -77397,7 +77680,7 @@ const PyramidChart = {
77397
77680
  scales: getPyramidChartScales(definition, chartData),
77398
77681
  plugins: {
77399
77682
  title: getChartTitle(definition, getters),
77400
- legend: getBarChartLegend(definition, chartData),
77683
+ legend: getPyramidChartLegend(definition, chartData),
77401
77684
  tooltip: getPyramidChartTooltip(definition, chartData),
77402
77685
  chartShowValuesPlugin: getPyramidChartShowValues(definition, chartData),
77403
77686
  background: { color: chartData.background }
@@ -85874,7 +86157,8 @@ const constants = {
85874
86157
  FIGURE_ID_SPLITTER: "??",
85875
86158
  GRID_ICON_EDGE_LENGTH: 17,
85876
86159
  GRID_ICON_MARGIN: 2,
85877
- CHART_TYPES
86160
+ CHART_TYPES,
86161
+ DARK_MODE_FILTER_STRING
85878
86162
  };
85879
86163
  const chartHelpers = {
85880
86164
  ...helpers_index_exports$1,
@@ -85975,6 +86259,6 @@ exports.stores = stores;
85975
86259
  exports.tokenColors = tokenColors;
85976
86260
  exports.tokenize = tokenize;
85977
86261
 
85978
- __info__.version = "19.4.0-alpha.6";
85979
- __info__.date = "2026-05-15T07:08:41.621Z";
85980
- __info__.hash = "0207b9d";
86262
+ __info__.version = "19.4.0-alpha.7";
86263
+ __info__.date = "2026-05-20T14:40:27.236Z";
86264
+ __info__.hash = "7ee566f";