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