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