@odoo/o-spreadsheet 16.4.0-alpha.1 → 16.4.0-alpha.3

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.
@@ -2215,249 +2215,6 @@
2215
2215
  return { sheetName, xc };
2216
2216
  }
2217
2217
 
2218
- class RangeImpl {
2219
- getSheetSize;
2220
- _zone;
2221
- parts;
2222
- invalidXc;
2223
- prefixSheet = false;
2224
- sheetId; // the sheet on which the range is defined
2225
- invalidSheetName; // the name of any sheet that is invalid
2226
- constructor(args, getSheetSize) {
2227
- this.getSheetSize = getSheetSize;
2228
- this._zone = args.zone;
2229
- this.parts = args.parts;
2230
- this.prefixSheet = args.prefixSheet;
2231
- this.invalidXc = args.invalidXc;
2232
- this.sheetId = args.sheetId;
2233
- this.invalidSheetName = args.invalidSheetName;
2234
- }
2235
- static fromRange(range, getters) {
2236
- if (range instanceof RangeImpl) {
2237
- return range;
2238
- }
2239
- return new RangeImpl(range, getters.getSheetSize);
2240
- }
2241
- get unboundedZone() {
2242
- return this._zone;
2243
- }
2244
- get zone() {
2245
- const { left, top, bottom, right } = this._zone;
2246
- if (right !== undefined && bottom !== undefined)
2247
- return { left, top, right, bottom };
2248
- else if (bottom === undefined && right !== undefined) {
2249
- return { right, top, left, bottom: this.getSheetSize(this.sheetId).numberOfRows - 1 };
2250
- }
2251
- else if (right === undefined && bottom !== undefined) {
2252
- return { bottom, left, top, right: this.getSheetSize(this.sheetId).numberOfCols - 1 };
2253
- }
2254
- throw new Error(_lt("Bad zone format"));
2255
- }
2256
- static getRangeParts(xc, zone) {
2257
- const parts = xc.split(":").map((p) => {
2258
- const isFullRow = isRowReference(p);
2259
- return {
2260
- colFixed: isFullRow ? false : p.startsWith("$"),
2261
- rowFixed: isFullRow ? p.startsWith("$") : p.includes("$", 1),
2262
- };
2263
- });
2264
- const isFullCol = zone.bottom === undefined;
2265
- const isFullRow = zone.right === undefined;
2266
- if (isFullCol) {
2267
- parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2268
- parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2269
- if (zone.left === zone.right) {
2270
- parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2271
- parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2272
- }
2273
- }
2274
- if (isFullRow) {
2275
- parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2276
- parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2277
- if (zone.top === zone.bottom) {
2278
- parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2279
- parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2280
- }
2281
- }
2282
- return parts;
2283
- }
2284
- get isFullCol() {
2285
- return this._zone.bottom === undefined;
2286
- }
2287
- get isFullRow() {
2288
- return this._zone.right === undefined;
2289
- }
2290
- get rangeData() {
2291
- return {
2292
- _zone: this._zone,
2293
- _sheetId: this.sheetId,
2294
- };
2295
- }
2296
- /**
2297
- * Check that a zone is valid regarding the order of top-bottom and left-right.
2298
- * Left should be smaller than right, top should be smaller than bottom.
2299
- * If it's not the case, simply invert them, and invert the linked parts
2300
- */
2301
- orderZone() {
2302
- const zone = { ...this._zone };
2303
- let parts = this.parts;
2304
- if (zone.right !== undefined && zone.right < zone.left) {
2305
- let right = zone.right;
2306
- zone.right = zone.left;
2307
- zone.left = right;
2308
- parts = [
2309
- {
2310
- colFixed: parts[1]?.colFixed || false,
2311
- rowFixed: parts[0]?.rowFixed || false,
2312
- },
2313
- {
2314
- colFixed: parts[0]?.colFixed || false,
2315
- rowFixed: parts[1]?.rowFixed || false,
2316
- },
2317
- ];
2318
- }
2319
- if (zone.bottom !== undefined && zone.bottom < zone.top) {
2320
- let bottom = zone.bottom;
2321
- zone.bottom = zone.top;
2322
- zone.top = bottom;
2323
- parts = [
2324
- {
2325
- colFixed: parts[0]?.colFixed || false,
2326
- rowFixed: parts[1]?.rowFixed || false,
2327
- },
2328
- {
2329
- colFixed: parts[1]?.colFixed || false,
2330
- rowFixed: parts[0]?.rowFixed || false,
2331
- },
2332
- ];
2333
- }
2334
- return this.clone({ zone, parts });
2335
- }
2336
- /**
2337
- *
2338
- * @param rangeParams optional, values to put in the cloned range instead of the current values of the range
2339
- */
2340
- clone(rangeParams) {
2341
- return new RangeImpl({
2342
- zone: rangeParams?.zone ? rangeParams.zone : { ...this._zone },
2343
- sheetId: rangeParams?.sheetId ? rangeParams.sheetId : this.sheetId,
2344
- invalidSheetName: rangeParams && "invalidSheetName" in rangeParams // 'attr in obj' instead of just 'obj.attr' because we accept undefined values
2345
- ? rangeParams.invalidSheetName
2346
- : this.invalidSheetName,
2347
- invalidXc: rangeParams && "invalidXc" in rangeParams ? rangeParams.invalidXc : this.invalidXc,
2348
- parts: rangeParams?.parts
2349
- ? rangeParams.parts
2350
- : this.parts.map((part) => {
2351
- return { rowFixed: part.rowFixed, colFixed: part.colFixed };
2352
- }),
2353
- prefixSheet: rangeParams?.prefixSheet ? rangeParams.prefixSheet : this.prefixSheet,
2354
- }, this.getSheetSize);
2355
- }
2356
- }
2357
- /**
2358
- * Copy a range. If the range is on the sheetIdFrom, the range will target
2359
- * sheetIdTo.
2360
- */
2361
- function copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) {
2362
- const sheetId = range.sheetId === sheetIdFrom ? sheetIdTo : range.sheetId;
2363
- return range.clone({ sheetId });
2364
- }
2365
- /**
2366
- * Create a range from a xc. If the xc is empty, this function returns undefined.
2367
- */
2368
- function createRange(getters, sheetId, range) {
2369
- return range ? getters.getRangeFromSheetXC(sheetId, range) : undefined;
2370
- }
2371
-
2372
- /** Methods from Odoo Web Utils */
2373
- /**
2374
- * This function computes a score that represent the fact that the
2375
- * string contains the pattern, or not
2376
- *
2377
- * - If the score is 0, the string does not contain the letters of the pattern in
2378
- * the correct order.
2379
- * - if the score is > 0, it actually contains the letters.
2380
- *
2381
- * Better matches will get a higher score: consecutive letters are better,
2382
- * and a match closer to the beginning of the string is also scored higher.
2383
- */
2384
- function fuzzyMatch(pattern, str) {
2385
- pattern = pattern.toLocaleLowerCase();
2386
- str = str.toLocaleLowerCase();
2387
- let totalScore = 0;
2388
- let currentScore = 0;
2389
- let len = str.length;
2390
- let patternIndex = 0;
2391
- for (let i = 0; i < len; i++) {
2392
- if (str[i] === pattern[patternIndex]) {
2393
- patternIndex++;
2394
- currentScore += 100 + currentScore - i / 200;
2395
- }
2396
- else {
2397
- currentScore = 0;
2398
- }
2399
- totalScore = totalScore + currentScore;
2400
- }
2401
- return patternIndex === pattern.length ? totalScore : 0;
2402
- }
2403
- /**
2404
- * Return a list of things that matches a pattern, ordered by their 'score' (
2405
- * higher score first). An higher score means that the match is better. For
2406
- * example, consecutive letters are considered a better match.
2407
- */
2408
- function fuzzyLookup(pattern, list, fn) {
2409
- const results = [];
2410
- list.forEach((data) => {
2411
- const score = fuzzyMatch(pattern, fn(data));
2412
- if (score > 0) {
2413
- results.push({ score, elem: data });
2414
- }
2415
- });
2416
- // we want better matches first
2417
- results.sort((a, b) => b.score - a.score);
2418
- return results.map((r) => r.elem);
2419
- }
2420
-
2421
- function createDefaultRows(rowNumber) {
2422
- const rows = [];
2423
- for (let i = 0; i < rowNumber; i++) {
2424
- const row = {
2425
- cells: {},
2426
- };
2427
- rows.push(row);
2428
- }
2429
- return rows;
2430
- }
2431
-
2432
- /*
2433
- * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
2434
- * */
2435
- class UuidGenerator {
2436
- isFastIdStrategy = false;
2437
- fastIdStart = 0;
2438
- setIsFastStrategy(isFast) {
2439
- this.isFastIdStrategy = isFast;
2440
- }
2441
- uuidv4() {
2442
- if (this.isFastIdStrategy) {
2443
- this.fastIdStart++;
2444
- return String(this.fastIdStart);
2445
- //@ts-ignore
2446
- }
2447
- else if (window.crypto && window.crypto.getRandomValues) {
2448
- //@ts-ignore
2449
- return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
2450
- }
2451
- else {
2452
- // mainly for jest and other browsers that do not have the crypto functionality
2453
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
2454
- var r = (Math.random() * 16) | 0, v = c == "x" ? r : (r & 0x3) | 0x8;
2455
- return v.toString(16);
2456
- });
2457
- }
2458
- }
2459
- }
2460
-
2461
2218
  /**
2462
2219
  * Convert from a cartesian reference to a Zone
2463
2220
  * The range boundaries will be kept in the same order as the
@@ -3064,6 +2821,287 @@
3064
2821
  return set;
3065
2822
  }
3066
2823
 
2824
+ class RangeImpl {
2825
+ getSheetSize;
2826
+ _zone;
2827
+ parts;
2828
+ invalidXc;
2829
+ prefixSheet = false;
2830
+ sheetId; // the sheet on which the range is defined
2831
+ invalidSheetName; // the name of any sheet that is invalid
2832
+ constructor(args, getSheetSize) {
2833
+ this.getSheetSize = getSheetSize;
2834
+ this._zone = args.zone;
2835
+ this.parts = args.parts;
2836
+ this.prefixSheet = args.prefixSheet;
2837
+ this.invalidXc = args.invalidXc;
2838
+ this.sheetId = args.sheetId;
2839
+ this.invalidSheetName = args.invalidSheetName;
2840
+ }
2841
+ static fromRange(range, getters) {
2842
+ if (range instanceof RangeImpl) {
2843
+ return range;
2844
+ }
2845
+ return new RangeImpl(range, getters.getSheetSize);
2846
+ }
2847
+ get unboundedZone() {
2848
+ return this._zone;
2849
+ }
2850
+ get zone() {
2851
+ const { left, top, bottom, right } = this._zone;
2852
+ if (right !== undefined && bottom !== undefined)
2853
+ return { left, top, right, bottom };
2854
+ else if (bottom === undefined && right !== undefined) {
2855
+ return { right, top, left, bottom: this.getSheetSize(this.sheetId).numberOfRows - 1 };
2856
+ }
2857
+ else if (right === undefined && bottom !== undefined) {
2858
+ return { bottom, left, top, right: this.getSheetSize(this.sheetId).numberOfCols - 1 };
2859
+ }
2860
+ throw new Error(_lt("Bad zone format"));
2861
+ }
2862
+ static getRangeParts(xc, zone) {
2863
+ const parts = xc.split(":").map((p) => {
2864
+ const isFullRow = isRowReference(p);
2865
+ return {
2866
+ colFixed: isFullRow ? false : p.startsWith("$"),
2867
+ rowFixed: isFullRow ? p.startsWith("$") : p.includes("$", 1),
2868
+ };
2869
+ });
2870
+ const isFullCol = zone.bottom === undefined;
2871
+ const isFullRow = zone.right === undefined;
2872
+ if (isFullCol) {
2873
+ parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2874
+ parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2875
+ if (zone.left === zone.right) {
2876
+ parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2877
+ parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2878
+ }
2879
+ }
2880
+ if (isFullRow) {
2881
+ parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
2882
+ parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
2883
+ if (zone.top === zone.bottom) {
2884
+ parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2885
+ parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
2886
+ }
2887
+ }
2888
+ return parts;
2889
+ }
2890
+ get isFullCol() {
2891
+ return this._zone.bottom === undefined;
2892
+ }
2893
+ get isFullRow() {
2894
+ return this._zone.right === undefined;
2895
+ }
2896
+ get rangeData() {
2897
+ return {
2898
+ _zone: this._zone,
2899
+ _sheetId: this.sheetId,
2900
+ };
2901
+ }
2902
+ /**
2903
+ * Check that a zone is valid regarding the order of top-bottom and left-right.
2904
+ * Left should be smaller than right, top should be smaller than bottom.
2905
+ * If it's not the case, simply invert them, and invert the linked parts
2906
+ */
2907
+ orderZone() {
2908
+ const zone = { ...this._zone };
2909
+ let parts = this.parts;
2910
+ if (zone.right !== undefined && zone.right < zone.left) {
2911
+ let right = zone.right;
2912
+ zone.right = zone.left;
2913
+ zone.left = right;
2914
+ parts = [
2915
+ {
2916
+ colFixed: parts[1]?.colFixed || false,
2917
+ rowFixed: parts[0]?.rowFixed || false,
2918
+ },
2919
+ {
2920
+ colFixed: parts[0]?.colFixed || false,
2921
+ rowFixed: parts[1]?.rowFixed || false,
2922
+ },
2923
+ ];
2924
+ }
2925
+ if (zone.bottom !== undefined && zone.bottom < zone.top) {
2926
+ let bottom = zone.bottom;
2927
+ zone.bottom = zone.top;
2928
+ zone.top = bottom;
2929
+ parts = [
2930
+ {
2931
+ colFixed: parts[0]?.colFixed || false,
2932
+ rowFixed: parts[1]?.rowFixed || false,
2933
+ },
2934
+ {
2935
+ colFixed: parts[1]?.colFixed || false,
2936
+ rowFixed: parts[0]?.rowFixed || false,
2937
+ },
2938
+ ];
2939
+ }
2940
+ return this.clone({ zone, parts });
2941
+ }
2942
+ /**
2943
+ *
2944
+ * @param rangeParams optional, values to put in the cloned range instead of the current values of the range
2945
+ */
2946
+ clone(rangeParams) {
2947
+ return new RangeImpl({
2948
+ zone: rangeParams?.zone ? rangeParams.zone : { ...this._zone },
2949
+ sheetId: rangeParams?.sheetId ? rangeParams.sheetId : this.sheetId,
2950
+ invalidSheetName: rangeParams && "invalidSheetName" in rangeParams // 'attr in obj' instead of just 'obj.attr' because we accept undefined values
2951
+ ? rangeParams.invalidSheetName
2952
+ : this.invalidSheetName,
2953
+ invalidXc: rangeParams && "invalidXc" in rangeParams ? rangeParams.invalidXc : this.invalidXc,
2954
+ parts: rangeParams?.parts
2955
+ ? rangeParams.parts
2956
+ : this.parts.map((part) => {
2957
+ return { rowFixed: part.rowFixed, colFixed: part.colFixed };
2958
+ }),
2959
+ prefixSheet: rangeParams?.prefixSheet ? rangeParams.prefixSheet : this.prefixSheet,
2960
+ }, this.getSheetSize);
2961
+ }
2962
+ }
2963
+ /**
2964
+ * Copy a range. If the range is on the sheetIdFrom, the range will target
2965
+ * sheetIdTo.
2966
+ */
2967
+ function copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) {
2968
+ const sheetId = range.sheetId === sheetIdFrom ? sheetIdTo : range.sheetId;
2969
+ return range.clone({ sheetId });
2970
+ }
2971
+ /**
2972
+ * Create a range from a xc. If the xc is empty, this function returns undefined.
2973
+ */
2974
+ function createRange(getters, sheetId, range) {
2975
+ return range ? getters.getRangeFromSheetXC(sheetId, range) : undefined;
2976
+ }
2977
+ /**
2978
+ * Spread multiple colrows zone to one row/col zone and add a many new input range as needed.
2979
+ * For example, A1:B4 will become [A1:A4, B1:B4]
2980
+ */
2981
+ function spreadRange(ranges) {
2982
+ const postProcessedRanges = [];
2983
+ for (const range of ranges) {
2984
+ const { sheetName } = splitReference(range);
2985
+ const sheetPrefix = sheetName ? `${sheetName}!` : "";
2986
+ const zone = toUnboundedZone(range);
2987
+ if (zone.bottom !== zone.top && zone.left != zone.right) {
2988
+ if (zone.right) {
2989
+ for (let j = zone.left; j <= zone.right; ++j) {
2990
+ postProcessedRanges.push(`${sheetPrefix}${zoneToXc({
2991
+ left: j,
2992
+ right: j,
2993
+ top: zone.top,
2994
+ bottom: zone.bottom,
2995
+ })}`);
2996
+ }
2997
+ }
2998
+ else {
2999
+ for (let j = zone.top; j <= zone.bottom; ++j) {
3000
+ postProcessedRanges.push(`${sheetPrefix}${zoneToXc({
3001
+ left: zone.left,
3002
+ right: zone.right,
3003
+ top: j,
3004
+ bottom: j,
3005
+ })}`);
3006
+ }
3007
+ }
3008
+ }
3009
+ else {
3010
+ postProcessedRanges.push(range);
3011
+ }
3012
+ }
3013
+ return postProcessedRanges;
3014
+ }
3015
+
3016
+ /** Methods from Odoo Web Utils */
3017
+ /**
3018
+ * This function computes a score that represent the fact that the
3019
+ * string contains the pattern, or not
3020
+ *
3021
+ * - If the score is 0, the string does not contain the letters of the pattern in
3022
+ * the correct order.
3023
+ * - if the score is > 0, it actually contains the letters.
3024
+ *
3025
+ * Better matches will get a higher score: consecutive letters are better,
3026
+ * and a match closer to the beginning of the string is also scored higher.
3027
+ */
3028
+ function fuzzyMatch(pattern, str) {
3029
+ pattern = pattern.toLocaleLowerCase();
3030
+ str = str.toLocaleLowerCase();
3031
+ let totalScore = 0;
3032
+ let currentScore = 0;
3033
+ let len = str.length;
3034
+ let patternIndex = 0;
3035
+ for (let i = 0; i < len; i++) {
3036
+ if (str[i] === pattern[patternIndex]) {
3037
+ patternIndex++;
3038
+ currentScore += 100 + currentScore - i / 200;
3039
+ }
3040
+ else {
3041
+ currentScore = 0;
3042
+ }
3043
+ totalScore = totalScore + currentScore;
3044
+ }
3045
+ return patternIndex === pattern.length ? totalScore : 0;
3046
+ }
3047
+ /**
3048
+ * Return a list of things that matches a pattern, ordered by their 'score' (
3049
+ * higher score first). An higher score means that the match is better. For
3050
+ * example, consecutive letters are considered a better match.
3051
+ */
3052
+ function fuzzyLookup(pattern, list, fn) {
3053
+ const results = [];
3054
+ list.forEach((data) => {
3055
+ const score = fuzzyMatch(pattern, fn(data));
3056
+ if (score > 0) {
3057
+ results.push({ score, elem: data });
3058
+ }
3059
+ });
3060
+ // we want better matches first
3061
+ results.sort((a, b) => b.score - a.score);
3062
+ return results.map((r) => r.elem);
3063
+ }
3064
+
3065
+ function createDefaultRows(rowNumber) {
3066
+ const rows = [];
3067
+ for (let i = 0; i < rowNumber; i++) {
3068
+ const row = {
3069
+ cells: {},
3070
+ };
3071
+ rows.push(row);
3072
+ }
3073
+ return rows;
3074
+ }
3075
+
3076
+ /*
3077
+ * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
3078
+ * */
3079
+ class UuidGenerator {
3080
+ isFastIdStrategy = false;
3081
+ fastIdStart = 0;
3082
+ setIsFastStrategy(isFast) {
3083
+ this.isFastIdStrategy = isFast;
3084
+ }
3085
+ uuidv4() {
3086
+ if (this.isFastIdStrategy) {
3087
+ this.fastIdStart++;
3088
+ return String(this.fastIdStart);
3089
+ //@ts-ignore
3090
+ }
3091
+ else if (window.crypto && window.crypto.getRandomValues) {
3092
+ //@ts-ignore
3093
+ return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
3094
+ }
3095
+ else {
3096
+ // mainly for jest and other browsers that do not have the crypto functionality
3097
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
3098
+ var r = (Math.random() * 16) | 0, v = c == "x" ? r : (r & 0x3) | 0x8;
3099
+ return v.toString(16);
3100
+ });
3101
+ }
3102
+ }
3103
+ }
3104
+
3067
3105
  class ChartJsComponent extends owl.Component {
3068
3106
  static template = "o-spreadsheet-ChartJsComponent";
3069
3107
  canvas = owl.useRef("graphContainer");
@@ -5181,14 +5219,6 @@
5181
5219
  }
5182
5220
  }
5183
5221
  }
5184
- function getElementScrollTop(el) {
5185
- return el?.scrollTop || 0;
5186
- }
5187
- function setElementScrollTop(el, scroll) {
5188
- if (!el)
5189
- return;
5190
- el.scrollTop = scroll;
5191
- }
5192
5222
  function getOpenedMenus() {
5193
5223
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
5194
5224
  }
@@ -17643,50 +17673,51 @@
17643
17673
  css /* scss */ `
17644
17674
  .o-selection {
17645
17675
  .o-selection-input {
17646
- display: flex;
17647
- flex-direction: row;
17676
+ padding: 2px 0px;
17648
17677
 
17649
17678
  input {
17650
17679
  padding: 4px 6px;
17651
17680
  border-radius: 4px;
17652
17681
  box-sizing: border-box;
17653
- flex-grow: 2;
17654
17682
  }
17655
17683
  input:focus {
17656
17684
  outline: none;
17657
17685
  }
17658
17686
  input.o-required,
17659
17687
  input.o-focused {
17660
- border-width: 2px;
17661
- padding: 3px 5px;
17688
+ border: 1px solid;
17662
17689
  }
17663
17690
  input.o-focused {
17664
- border-color: ${SELECTION_BORDER_COLOR};
17691
+ border-width: 2px;
17692
+ padding: 3px 5px;
17665
17693
  }
17666
17694
  input.o-invalid {
17667
- border-color: red;
17695
+ /* The background-color is similar to the bootstrap alert-danger class but, because of the commit 0358a76d,
17696
+ * which avoids being parasitized by the dark-mode in spreadsheet, we cannot use this class.
17697
+ * TODO: Replace with the bootstrap alert-danger class when we support dark mode
17698
+ */
17699
+ background-color: #ffdddd;
17700
+ border-width: 2px;
17668
17701
  }
17669
17702
  button.o-btn {
17670
- background: transparent;
17671
- border: none;
17672
17703
  color: #333;
17673
- cursor: pointer;
17674
17704
  }
17675
17705
  button.o-btn-action {
17676
17706
  margin: 8px 1px;
17677
17707
  border-radius: 4px;
17678
- background: transparent;
17679
17708
  border: 1px solid #dadce0;
17680
17709
  color: #188038;
17681
- font-weight: bold;
17682
17710
  font-size: 14px;
17683
17711
  height: 25px;
17684
17712
  }
17713
+ .error-icon {
17714
+ right: 7px;
17715
+ top: 7px;
17716
+ }
17685
17717
  }
17686
17718
  /** Make the character a bit bigger
17687
17719
  compared to its neighbor INPUT box */
17688
17720
  .o-remove-selection {
17689
- font-weight: bold;
17690
17721
  font-size: calc(100% + 4px);
17691
17722
  }
17692
17723
  }
@@ -17702,7 +17733,7 @@
17702
17733
  class SelectionInput extends owl.Component {
17703
17734
  static template = "o-spreadsheet-SelectionInput";
17704
17735
  id = uuidGenerator$1.uuidv4();
17705
- previousRanges = this.props.ranges || [];
17736
+ previousRanges = this.props.ranges() || [];
17706
17737
  originSheet = this.env.model.getters.getActiveSheetId();
17707
17738
  state = owl.useState({
17708
17739
  isMissing: false,
@@ -17710,11 +17741,11 @@
17710
17741
  });
17711
17742
  focusedInput = owl.useRef("focusedInput");
17712
17743
  get ranges() {
17713
- const existingSelectionRange = this.env.model.getters.getSelectionInput(this.id);
17714
- const ranges = existingSelectionRange.length
17715
- ? existingSelectionRange
17716
- : this.props.ranges
17717
- ? this.props.ranges.map((xc, id) => ({
17744
+ const existingSelectionRanges = this.env.model.getters.getSelectionInput(this.id);
17745
+ const ranges = existingSelectionRanges.length
17746
+ ? existingSelectionRanges
17747
+ : this.props.ranges()
17748
+ ? this.props.ranges().map((xc, id) => ({
17718
17749
  xc,
17719
17750
  id,
17720
17751
  isFocused: false,
@@ -17734,6 +17765,12 @@
17734
17765
  get isInvalid() {
17735
17766
  return this.props.isInvalid || this.state.isMissing;
17736
17767
  }
17768
+ get isConfirmable() {
17769
+ return this.hasFocus && this.ranges.every((range) => range.isValidRange);
17770
+ }
17771
+ get isResettable() {
17772
+ return this.previousRanges.join() !== this.ranges.map((r) => r.xc).join();
17773
+ }
17737
17774
  setup() {
17738
17775
  owl.useEffect(() => this.focusedInput.el?.focus(), () => [this.focusedInput.el]);
17739
17776
  owl.onMounted(() => this.enableNewSelectionInput());
@@ -17743,7 +17780,7 @@
17743
17780
  enableNewSelectionInput() {
17744
17781
  this.env.model.dispatch("ENABLE_NEW_SELECTION_INPUT", {
17745
17782
  id: this.id,
17746
- initialRanges: this.props.ranges,
17783
+ initialRanges: this.props.ranges(),
17747
17784
  hasSingleRange: this.props.hasSingleRange,
17748
17785
  });
17749
17786
  }
@@ -17752,7 +17789,8 @@
17752
17789
  }
17753
17790
  checkChange() {
17754
17791
  const value = this.env.model.getters.getSelectionInputValue(this.id);
17755
- if (this.previousRanges.join() !== value.join()) {
17792
+ const valid = !this.isInvalid && this.ranges.every((range) => range.isValidRange);
17793
+ if (valid && this.previousRanges.join() !== value.join()) {
17756
17794
  this.triggerChange();
17757
17795
  }
17758
17796
  }
@@ -17763,7 +17801,6 @@
17763
17801
  triggerChange() {
17764
17802
  const ranges = this.env.model.getters.getSelectionInputValue(this.id);
17765
17803
  this.props.onSelectionChanged?.(ranges);
17766
- this.previousRanges = ranges;
17767
17804
  }
17768
17805
  onKeydown(ev) {
17769
17806
  if (ev.key === "F2") {
@@ -17813,10 +17850,37 @@
17813
17850
  });
17814
17851
  this.triggerChange();
17815
17852
  }
17853
+ reset() {
17854
+ const existingSelectionRanges = this.env.model.getters.getSelectionInput(this.id);
17855
+ for (const range of existingSelectionRanges) {
17856
+ this.env.model.dispatch("REMOVE_RANGE", {
17857
+ id: this.id,
17858
+ rangeId: range.id,
17859
+ });
17860
+ }
17861
+ for (const range of this.previousRanges) {
17862
+ this.env.model.dispatch("ADD_RANGE", { id: this.id, value: range });
17863
+ }
17864
+ this.confirm();
17865
+ }
17816
17866
  confirm() {
17817
- this.env.model.dispatch("UNFOCUS_SELECTION_INPUT");
17818
- const ranges = this.env.model.getters.getSelectionInputValue(this.id);
17819
- if (this.props.required && ranges.length === 0) {
17867
+ let anyValidInput = false;
17868
+ const existingSelectionRanges = this.env.model.getters.getSelectionInput(this.id);
17869
+ const existingSelectionXcs = [];
17870
+ for (const range of existingSelectionRanges) {
17871
+ if (range.xc === "") {
17872
+ this.env.model.dispatch("REMOVE_RANGE", {
17873
+ id: this.id,
17874
+ rangeId: range.id,
17875
+ });
17876
+ continue;
17877
+ }
17878
+ existingSelectionXcs.push(range.xc);
17879
+ if (this.env.model.getters.isRangeValid(range.xc)) {
17880
+ anyValidInput = true;
17881
+ }
17882
+ }
17883
+ if (this.props.required && !anyValidInput) {
17820
17884
  this.state.isMissing = true;
17821
17885
  }
17822
17886
  const activeSheetId = this.env.model.getters.getActiveSheetId();
@@ -17826,11 +17890,17 @@
17826
17890
  sheetIdTo: this.originSheet,
17827
17891
  });
17828
17892
  }
17893
+ this.props.onSelectionChanged?.(existingSelectionXcs);
17829
17894
  this.props.onSelectionConfirmed?.();
17895
+ this.previousRanges = this.props.ranges();
17896
+ if (existingSelectionXcs.join() !== this.previousRanges.join()) {
17897
+ this.enableNewSelectionInput();
17898
+ }
17899
+ this.env.model.dispatch("UNFOCUS_SELECTION_INPUT");
17830
17900
  }
17831
17901
  }
17832
17902
  SelectionInput.props = {
17833
- ranges: Array,
17903
+ ranges: Function,
17834
17904
  hasSingleRange: { type: Boolean, optional: true },
17835
17905
  required: { type: Boolean, optional: true },
17836
17906
  isInvalid: { type: Boolean, optional: true },
@@ -17866,7 +17936,7 @@
17866
17936
  return !!this.state.labelsDispatchResult?.isCancelledBecause(33 /* CommandResult.InvalidLabelRange */);
17867
17937
  }
17868
17938
  onUpdateDataSetsHaveTitle(ev) {
17869
- this.props.updateChart({
17939
+ this.props.updateChart(this.props.figureId, {
17870
17940
  dataSetsHaveTitle: ev.target.checked,
17871
17941
  });
17872
17942
  }
@@ -17878,10 +17948,14 @@
17878
17948
  this.dataSeriesRanges = ranges;
17879
17949
  }
17880
17950
  onDataSeriesConfirmed() {
17881
- this.state.datasetDispatchResult = this.props.updateChart({
17951
+ this.dataSeriesRanges = spreadRange(this.dataSeriesRanges);
17952
+ this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
17882
17953
  dataSets: this.dataSeriesRanges,
17883
17954
  });
17884
17955
  }
17956
+ getDataSeriesRanges() {
17957
+ return this.dataSeriesRanges;
17958
+ }
17885
17959
  /**
17886
17960
  * Change the local labelRange. The model should be updated when the
17887
17961
  * button "confirm" is clicked
@@ -17890,12 +17964,15 @@
17890
17964
  this.labelRange = ranges[0];
17891
17965
  }
17892
17966
  onLabelRangeConfirmed() {
17893
- this.state.labelsDispatchResult = this.props.updateChart({
17967
+ this.state.labelsDispatchResult = this.props.updateChart(this.props.figureId, {
17894
17968
  labelRange: this.labelRange,
17895
17969
  });
17896
17970
  }
17971
+ getLabelRange() {
17972
+ return this.labelRange || "";
17973
+ }
17897
17974
  onUpdateAggregated(ev) {
17898
- this.props.updateChart({
17975
+ this.props.updateChart(this.props.figureId, {
17899
17976
  aggregated: ev.target.checked,
17900
17977
  });
17901
17978
  }
@@ -17925,12 +18002,12 @@
17925
18002
  class BarConfigPanel extends LineBarPieConfigPanel {
17926
18003
  static template = "o-spreadsheet-BarConfigPanel";
17927
18004
  onUpdateStacked(ev) {
17928
- this.props.updateChart({
18005
+ this.props.updateChart(this.props.figureId, {
17929
18006
  stacked: ev.target.checked,
17930
18007
  });
17931
18008
  }
17932
18009
  onUpdateAggregated(ev) {
17933
- this.props.updateChart({
18010
+ this.props.updateChart(this.props.figureId, {
17934
18011
  aggregated: ev.target.checked,
17935
18012
  });
17936
18013
  }
@@ -18267,17 +18344,17 @@
18267
18344
  this.state.fillColorTool = !this.state.fillColorTool;
18268
18345
  }
18269
18346
  updateBackgroundColor(color) {
18270
- this.props.updateChart({
18347
+ this.props.updateChart(this.props.figureId, {
18271
18348
  background: color,
18272
18349
  });
18273
18350
  }
18274
18351
  updateTitle(ev) {
18275
- this.props.updateChart({
18352
+ this.props.updateChart(this.props.figureId, {
18276
18353
  title: ev.target.value,
18277
18354
  });
18278
18355
  }
18279
18356
  updateSelect(attr, ev) {
18280
- this.props.updateChart({
18357
+ this.props.updateChart(this.props.figureId, {
18281
18358
  [attr]: ev.target.value,
18282
18359
  });
18283
18360
  }
@@ -18310,10 +18387,13 @@
18310
18387
  this.dataRange = ranges[0];
18311
18388
  }
18312
18389
  updateDataRange() {
18313
- this.state.dataRangeDispatchResult = this.props.updateChart({
18390
+ this.state.dataRangeDispatchResult = this.props.updateChart(this.props.figureId, {
18314
18391
  dataRange: this.dataRange,
18315
18392
  });
18316
18393
  }
18394
+ getDataRange() {
18395
+ return this.dataRange || "";
18396
+ }
18317
18397
  }
18318
18398
  GaugeChartConfigPanel.props = {
18319
18399
  figureId: String,
@@ -18368,12 +18448,12 @@
18368
18448
  }
18369
18449
  updateBackgroundColor(color) {
18370
18450
  this.state.openedMenu = undefined;
18371
- this.props.updateChart({
18451
+ this.props.updateChart(this.props.figureId, {
18372
18452
  background: color,
18373
18453
  });
18374
18454
  }
18375
18455
  updateTitle(ev) {
18376
- this.props.updateChart({
18456
+ this.props.updateChart(this.props.figureId, {
18377
18457
  title: ev.target.value,
18378
18458
  });
18379
18459
  }
@@ -18438,7 +18518,7 @@
18438
18518
  }
18439
18519
  }
18440
18520
  updateSectionRule(sectionRule) {
18441
- this.state.sectionRuleDispatchResult = this.props.updateChart({
18521
+ this.state.sectionRuleDispatchResult = this.props.updateChart(this.props.figureId, {
18442
18522
  sectionRule,
18443
18523
  });
18444
18524
  }
@@ -18462,17 +18542,17 @@
18462
18542
  return false;
18463
18543
  }
18464
18544
  onUpdateLabelsAsText(ev) {
18465
- this.props.updateChart({
18545
+ this.props.updateChart(this.props.figureId, {
18466
18546
  labelsAsText: ev.target.checked,
18467
18547
  });
18468
18548
  }
18469
18549
  onUpdateStacked(ev) {
18470
- this.props.updateChart({
18550
+ this.props.updateChart(this.props.figureId, {
18471
18551
  stacked: ev.target.checked,
18472
18552
  });
18473
18553
  }
18474
18554
  onUpdateAggregated(ev) {
18475
- this.props.updateChart({
18555
+ this.props.updateChart(this.props.figureId, {
18476
18556
  aggregated: ev.target.checked,
18477
18557
  });
18478
18558
  }
@@ -18508,20 +18588,26 @@
18508
18588
  this.keyValue = ranges[0];
18509
18589
  }
18510
18590
  updateKeyValueRange() {
18511
- this.state.keyValueDispatchResult = this.props.updateChart({
18591
+ this.state.keyValueDispatchResult = this.props.updateChart(this.props.figureId, {
18512
18592
  keyValue: this.keyValue,
18513
18593
  });
18514
18594
  }
18595
+ getKeyValueRange() {
18596
+ return this.keyValue || "";
18597
+ }
18515
18598
  onBaselineRangeChanged(ranges) {
18516
18599
  this.baseline = ranges[0];
18517
18600
  }
18518
18601
  updateBaselineRange() {
18519
- this.state.baselineDispatchResult = this.props.updateChart({
18602
+ this.state.baselineDispatchResult = this.props.updateChart(this.props.figureId, {
18520
18603
  baseline: this.baseline,
18521
18604
  });
18522
18605
  }
18606
+ getBaselineRange() {
18607
+ return this.baseline || "";
18608
+ }
18523
18609
  updateBaselineMode(ev) {
18524
- this.props.updateChart({ baselineMode: ev.target.value });
18610
+ this.props.updateChart(this.props.figureId, { baselineMode: ev.target.value });
18525
18611
  }
18526
18612
  }
18527
18613
  ScorecardChartConfigPanel.props = {
@@ -18540,12 +18626,12 @@
18540
18626
  owl.useExternalListener(window, "click", this.closeMenus);
18541
18627
  }
18542
18628
  updateTitle(ev) {
18543
- this.props.updateChart({
18629
+ this.props.updateChart(this.props.figureId, {
18544
18630
  title: ev.target.value,
18545
18631
  });
18546
18632
  }
18547
18633
  updateBaselineDescr(ev) {
18548
- this.props.updateChart({ baselineDescr: ev.target.value });
18634
+ this.props.updateChart(this.props.figureId, { baselineDescr: ev.target.value });
18549
18635
  }
18550
18636
  toggleColorPicker(colorPickerId) {
18551
18637
  if (this.state.openedColorPicker === colorPickerId) {
@@ -18558,13 +18644,13 @@
18558
18644
  setColor(color, colorPickerId) {
18559
18645
  switch (colorPickerId) {
18560
18646
  case "backgroundColor":
18561
- this.props.updateChart({ background: color });
18647
+ this.props.updateChart(this.props.figureId, { background: color });
18562
18648
  break;
18563
18649
  case "baselineColorDown":
18564
- this.props.updateChart({ baselineColorDown: color });
18650
+ this.props.updateChart(this.props.figureId, { baselineColorDown: color });
18565
18651
  break;
18566
18652
  case "baselineColorUp":
18567
- this.props.updateChart({ baselineColorUp: color });
18653
+ this.props.updateChart(this.props.figureId, { baselineColorUp: color });
18568
18654
  break;
18569
18655
  }
18570
18656
  this.closeMenus();
@@ -18629,13 +18715,9 @@
18629
18715
  class ChartPanel extends owl.Component {
18630
18716
  static template = "o-spreadsheet-ChartPanel";
18631
18717
  state;
18632
- shouldUpdateChart = true;
18633
18718
  get figureId() {
18634
18719
  return this.state.figureId;
18635
18720
  }
18636
- get sheetId() {
18637
- return this.state.sheetId;
18638
- }
18639
18721
  setup() {
18640
18722
  const selectedFigureId = this.env.model.getters.getSelectedFigureId();
18641
18723
  if (!selectedFigureId) {
@@ -18645,17 +18727,11 @@
18645
18727
  this.state = owl.useState({
18646
18728
  panel: "configuration",
18647
18729
  figureId: selectedFigureId,
18648
- sheetId: this.env.model.getters.getActiveSheetId(),
18649
18730
  });
18650
18731
  owl.onWillUpdateProps(() => {
18651
18732
  const selectedFigureId = this.env.model.getters.getSelectedFigureId();
18652
18733
  if (selectedFigureId && selectedFigureId !== this.state.figureId) {
18653
18734
  this.state.figureId = selectedFigureId;
18654
- this.state.sheetId = this.env.model.getters.getActiveSheetId();
18655
- this.shouldUpdateChart = false;
18656
- }
18657
- else {
18658
- this.shouldUpdateChart = true;
18659
18735
  }
18660
18736
  if (!this.env.model.getters.isChartDefined(this.figureId)) {
18661
18737
  this.props.onCloseSidePanel();
@@ -18663,8 +18739,8 @@
18663
18739
  }
18664
18740
  });
18665
18741
  }
18666
- updateChart(updateDefinition) {
18667
- if (!this.shouldUpdateChart) {
18742
+ updateChart(figureId, updateDefinition) {
18743
+ if (figureId !== this.figureId) {
18668
18744
  return;
18669
18745
  }
18670
18746
  const definition = {
@@ -18673,8 +18749,8 @@
18673
18749
  };
18674
18750
  return this.env.model.dispatch("UPDATE_CHART", {
18675
18751
  definition,
18676
- id: this.figureId,
18677
- sheetId: this.sheetId,
18752
+ id: figureId,
18753
+ sheetId: this.env.model.getters.getFigureSheetId(figureId),
18678
18754
  });
18679
18755
  }
18680
18756
  onTypeChange(type) {
@@ -18686,7 +18762,7 @@
18686
18762
  this.env.model.dispatch("UPDATE_CHART", {
18687
18763
  definition,
18688
18764
  id: this.figureId,
18689
- sheetId: this.sheetId,
18765
+ sheetId: this.env.model.getters.getFigureSheetId(this.figureId),
18690
18766
  });
18691
18767
  }
18692
18768
  get chartPanel() {
@@ -20482,12 +20558,23 @@
20482
20558
  */
20483
20559
  selectRange(start, end) {
20484
20560
  let selection = window.getSelection();
20485
- this.removeSelection();
20486
- let range = document.createRange();
20561
+ const { start: currentStart, end: currentEnd } = this.getCurrentSelection();
20562
+ if (currentStart === start && currentEnd === end) {
20563
+ return;
20564
+ }
20565
+ const currentRange = selection.getRangeAt(0);
20566
+ let range;
20567
+ if (this.el.contains(currentRange.startContainer)) {
20568
+ range = currentRange;
20569
+ }
20570
+ else {
20571
+ range = document.createRange();
20572
+ selection.removeAllRanges();
20573
+ selection.addRange(range);
20574
+ }
20487
20575
  if (start == end && start === 0) {
20488
20576
  range.setStart(this.el, 0);
20489
20577
  range.setEnd(this.el, 0);
20490
- selection.addRange(range);
20491
20578
  }
20492
20579
  else {
20493
20580
  const textLength = this.getText().length;
@@ -20503,7 +20590,6 @@
20503
20590
  let startNode = this.findChildAtCharacterIndex(start);
20504
20591
  let endNode = this.findChildAtCharacterIndex(end);
20505
20592
  range.setStart(startNode.node, startNode.offset);
20506
- selection.addRange(range);
20507
20593
  selection.extend(endNode.node, endNode.offset);
20508
20594
  }
20509
20595
  }
@@ -20543,26 +20629,54 @@
20543
20629
  return { node: previous, offset: usedCharacters };
20544
20630
  }
20545
20631
  /**
20546
- * Sets (or Replaces all) the text inside the root element in the form of distinctive
20632
+ * Sets (or Replaces all) the text inside the root element in the form of distinctive paragraphs and
20547
20633
  * span for each element provided in `contents`.
20548
20634
  *
20635
+ * The function will apply the diff between the current content and the new content to avoid the systematic
20636
+ * destruction of DOM elements which interferes with IME[1]
20637
+ *
20638
+ * Each line of text will be encapsulated in a paragraph element.
20549
20639
  * Each span will have its own fontcolor and specific class if provided in the HtmlContent object.
20640
+ *
20641
+ * [1] https://developer.mozilla.org/en-US/docs/Glossary/Input_method_editor
20550
20642
  */
20551
20643
  setText(contents) {
20552
- this.el.innerHTML = "";
20553
20644
  if (contents.length === 0) {
20645
+ this.removeAll();
20554
20646
  return;
20555
20647
  }
20556
- for (const line of contents) {
20557
- const p = document.createElement("p");
20558
- // Empty line
20559
- if (line.length === 0 || line.every((content) => !content.value && !content.class)) {
20560
- p.appendChild(document.createElement("br"));
20561
- this.el.appendChild(p);
20562
- continue;
20648
+ const childElements = Array.from(this.el.childNodes);
20649
+ const contentLength = contents.length;
20650
+ for (let i = 0; i < contentLength; i++) {
20651
+ const line = contents[i];
20652
+ const childElement = childElements[i];
20653
+ let newChild = false;
20654
+ let p;
20655
+ if (childElement && childElement.nodeName === "P") {
20656
+ p = childElement;
20563
20657
  }
20564
- for (const content of line) {
20658
+ else {
20659
+ newChild = true;
20660
+ p = document.createElement("p");
20661
+ }
20662
+ const lineLength = line.length;
20663
+ const existingChildren = Array.from(p.childNodes);
20664
+ for (let j = 0; j < lineLength; j++) {
20665
+ const content = line[j];
20666
+ const child = existingChildren[j];
20667
+ // child nodes can be multiple types of nodes: Span, Text, Div, etc...
20668
+ // We can only modify a node in place if it has the same type as the content
20669
+ // that we would insert, which are spans.
20670
+ // Otherwise, it means that the node has been input by the user, through the keyboard or a copy/paste
20671
+ // @ts-ignore (somehow required because jest does not like child.tagName despite the prior check)
20672
+ const childIsSpan = child && "tagName" in child && child.tagName === "SPAN";
20673
+ if (childIsSpan && compareContentToSpanElement(content, child)) {
20674
+ continue;
20675
+ }
20676
+ // this is an empty line in the content
20565
20677
  if (!content.value && !content.class) {
20678
+ if (child)
20679
+ p.removeChild(child);
20566
20680
  continue;
20567
20681
  }
20568
20682
  const span = document.createElement("span");
@@ -20571,9 +20685,38 @@
20571
20685
  if (content.class) {
20572
20686
  span.classList.add(content.class);
20573
20687
  }
20688
+ if (child) {
20689
+ p.replaceChild(span, child);
20690
+ }
20691
+ else {
20692
+ p.appendChild(span);
20693
+ }
20694
+ }
20695
+ if (existingChildren.length > lineLength) {
20696
+ for (let i = lineLength; i < existingChildren.length; i++) {
20697
+ p.removeChild(existingChildren[i]);
20698
+ }
20699
+ }
20700
+ // Empty line
20701
+ if (!p.hasChildNodes()) {
20702
+ const span = document.createElement("span");
20703
+ span.appendChild(document.createElement("br"));
20574
20704
  p.appendChild(span);
20575
20705
  }
20576
- this.el.appendChild(p);
20706
+ // replace p if necessary
20707
+ if (newChild) {
20708
+ if (childElement) {
20709
+ this.el.replaceChild(p, childElement);
20710
+ }
20711
+ else {
20712
+ this.el.appendChild(p);
20713
+ }
20714
+ }
20715
+ }
20716
+ if (childElements.length > contentLength) {
20717
+ for (let i = contentLength; i < childElements.length; i++) {
20718
+ this.el.removeChild(childElements[i]);
20719
+ }
20577
20720
  }
20578
20721
  }
20579
20722
  scrollSelectionIntoView() {
@@ -20583,13 +20726,6 @@
20583
20726
  const element = focusedNode instanceof HTMLElement ? focusedNode : focusedNode.parentElement;
20584
20727
  element?.scrollIntoView({ block: "nearest" });
20585
20728
  }
20586
- /**
20587
- * remove the current selection of the user
20588
- * */
20589
- removeSelection() {
20590
- let selection = window.getSelection();
20591
- selection.removeAllRanges();
20592
- }
20593
20729
  removeAll() {
20594
20730
  if (this.el) {
20595
20731
  while (this.el.firstChild) {
@@ -20719,6 +20855,14 @@
20719
20855
  }
20720
20856
  return text;
20721
20857
  }
20858
+ }
20859
+ function compareContentToSpanElement(content, node) {
20860
+ const contentColor = content.color ? toHex(content.color) : "";
20861
+ const nodeColor = node.style?.color ? toHex(node.style.color) : "";
20862
+ const sameColor = contentColor === nodeColor;
20863
+ const sameClass = deepEquals([content.class], [...node.classList]);
20864
+ const sameContent = node.innerText === content.value;
20865
+ return sameColor && sameClass && sameContent;
20722
20866
  }
20723
20867
 
20724
20868
  // -----------------------------------------------------------------------------
@@ -20864,7 +21008,6 @@
20864
21008
  functionDescription: {},
20865
21009
  argToFocus: 0,
20866
21010
  });
20867
- isKeyStillDown = false;
20868
21011
  compositionActive = false;
20869
21012
  get assistantStyle() {
20870
21013
  if (this.props.delimitation && this.props.rect) {
@@ -20899,21 +21042,18 @@
20899
21042
  F2: () => console.warn("Not implemented"),
20900
21043
  F4: this.processF4Key,
20901
21044
  Tab: (ev) => this.processTabKey(ev),
21045
+ " ": (ev) => this.processSpaceKey(ev),
20902
21046
  };
20903
21047
  setup() {
20904
21048
  owl.onMounted(() => {
20905
21049
  const el = this.composerRef.el;
20906
21050
  this.contentHelper.updateEl(el);
20907
- this.processContent();
20908
- this.contentHelper.scrollSelectionIntoView();
20909
21051
  });
20910
21052
  owl.onWillUnmount(() => {
20911
21053
  this.props.onComposerUnmounted?.();
20912
21054
  });
20913
- owl.onPatched(() => {
20914
- if (!this.isKeyStillDown) {
20915
- this.processContent();
20916
- }
21055
+ owl.useEffect(() => {
21056
+ this.processContent();
20917
21057
  });
20918
21058
  }
20919
21059
  // ---------------------------------------------------------------------------
@@ -20966,32 +21106,25 @@
20966
21106
  return;
20967
21107
  }
20968
21108
  }
20969
- else {
20970
- // when completing with tab, if there is no value to complete, the active cell will be moved to the right.
20971
- // we can't let the model think that it is for a ref selection.
20972
- // todo: check if this can be removed someday
20973
- this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
20974
- }
20975
21109
  const direction = ev.shiftKey ? "left" : "right";
20976
21110
  this.env.model.dispatch("STOP_EDITION");
20977
21111
  this.env.model.selection.moveAnchorCell(direction, 1);
20978
21112
  }
21113
+ processSpaceKey(ev) {
21114
+ if (ev.ctrlKey) {
21115
+ ev.preventDefault();
21116
+ ev.stopPropagation();
21117
+ this.showAutocomplete("");
21118
+ this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
21119
+ }
21120
+ }
20979
21121
  processEnterKey(ev) {
20980
21122
  ev.preventDefault();
20981
21123
  ev.stopPropagation();
20982
21124
  if (ev.altKey || ev.ctrlKey) {
20983
- const selection = this.contentHelper.getCurrentSelection();
20984
- const currentContent = this.env.model.getters.getCurrentContent();
20985
- const content = currentContent.slice(0, selection.start) + NEWLINE + currentContent.slice(selection.end);
20986
- this.env.model.dispatch("SET_CURRENT_CONTENT", {
20987
- content,
20988
- selection: { start: selection.start + 1, end: selection.start + 1 },
20989
- });
20990
- this.processContent();
20991
- this.contentHelper.scrollSelectionIntoView();
21125
+ this.composerRef.el?.dispatchEvent(new InputEvent("input", { inputType: "insertParagraph", bubbles: true, isComposing: false }));
20992
21126
  return;
20993
21127
  }
20994
- this.isKeyStillDown = false;
20995
21128
  if (this.autoCompleteState.showProvider) {
20996
21129
  const autoCompleteValue = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]?.text;
20997
21130
  if (autoCompleteValue) {
@@ -21024,56 +21157,42 @@
21024
21157
  else {
21025
21158
  ev.stopPropagation();
21026
21159
  }
21027
- const { start, end } = this.contentHelper.getCurrentSelection();
21028
- if (!this.env.model.getters.isSelectingForComposer() &&
21029
- !(ev.key === "Enter" && (ev.altKey || ev.ctrlKey))) {
21030
- this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", { start, end });
21031
- this.isKeyStillDown = true;
21032
- }
21033
21160
  }
21034
21161
  /*
21035
21162
  * Triggered automatically by the content-editable between the keydown and key up
21036
21163
  * */
21037
- onInput() {
21164
+ onInput(ev) {
21038
21165
  if (this.props.focus === "inactive" || !this.shouldProcessInputEvents) {
21039
21166
  return;
21040
21167
  }
21168
+ let content = this.contentHelper.getText();
21169
+ let selection = this.contentHelper.getCurrentSelection();
21170
+ if (ev.inputType === "insertParagraph") {
21171
+ const start = Math.min(selection.start, selection.end);
21172
+ const end = Math.max(selection.start, selection.end);
21173
+ content = content.slice(0, start) + NEWLINE + content.slice(end);
21174
+ selection = { start: start + 1, end: start + 1 };
21175
+ }
21041
21176
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
21042
21177
  this.env.model.dispatch("SET_CURRENT_CONTENT", {
21043
- content: this.contentHelper.getText(),
21044
- selection: this.contentHelper.getCurrentSelection(),
21178
+ content,
21179
+ selection,
21045
21180
  });
21181
+ this.processTokenAtCursor();
21046
21182
  }
21047
- onKeyup(ev) {
21048
- this.isKeyStillDown = false;
21049
- if (this.props.focus === "inactive" ||
21050
- ["Control", "Alt", "Shift", "Tab", "Enter", "F4"].includes(ev.key)) {
21051
- return;
21052
- }
21053
- if (this.autoCompleteState.showProvider && ["ArrowUp", "ArrowDown"].includes(ev.key)) {
21054
- return; // already processed in keydown
21055
- }
21056
- if (this.env.model.getters.isSelectingForComposer() &&
21057
- ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(ev.key)) {
21058
- return; // already processed in keydown
21059
- }
21060
- ev.preventDefault();
21061
- ev.stopPropagation();
21062
- this.autoCompleteState.showProvider = false;
21063
- if (ev.ctrlKey && ev.key === " ") {
21064
- this.showAutocomplete("");
21065
- this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
21066
- return;
21067
- }
21068
- const { start: oldStart, end: oldEnd } = this.env.model.getters.getComposerSelection();
21069
- const { start, end } = this.contentHelper.getCurrentSelection();
21070
- if (start !== oldStart || end !== oldEnd) {
21071
- this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", this.contentHelper.getCurrentSelection());
21183
+ onKeyup() {
21184
+ if (this.contentHelper.el === document.activeElement) {
21185
+ const { start: oldStart, end: oldEnd } = this.env.model.getters.getComposerSelection();
21186
+ const { start, end } = this.contentHelper.getCurrentSelection();
21187
+ if (start !== oldStart || end !== oldEnd) {
21188
+ this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", { start, end });
21189
+ }
21072
21190
  }
21073
- this.processTokenAtCursor();
21074
- this.processContent();
21075
21191
  }
21076
21192
  showAutocomplete(searchTerm) {
21193
+ if (!this.env.model.getters.getCurrentContent().startsWith("=")) {
21194
+ return;
21195
+ }
21077
21196
  this.autoCompleteState.showProvider = true;
21078
21197
  let values = Object.entries(functionRegistry.content)
21079
21198
  .filter(([_, { hidden }]) => !hidden)
@@ -21093,13 +21212,6 @@
21093
21212
  this.autoCompleteState.values = values.slice(0, 10);
21094
21213
  this.autoCompleteState.selectedIndex = 0;
21095
21214
  }
21096
- onMousedown(ev) {
21097
- if (ev.button > 0) {
21098
- // not main button, probably a context menu
21099
- return;
21100
- }
21101
- this.contentHelper.removeSelection();
21102
- }
21103
21215
  onClick() {
21104
21216
  if (this.env.model.getters.isReadonly()) {
21105
21217
  return;
@@ -21112,9 +21224,6 @@
21112
21224
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", newSelection);
21113
21225
  this.processTokenAtCursor();
21114
21226
  }
21115
- onBlur() {
21116
- this.isKeyStillDown = false;
21117
- }
21118
21227
  // ---------------------------------------------------------------------------
21119
21228
  // Private
21120
21229
  // ---------------------------------------------------------------------------
@@ -21122,22 +21231,19 @@
21122
21231
  if (this.compositionActive) {
21123
21232
  return;
21124
21233
  }
21125
- const oldScroll = getElementScrollTop(this.composerRef.el);
21126
- this.contentHelper.removeAll(); // removes the content of the composer, to be added just after
21127
21234
  this.shouldProcessInputEvents = false;
21128
21235
  if (this.props.focus !== "inactive") {
21129
21236
  this.contentHelper.el.focus();
21130
- this.contentHelper.selectRange(0, 0); // move the cursor inside the composer at 0 0.
21131
21237
  }
21132
21238
  const content = this.getContentLines();
21239
+ this.contentHelper.setText(content);
21133
21240
  if (content.length !== 0 && content.length[0] !== 0) {
21134
- this.contentHelper.setText(content);
21135
- const { start, end } = this.env.model.getters.getComposerSelection();
21136
21241
  if (this.props.focus !== "inactive") {
21137
21242
  // Put the cursor back where it was before the rendering
21243
+ const { start, end } = this.env.model.getters.getComposerSelection();
21138
21244
  this.contentHelper.selectRange(start, end);
21139
21245
  }
21140
- setElementScrollTop(this.composerRef.el, oldScroll);
21246
+ this.contentHelper.scrollSelectionIntoView();
21141
21247
  }
21142
21248
  this.shouldProcessInputEvents = true;
21143
21249
  }
@@ -21392,8 +21498,8 @@
21392
21498
  height: el.clientHeight,
21393
21499
  };
21394
21500
  this.composerState.delimitation = {
21395
- width: el.parentElement.clientWidth,
21396
- height: el.parentElement.clientHeight,
21501
+ width: this.props.gridDims.width,
21502
+ height: this.props.gridDims.height,
21397
21503
  };
21398
21504
  });
21399
21505
  owl.onWillUpdateProps(() => {
@@ -23111,7 +23217,7 @@
23111
23217
  shiftingMode: "none",
23112
23218
  });
23113
23219
  onResizeHighlight(isLeft, isTop) {
23114
- const activeSheet = this.env.model.getters.getActiveSheet();
23220
+ const activeSheetId = this.env.model.getters.getActiveSheetId();
23115
23221
  this.highlightState.shiftingMode = "isResizing";
23116
23222
  const z = this.props.zone;
23117
23223
  const pivotCol = isLeft ? z.right : z.left;
@@ -23120,11 +23226,10 @@
23120
23226
  let lastRow = isTop ? z.top : z.bottom;
23121
23227
  let currentZone = z;
23122
23228
  this.env.model.dispatch("START_CHANGE_HIGHLIGHT", {
23123
- range: this.env.model.getters.getRangeDataFromZone(activeSheet.id, currentZone),
23229
+ range: this.env.model.getters.getRangeDataFromZone(activeSheetId, currentZone),
23124
23230
  });
23125
23231
  const mouseMove = (col, row) => {
23126
23232
  if (lastCol !== col || lastRow !== row) {
23127
- const activeSheetId = this.env.model.getters.getActiveSheetId();
23128
23233
  lastCol = clip(col === -1 ? lastCol : col, 0, this.env.model.getters.getNumberCols(activeSheetId) - 1);
23129
23234
  lastRow = clip(row === -1 ? lastRow : row, 0, this.env.model.getters.getNumberRows(activeSheetId) - 1);
23130
23235
  let newZone = {
@@ -23136,7 +23241,7 @@
23136
23241
  newZone = this.env.model.getters.expandZone(activeSheetId, newZone);
23137
23242
  if (!isEqual(newZone, currentZone)) {
23138
23243
  this.env.model.dispatch("CHANGE_HIGHLIGHT", {
23139
- range: this.env.model.getters.getRangeDataFromZone(activeSheet.id, newZone),
23244
+ range: this.env.model.getters.getRangeFromZone(activeSheetId, this.env.model.getters.getUnboundedZone(activeSheetId, newZone)).rangeData,
23140
23245
  });
23141
23246
  currentZone = newZone;
23142
23247
  }
@@ -23183,7 +23288,7 @@
23183
23288
  newZone = this.env.model.getters.expandZone(activeSheetId, newZone);
23184
23289
  if (!isEqual(newZone, currentZone)) {
23185
23290
  this.env.model.dispatch("CHANGE_HIGHLIGHT", {
23186
- range: this.env.model.getters.getRangeDataFromZone(activeSheetId, newZone),
23291
+ range: this.env.model.getters.getRangeFromZone(activeSheetId, this.env.model.getters.getUnboundedZone(activeSheetId, newZone)).rangeData,
23187
23292
  });
23188
23293
  currentZone = newZone;
23189
23294
  }
@@ -24505,6 +24610,16 @@
24505
24610
  63: "333333",
24506
24611
  64: "000000",
24507
24612
  65: "FFFFFF", // system background
24613
+ };
24614
+ const IMAGE_MIMETYPE_EXTENSION_MAPPING = {
24615
+ "image/avif": "avif",
24616
+ "image/bmp": "bmp",
24617
+ "image/gif": "gif",
24618
+ "image/vnd.microsoft.icon": "ico",
24619
+ "image/jpeg": "jpeg",
24620
+ "image/png": "png",
24621
+ "image/tiff": "tiff",
24622
+ "image/webp": "webp",
24508
24623
  };
24509
24624
 
24510
24625
  /**
@@ -26987,6 +27102,11 @@
26987
27102
  <Override ContentType="${contentType}" PartName="${partName}" />
26988
27103
  `;
26989
27104
  }
27105
+ function createDefaultXMLElement(extension, contentType) {
27106
+ return escapeXml /*xml*/ `
27107
+ <Default Extension="${extension}" ContentType="${contentType}" />
27108
+ `;
27109
+ }
26990
27110
  function joinXmlNodes(xmlNodes) {
26991
27111
  return new XMLString(xmlNodes.join("\n"));
26992
27112
  }
@@ -28852,11 +28972,9 @@
28852
28972
  */
28853
28973
  class CorePlugin extends BasePlugin {
28854
28974
  getters;
28855
- range;
28856
28975
  uuidGenerator;
28857
28976
  constructor({ getters, stateObserver, range, dispatch, uuidGenerator }) {
28858
28977
  super(stateObserver, dispatch);
28859
- this.range = range;
28860
28978
  range.addRangeProvider(this.adaptRanges.bind(this));
28861
28979
  this.getters = getters;
28862
28980
  this.uuidGenerator = uuidGenerator;
@@ -31430,6 +31548,7 @@
31430
31548
  "getMerge",
31431
31549
  "getMergesInZone",
31432
31550
  "isSingleCellOrMerge",
31551
+ "getSelectionRangeString",
31433
31552
  ];
31434
31553
  nextId = 1;
31435
31554
  merges = {};
@@ -31517,6 +31636,26 @@
31517
31636
  .map((mergeId) => this.getMergeById(sheetId, mergeId))
31518
31637
  .filter(isDefined$1);
31519
31638
  }
31639
+ /**
31640
+ * Same as `getRangeString` but add all necessary merge to the range to make it a valid selection
31641
+ */
31642
+ getSelectionRangeString(range, forSheetId) {
31643
+ const rangeImpl = RangeImpl.fromRange(range, this.getters);
31644
+ const expandedZone = this.getters.expandZone(rangeImpl.sheetId, rangeImpl.zone);
31645
+ const expandedRange = rangeImpl.clone({
31646
+ zone: {
31647
+ ...expandedZone,
31648
+ bottom: rangeImpl.isFullCol ? undefined : expandedZone.bottom,
31649
+ right: rangeImpl.isFullRow ? undefined : expandedZone.right,
31650
+ },
31651
+ });
31652
+ const rangeString = this.getters.getRangeString(expandedRange, forSheetId);
31653
+ if (this.isSingleCellOrMerge(rangeImpl.sheetId, rangeImpl.zone)) {
31654
+ const { sheetName, xc } = splitReference(rangeString);
31655
+ return `${sheetName !== undefined ? getCanonicalSheetName(sheetName) + "!" : ""}${xc.split(":")[0]}`;
31656
+ }
31657
+ return rangeString;
31658
+ }
31520
31659
  /**
31521
31660
  * Return true if the zone intersects an existing merge:
31522
31661
  * if they have at least a common cell
@@ -31849,12 +31988,12 @@
31849
31988
  }
31850
31989
  static getters = [
31851
31990
  "getRangeString",
31852
- "getSelectionRangeString",
31853
31991
  "getRangeFromSheetXC",
31854
31992
  "createAdaptedRanges",
31855
31993
  "getRangeDataFromXc",
31856
31994
  "getRangeDataFromZone",
31857
31995
  "getRangeFromRangeData",
31996
+ "getRangeFromZone",
31858
31997
  ];
31859
31998
  // ---------------------------------------------------------------------------
31860
31999
  // Command Handling
@@ -32103,21 +32242,6 @@
32103
32242
  const rangeInterface = { prefixSheet, zone, sheetId, invalidSheetName, parts };
32104
32243
  return new RangeImpl(rangeInterface, this.getters.getSheetSize).orderZone();
32105
32244
  }
32106
- /**
32107
- * Same as `getRangeString` but add all necessary merge to the range to make it a valid selection
32108
- */
32109
- getSelectionRangeString(range, forSheetId) {
32110
- const rangeImpl = RangeImpl.fromRange(range, this.getters);
32111
- const expandedZone = this.getters.expandZone(rangeImpl.sheetId, rangeImpl.zone);
32112
- const expandedRange = rangeImpl.clone({
32113
- zone: {
32114
- ...expandedZone,
32115
- bottom: rangeImpl.isFullCol ? undefined : expandedZone.bottom,
32116
- right: rangeImpl.isFullRow ? undefined : expandedZone.right,
32117
- },
32118
- });
32119
- return this.getRangeString(expandedRange, forSheetId);
32120
- }
32121
32245
  /**
32122
32246
  * Gets the string that represents the range as it is at the moment of the call.
32123
32247
  * The string will be prefixed with the sheet name if the call specified a sheet id in `forSheetId`
@@ -32174,6 +32298,17 @@
32174
32298
  getRangeDataFromZone(sheetId, zone) {
32175
32299
  return { _sheetId: sheetId, _zone: zone };
32176
32300
  }
32301
+ getRangeFromZone(sheetId, zone) {
32302
+ return new RangeImpl({
32303
+ sheetId,
32304
+ zone,
32305
+ parts: [
32306
+ { colFixed: false, rowFixed: false },
32307
+ { colFixed: false, rowFixed: false },
32308
+ ],
32309
+ prefixSheet: false,
32310
+ }, this.getters.getSheetSize);
32311
+ }
32177
32312
  getRangeFromRangeData(data) {
32178
32313
  const rangeInterface = {
32179
32314
  prefixSheet: false,
@@ -32259,6 +32394,7 @@
32259
32394
  "getPaneDivisions",
32260
32395
  "checkZonesExistInSheet",
32261
32396
  "getCommandZones",
32397
+ "getUnboundedZone",
32262
32398
  ];
32263
32399
  sheetIdsMapName = {};
32264
32400
  orderedSheetIds = [];
@@ -32584,6 +32720,16 @@
32584
32720
  right: this.getNumberCols(sheetId) - 1,
32585
32721
  };
32586
32722
  }
32723
+ getUnboundedZone(sheetId, zone) {
32724
+ const isFullRow = zone.left === 0 && zone.right === this.getNumberCols(sheetId) - 1;
32725
+ const isFullCol = zone.top === 0 && zone.bottom === this.getNumberRows(sheetId) - 1;
32726
+ return {
32727
+ ...zone,
32728
+ bottom: isFullCol ? undefined : zone.bottom,
32729
+ // cannot be unbounded in the 2 dimensions at once
32730
+ right: isFullRow && !isFullCol ? undefined : zone.right,
32731
+ };
32732
+ }
32587
32733
  getPaneDivisions(sheetId) {
32588
32734
  return this.getSheet(sheetId).panes;
32589
32735
  }
@@ -38619,6 +38765,7 @@
38619
38765
  // ---------------------------------------------------------------------------
38620
38766
  allowDispatch(cmd) {
38621
38767
  switch (cmd.type) {
38768
+ case "ADD_RANGE":
38622
38769
  case "ADD_EMPTY_RANGE":
38623
38770
  if (this.inputHasSingleRange && this.ranges.length === 1) {
38624
38771
  return 30 /* CommandResult.MaximumRangesReached */;
@@ -38633,11 +38780,11 @@
38633
38780
  return 0 /* CommandResult.Success */;
38634
38781
  }
38635
38782
  handleEvent(event) {
38636
- const xc = zoneToXc(event.anchor.zone);
38637
38783
  const inputSheetId = this.activeSheet;
38638
38784
  const sheetId = this.getters.getActiveSheetId();
38639
- const sheetName = this.getters.getSheetName(sheetId);
38640
- this.add([sheetId === inputSheetId ? xc : `${getCanonicalSheetName(sheetName)}!${xc}`]);
38785
+ const zone = event.anchor.zone;
38786
+ const range = this.getters.getRangeFromZone(sheetId, event.type === "HeadersSelected" ? this.getters.getUnboundedZone(sheetId, zone) : zone);
38787
+ this.add([this.getters.getSelectionRangeString(range, inputSheetId)]);
38641
38788
  }
38642
38789
  handle(cmd) {
38643
38790
  switch (cmd.type) {
@@ -38663,6 +38810,10 @@
38663
38810
  this.insertNewRange(this.ranges.length, [""]);
38664
38811
  this.focusLast();
38665
38812
  break;
38813
+ case "ADD_RANGE":
38814
+ this.insertNewRange(this.ranges.length, [cmd.value]);
38815
+ this.focusLast();
38816
+ break;
38666
38817
  case "REMOVE_RANGE":
38667
38818
  const index = this.getIndex(cmd.rangeId);
38668
38819
  if (index !== null) {
@@ -38874,6 +39025,7 @@
38874
39025
  case "UNFOCUS_SELECTION_INPUT":
38875
39026
  this.unfocus();
38876
39027
  break;
39028
+ case "ADD_RANGE":
38877
39029
  case "ADD_EMPTY_RANGE":
38878
39030
  case "REMOVE_RANGE":
38879
39031
  if (cmd.id !== this.focusedInputId) {
@@ -38887,9 +39039,11 @@
38887
39039
  if (cmd.id !== this.focusedInputId) {
38888
39040
  const input = this.inputs[cmd.id];
38889
39041
  const range = input.ranges.find((range) => range.id === cmd.rangeId);
38890
- const sheetId = this.getters.getActiveSheetId();
38891
- const zone = this.getters.getRangeFromSheetXC(sheetId, range?.xc || "A1").zone;
38892
- this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, { handleEvent: input.handleEvent.bind(input) });
39042
+ if (this.isRangeValid(range?.xc || "A1")) {
39043
+ const sheetId = this.getters.getActiveSheetId();
39044
+ const zone = this.getters.getRangeFromSheetXC(sheetId, range?.xc || "A1").zone;
39045
+ this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, { handleEvent: input.handleEvent.bind(input) });
39046
+ }
38893
39047
  this.focusedInputId = cmd.id;
38894
39048
  }
38895
39049
  break;
@@ -41088,12 +41242,20 @@
41088
41242
  if (this.mode !== "selecting") {
41089
41243
  return;
41090
41244
  }
41245
+ const sheetId = this.getters.getActiveSheetId();
41246
+ let unboundedZone;
41247
+ if (event.type === "HeadersSelected") {
41248
+ unboundedZone = this.getters.getUnboundedZone(sheetId, event.anchor.zone);
41249
+ }
41250
+ else {
41251
+ unboundedZone = event.anchor.zone;
41252
+ }
41091
41253
  switch (event.mode) {
41092
41254
  case "newAnchor":
41093
- this.insertSelectedRange(event.anchor.zone);
41255
+ this.insertSelectedRange(unboundedZone);
41094
41256
  break;
41095
41257
  default:
41096
- this.replaceSelectedRanges(event.anchor.zone);
41258
+ this.replaceSelectedRanges(unboundedZone);
41097
41259
  break;
41098
41260
  }
41099
41261
  }
@@ -41150,7 +41312,9 @@
41150
41312
  }
41151
41313
  break;
41152
41314
  case "START_CHANGE_HIGHLIGHT":
41315
+ // FIXME: thiws whole ordeal could be handled with the Selection Processor which would extend the feature to selection inputs
41153
41316
  this.dispatch("STOP_COMPOSER_RANGE_SELECTION");
41317
+ // FIXME: we should check range SheetId compared to this.activeSheetId r maybe not have a sheetId in the payload ??
41154
41318
  const range = this.getters.getRangeFromRangeData(cmd.range);
41155
41319
  const previousRefToken = this.currentTokens
41156
41320
  .filter((token) => token.type === "REFERENCE")
@@ -41504,14 +41668,11 @@
41504
41668
  const ref = this.getZoneReference(zone);
41505
41669
  this.replaceText(ref, this.selectionInitialStart, this.selectionEnd);
41506
41670
  }
41507
- getZoneReference(zone, fixedParts = [{ colFixed: false, rowFixed: false }]) {
41671
+ getZoneReference(zone) {
41672
+ const inputSheetId = this.getters.getCurrentEditedCell().sheetId;
41508
41673
  const sheetId = this.getters.getActiveSheetId();
41509
- let selectedXc = this.getters.zoneToXC(sheetId, zone, fixedParts);
41510
- if (this.getters.getCurrentEditedCell().sheetId !== this.getters.getActiveSheetId()) {
41511
- const sheetName = getCanonicalSheetName(this.getters.getSheetName(this.getters.getActiveSheetId()));
41512
- selectedXc = `${sheetName}!${selectedXc}`;
41513
- }
41514
- return selectedXc;
41674
+ const range = this.getters.getRangeFromZone(sheetId, zone);
41675
+ return this.getters.getSelectionRangeString(range, inputSheetId);
41515
41676
  }
41516
41677
  getRangeReference(range, fixedParts = [{ colFixed: false, rowFixed: false }]) {
41517
41678
  let _fixedParts = [...fixedParts];
@@ -42365,7 +42526,7 @@
42365
42526
  const file = await this.getImageFromUser();
42366
42527
  const path = await this.fileStore.upload(file);
42367
42528
  const size = await this.getImageSize(path);
42368
- return { path, size };
42529
+ return { path, size, mimetype: file.type };
42369
42530
  }
42370
42531
  getImageFromUser() {
42371
42532
  return new Promise((resolve, reject) => {
@@ -47432,8 +47593,7 @@
47432
47593
  // Figures and Charts
47433
47594
  let drawingNode = escapeXml ``;
47434
47595
  const drawingRelIds = [];
47435
- const charts = sheet.charts;
47436
- for (const chart of charts) {
47596
+ for (const chart of sheet.charts) {
47437
47597
  const xlsxChartId = convertChartId(chart.id);
47438
47598
  const chartRelId = addRelsToFile(construct.relsFiles, `xl/drawings/_rels/drawing${sheetIndex}.xml.rels`, {
47439
47599
  target: `../charts/chart${xlsxChartId}.xml`,
@@ -47442,20 +47602,27 @@
47442
47602
  drawingRelIds.push(chartRelId);
47443
47603
  files.push(createXMLFile(createChart(chart, sheetIndex, data), `xl/charts/chart${xlsxChartId}.xml`, "chart"));
47444
47604
  }
47445
- const images = sheet.images;
47446
- for (const image of images) {
47605
+ for (const image of sheet.images) {
47606
+ const mimeType = image.data.mimetype;
47607
+ if (mimeType === undefined)
47608
+ continue;
47609
+ const extension = IMAGE_MIMETYPE_EXTENSION_MAPPING[mimeType];
47610
+ // only support exporting images with mimetypes specified in the mapping
47611
+ if (extension === undefined)
47612
+ continue;
47447
47613
  const xlsxImageId = convertImageId(image.id);
47614
+ let imageFileName = `image${xlsxImageId}.${extension}`;
47448
47615
  const imageRelId = addRelsToFile(construct.relsFiles, `xl/drawings/_rels/drawing${sheetIndex}.xml.rels`, {
47449
- target: `../media/image${xlsxImageId}`,
47616
+ target: `../media/${imageFileName}`,
47450
47617
  type: XLSX_RELATION_TYPE.image,
47451
47618
  });
47452
47619
  drawingRelIds.push(imageRelId);
47453
47620
  files.push({
47454
- path: `xl/media/image${xlsxImageId}`,
47455
- imagePath: image.data.path,
47621
+ path: `xl/media/${imageFileName}`,
47622
+ imageSrc: image.data.path,
47456
47623
  });
47457
47624
  }
47458
- const drawings = [...charts, ...images];
47625
+ const drawings = [...sheet.charts, ...sheet.images];
47459
47626
  if (drawings.length) {
47460
47627
  const drawingRelId = addRelsToFile(construct.relsFiles, `xl/worksheets/_rels/sheet${sheetIndex}.xml.rels`, {
47461
47628
  target: `../drawings/drawing${sheetIndex}.xml`,
@@ -47574,6 +47741,8 @@
47574
47741
  }
47575
47742
  function createContentTypes(files) {
47576
47743
  const overrideNodes = [];
47744
+ // hard-code supported image mimetypes
47745
+ const imageDefaultNodes = Object.entries(IMAGE_MIMETYPE_EXTENSION_MAPPING).map(([mimetype, extension]) => createDefaultXMLElement(extension, mimetype));
47577
47746
  for (const file of files) {
47578
47747
  if ("contentType" in file && file.contentType) {
47579
47748
  overrideNodes.push(createOverride("/" + file.path, CONTENT_TYPES[file.contentType]));
@@ -47589,6 +47758,7 @@
47589
47758
  ];
47590
47759
  const xml = escapeXml /*xml*/ `
47591
47760
  <Types xmlns="${NAMESPACE["Types"]}">
47761
+ ${joinXmlNodes(Object.values(imageDefaultNodes))}
47592
47762
  <Default ${formatAttributes(relsAttributes)} />
47593
47763
  <Default ${formatAttributes(xmlAttributes)} />
47594
47764
  ${joinXmlNodes(overrideNodes)}
@@ -47680,7 +47850,7 @@
47680
47850
  this.coreGetters.getRangeDataFromXc = this.range.getRangeDataFromXc.bind(this.range);
47681
47851
  this.coreGetters.getRangeDataFromZone = this.range.getRangeDataFromZone.bind(this.range);
47682
47852
  this.coreGetters.getRangeFromRangeData = this.range.getRangeFromRangeData.bind(this.range);
47683
- this.coreGetters.getSelectionRangeString = this.range.getSelectionRangeString.bind(this.range);
47853
+ this.coreGetters.getRangeFromZone = this.range.getRangeFromZone.bind(this.range);
47684
47854
  this.getters = {
47685
47855
  isReadonly: () => this.config.mode === "readonly" || this.config.mode === "dashboard",
47686
47856
  isDashboard: () => this.config.mode === "dashboard",
@@ -48188,6 +48358,8 @@
48188
48358
  exports.findCellInNewZone = findCellInNewZone;
48189
48359
  exports.functionCache = functionCache;
48190
48360
  exports.helpers = helpers;
48361
+ exports.invalidateCFEvaluationCommands = invalidateCFEvaluationCommands;
48362
+ exports.invalidateDependenciesCommands = invalidateDependenciesCommands;
48191
48363
  exports.invalidateEvaluationCommands = invalidateEvaluationCommands;
48192
48364
  exports.links = links;
48193
48365
  exports.load = load;
@@ -48201,9 +48373,9 @@
48201
48373
  Object.defineProperty(exports, '__esModule', { value: true });
48202
48374
 
48203
48375
 
48204
- __info__.version = '16.4.0-alpha.1';
48205
- __info__.date = '2023-05-25T13:00:14.133Z';
48206
- __info__.hash = '564c094';
48376
+ __info__.version = '16.4.0-alpha.3';
48377
+ __info__.date = '2023-06-02T10:13:30.127Z';
48378
+ __info__.hash = '059d66a';
48207
48379
 
48208
48380
 
48209
48381
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);