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