@trebco/treb 27.7.6 → 27.11.1

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.
Files changed (55) hide show
  1. package/dist/treb-spreadsheet.mjs +14 -14
  2. package/dist/treb.d.ts +28 -26
  3. package/notes/conditional-fomratring.md +29 -0
  4. package/package.json +1 -1
  5. package/treb-base-types/src/area.ts +181 -0
  6. package/{treb-grid/src/util/dom_utilities.ts → treb-base-types/src/dom-utilities.ts} +29 -20
  7. package/treb-base-types/src/evaluate-options.ts +21 -0
  8. package/treb-base-types/src/gradient.ts +97 -0
  9. package/treb-base-types/src/import.ts +2 -1
  10. package/treb-base-types/src/index.ts +3 -0
  11. package/treb-base-types/src/theme.ts +3 -5
  12. package/treb-calculator/src/calculator.ts +190 -28
  13. package/treb-calculator/src/dag/calculation_leaf_vertex.ts +97 -0
  14. package/treb-calculator/src/dag/graph.ts +10 -22
  15. package/treb-calculator/src/dag/{leaf_vertex.ts → state_leaf_vertex.ts} +3 -3
  16. package/treb-calculator/src/descriptors.ts +10 -3
  17. package/treb-calculator/src/expression-calculator.ts +1 -1
  18. package/treb-calculator/src/function-library.ts +25 -22
  19. package/treb-calculator/src/functions/base-functions.ts +166 -5
  20. package/treb-calculator/src/index.ts +6 -6
  21. package/treb-calculator/src/notifier-types.ts +1 -1
  22. package/treb-calculator/src/utilities.ts +2 -2
  23. package/treb-charts/src/util.ts +2 -2
  24. package/treb-embed/src/custom-element/global.d.ts +3 -1
  25. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +13 -19
  26. package/treb-embed/src/embedded-spreadsheet.ts +378 -132
  27. package/treb-embed/src/spinner.ts +3 -3
  28. package/treb-embed/style/layout.scss +1 -1
  29. package/treb-export/src/drawing2/chart2.ts +11 -2
  30. package/treb-export/src/export-worker/export-worker.ts +0 -13
  31. package/treb-export/src/export2.ts +197 -2
  32. package/treb-export/src/import2.ts +169 -4
  33. package/treb-export/src/workbook-style2.ts +59 -10
  34. package/treb-export/src/workbook2.ts +10 -1
  35. package/treb-grid/src/editors/autocomplete.ts +28 -24
  36. package/treb-grid/src/editors/editor.ts +3 -4
  37. package/treb-grid/src/editors/formula_bar.ts +1 -1
  38. package/treb-grid/src/index.ts +2 -1
  39. package/treb-grid/src/layout/base_layout.ts +34 -31
  40. package/treb-grid/src/layout/grid_layout.ts +17 -28
  41. package/treb-grid/src/render/selection-renderer.ts +2 -3
  42. package/treb-grid/src/render/svg_header_overlay.ts +4 -11
  43. package/treb-grid/src/render/svg_selection_block.ts +27 -34
  44. package/treb-grid/src/render/tile_renderer.ts +8 -6
  45. package/treb-grid/src/types/conditional_format.ts +168 -0
  46. package/treb-grid/src/types/grid.ts +37 -47
  47. package/treb-grid/src/types/grid_base.ts +188 -33
  48. package/treb-grid/src/types/scale-control.ts +2 -2
  49. package/treb-grid/src/types/sheet.ts +332 -28
  50. package/treb-grid/src/types/sheet_types.ts +4 -0
  51. package/treb-grid/src/types/tab_bar.ts +4 -8
  52. package/treb-utils/src/index.ts +0 -1
  53. package/treb-utils/src/resizable.ts +26 -27
  54. package/treb-utils/src/template.ts +0 -70
  55. /package/{README-shadow-DOM.md → notes/shadow-DOM.md} +0 -0
@@ -32,33 +32,47 @@ import type {
32
32
  AnnotationViewData,
33
33
  AnnotationType,
34
34
  ExternalEditorConfig,
35
+ ConditionalFormatDuplicateValuesOptions,
36
+ ConditionalFormatDuplicateValues,
37
+ ConditionalFormatGradientOptions,
38
+ ConditionalFormat,
39
+ ConditionalFormatGradient,
40
+ ConditionalFormatExpression,
41
+ StandardGradient,
42
+ CondifionalFormatExpressionOptions,
43
+ ConditionalFormatCellMatchOptions,
44
+ ConditionalFormatCellMatch,
35
45
  } from 'treb-grid';
36
46
 
37
47
  import {
38
- DataModel, Grid, BorderConstants, Sheet, ErrorCode, UA
48
+ DataModel, Grid, BorderConstants, Sheet, ErrorCode, UA,
49
+ StandardGradientsList,
39
50
  } from 'treb-grid';
40
51
 
41
52
  import {
42
53
  Parser, DecimalMarkType,
43
54
  ArgumentSeparatorType, QuotedSheetNameRegex } from 'treb-parser';
44
55
 
45
- import { Calculator, type EvaluateOptions, type LeafVertex } from 'treb-calculator';
56
+ import { Calculator, type LeafVertex } from 'treb-calculator';
46
57
 
47
58
  import type {
48
59
  ICellAddress,
60
+ EvaluateOptions,
49
61
  IArea, CellValue, Point,
50
62
  Complex, ExtendedUnion, IRectangle,
51
- AddressReference, RangeReference, TableSortOptions, Table, TableTheme,
63
+ AddressReference, RangeReference, TableSortOptions, Table, TableTheme, GradientStop,
52
64
  } from 'treb-base-types';
53
65
 
54
66
  import {
55
67
  IsArea, ThemeColorTable, ComplexToString, Rectangle, IsComplex, type CellStyle,
56
- Localization, Style, type Color, ThemeColor2, IsCellAddress, Area, IsFlatData, IsFlatDataArray,
68
+ Localization, Style, type Color, ThemeColor2, IsCellAddress, Area, IsFlatData, IsFlatDataArray, Gradient, ValueType, DOMUtilities,
57
69
  } from 'treb-base-types';
58
70
 
59
71
  import { EventSource, Yield, ValidateURI } from 'treb-utils';
60
72
  import { NumberFormatCache, ValueParser, NumberFormat } from 'treb-format';
61
73
 
74
+
75
+
62
76
  // --- local -------------------------------------------------------------------
63
77
 
64
78
  import { Dialog, DialogType } from './progress-dialog';
@@ -81,6 +95,7 @@ import type { SetRangeOptions } from 'treb-grid';
81
95
  * the script so we can run it as a worker.
82
96
  */
83
97
  import * as export_worker_script from 'worker:../../treb-export/src/export-worker/index.worker';
98
+ import { StateLeafVertex } from 'treb-calculator/src/dag/state_leaf_vertex';
84
99
 
85
100
  // --- types -------------------------------------------------------------------
86
101
 
@@ -1251,6 +1266,265 @@ export class EmbeddedSpreadsheet {
1251
1266
 
1252
1267
  }
1253
1268
 
1269
+ //////////////////////////////////////////////////////////////////////////////
1270
+ //
1271
+ // conditional formatting API (WIP)
1272
+ //
1273
+
1274
+ /**
1275
+ * list conditional formats. uses the active sheet by default, or pass a
1276
+ * sheet name or id.
1277
+ *
1278
+ * @internal
1279
+ */
1280
+ public ListConditionalFormats(sheet?: number|string) {
1281
+
1282
+ const target = (typeof sheet === 'undefined') ?
1283
+ this.grid.active_sheet :
1284
+ this.model.sheets.Find(sheet);
1285
+
1286
+ return target?.conditional_formats || [];
1287
+
1288
+ }
1289
+
1290
+ /** @internal */
1291
+ public ConditionalFormatDuplicateValues(range: RangeReference|undefined, options: ConditionalFormatDuplicateValuesOptions): ConditionalFormat {
1292
+
1293
+ if (range === undefined) {
1294
+ const ref = this.GetSelectionReference();
1295
+ if (ref.empty) {
1296
+ throw new Error('invalid range (no selection)');
1297
+ }
1298
+ range = ref.area;
1299
+ }
1300
+
1301
+ return this.AddConditionalFormat({
1302
+ type: 'duplicate-values',
1303
+ area: this.model.ResolveArea(range, this.grid.active_sheet),
1304
+ ...options,
1305
+ });
1306
+
1307
+ }
1308
+
1309
+ /**
1310
+ * @internal
1311
+ */
1312
+ public ConditionalFormatGradient(range: RangeReference|undefined, options: ConditionalFormatGradientOptions|StandardGradient): ConditionalFormat {
1313
+
1314
+ if (range === undefined) {
1315
+ const ref = this.GetSelectionReference();
1316
+ if (ref.empty) {
1317
+ throw new Error('invalid range (no selection)');
1318
+ }
1319
+ range = ref.area;
1320
+ }
1321
+
1322
+ const area = this.model.ResolveArea(range, this.grid.active_sheet);
1323
+
1324
+ const format: ConditionalFormatGradient = (typeof options === 'object') ?
1325
+ {
1326
+ type: 'gradient', area, ...options,
1327
+ } :
1328
+ {
1329
+ type: 'gradient', area, ...StandardGradientsList[options]
1330
+ };
1331
+
1332
+ this.AddConditionalFormat(format);
1333
+ return format;
1334
+
1335
+ }
1336
+
1337
+ /** @internal */
1338
+ public ConditionalFormatCellMatch(range: RangeReference|undefined, options: ConditionalFormatCellMatchOptions): ConditionalFormat {
1339
+
1340
+ if (range === undefined) {
1341
+ const ref = this.GetSelectionReference();
1342
+ if (ref.empty) {
1343
+ throw new Error('invalid range (no selection)');
1344
+ }
1345
+ range = ref.area;
1346
+ }
1347
+
1348
+ const area = this.model.ResolveArea(range, this.grid.active_sheet);
1349
+
1350
+ const format: ConditionalFormatCellMatch = {
1351
+ type: 'cell-match',
1352
+ area,
1353
+ ...options,
1354
+ };
1355
+
1356
+ // we need to calculate the formula once, to get an initial state
1357
+ // update: internal
1358
+ // let result = this.Evaluate(this.Unresolve(area, true, false) + ' ' + options.expression, options.options);
1359
+
1360
+ // ... apply ...
1361
+
1362
+ this.AddConditionalFormat(format);
1363
+ return format;
1364
+
1365
+
1366
+ }
1367
+
1368
+ /**
1369
+ * @internal
1370
+ */
1371
+ public ConditionalFormatExpression(range: RangeReference|undefined, options: CondifionalFormatExpressionOptions): ConditionalFormat {
1372
+
1373
+ if (range === undefined) {
1374
+ const ref = this.GetSelectionReference();
1375
+ if (ref.empty) {
1376
+ throw new Error('invalid range (no selection)');
1377
+ }
1378
+ range = ref.area;
1379
+ }
1380
+
1381
+ const area = this.model.ResolveArea(range, this.grid.active_sheet);
1382
+
1383
+ const format: ConditionalFormatExpression = {
1384
+ type: 'expression',
1385
+ area,
1386
+ ...options,
1387
+ };
1388
+
1389
+ /*
1390
+ // we need to calculate the formula once, to get an initial state
1391
+ let result = this.Evaluate(options.expression, options.options);
1392
+
1393
+ if (Array.isArray(result)) {
1394
+ result = result[0][0];
1395
+ }
1396
+ const applied = !!result;
1397
+
1398
+ this.AddConditionalFormat({...format, applied });
1399
+ */
1400
+
1401
+ this.AddConditionalFormat(format);
1402
+
1403
+ return format;
1404
+
1405
+ }
1406
+
1407
+ /**
1408
+ * add a conditional format
1409
+ *
1410
+ * @internal
1411
+ */
1412
+ public AddConditionalFormat(format: ConditionalFormat) {
1413
+
1414
+ const sheet = this.model.sheets.Find(format.area.start.sheet_id||0);
1415
+
1416
+ if (!sheet) {
1417
+ throw new Error('invalid reference in format');
1418
+ }
1419
+
1420
+ sheet.conditional_formats.push(format);
1421
+
1422
+ this.calculator.UpdateConditionals(format, sheet);
1423
+
1424
+ // call update if it's the current sheet
1425
+ this.ApplyConditionalFormats(sheet, sheet === this.grid.active_sheet);
1426
+
1427
+ this.PushUndo();
1428
+
1429
+ // fluent
1430
+ return format;
1431
+
1432
+ }
1433
+
1434
+ /**
1435
+ * remove conditional format
1436
+ *
1437
+ * @internal
1438
+ */
1439
+ public RemoveConditionalFormat(format: ConditionalFormat) {
1440
+ const area = format.area;
1441
+ const sheet = area.start.sheet_id ? this.model.sheets.Find(area.start.sheet_id) : this.grid.active_sheet;
1442
+
1443
+ if (!sheet) {
1444
+ throw new Error('invalid reference in format');
1445
+ }
1446
+
1447
+ let count = 0;
1448
+ sheet.conditional_formats = sheet.conditional_formats.filter(test => {
1449
+ if (test === format) {
1450
+ count++;
1451
+ this.calculator.RemoveConditional(test);
1452
+ return false;
1453
+ }
1454
+ return true;
1455
+ });
1456
+
1457
+ if (count) {
1458
+ sheet.FlushConditionalFormats();
1459
+ }
1460
+
1461
+ // we want to call update if it's the current sheet,
1462
+ // but we want a full repaint
1463
+
1464
+ this.ApplyConditionalFormats(sheet, false);
1465
+
1466
+ if (sheet === this.grid.active_sheet) {
1467
+ this.grid.Update(true);
1468
+ }
1469
+
1470
+ if (count) {
1471
+ this.PushUndo();
1472
+ }
1473
+
1474
+ }
1475
+
1476
+ /**
1477
+ * clear conditional formats from the target range (or currently selected
1478
+ * range). we operate on format objects, meaning we'll remove the whole
1479
+ * format object rather than clip the area.
1480
+ *
1481
+ * @internal
1482
+ */
1483
+ public RemoveConditionalFormats(range?: RangeReference) {
1484
+
1485
+ if (range === undefined) {
1486
+ const ref = this.GetSelectionReference();
1487
+ if (ref.empty) {
1488
+ throw new Error('invalid range (no selection)');
1489
+ }
1490
+ range = ref.area;
1491
+ }
1492
+ const area = this.model.ResolveArea(range, this.grid.active_sheet);
1493
+ const sheet = area.start.sheet_id ? this.model.sheets.Find(area.start.sheet_id) : this.grid.active_sheet;
1494
+
1495
+ if (sheet) {
1496
+ let count = 0;
1497
+
1498
+ sheet.conditional_formats = sheet.conditional_formats.filter(test => {
1499
+ const compare = new Area(test.area.start, test.area.end);
1500
+ if (compare.Intersects(area)) {
1501
+ count++;
1502
+ this.calculator.RemoveConditional(test);
1503
+ return false;
1504
+ }
1505
+ return true;
1506
+ });
1507
+
1508
+ if (count) {
1509
+
1510
+ sheet.FlushConditionalFormats();
1511
+
1512
+ this.ApplyConditionalFormats(sheet, false);
1513
+
1514
+ if (sheet === this.grid.active_sheet) {
1515
+ this.grid.Update(true);
1516
+ }
1517
+
1518
+ this.PushUndo();
1519
+ }
1520
+
1521
+ }
1522
+
1523
+
1524
+ }
1525
+
1526
+ //////////////////////////////////////////////////////////////////////////////
1527
+
1254
1528
  /**
1255
1529
  * @internal
1256
1530
  */
@@ -1794,6 +2068,19 @@ export class EmbeddedSpreadsheet {
1794
2068
 
1795
2069
  this.UpdateDocumentStyles();
1796
2070
 
2071
+ // we need to flush conditional formats that use theme colors
2072
+ // (I guess we're just flushing everybody?)
2073
+
2074
+ for (const sheet of this.model.sheets.list) {
2075
+ for (const format of sheet.conditional_formats) {
2076
+ format.internal = undefined;
2077
+ }
2078
+ }
2079
+
2080
+ this.calculator.UpdateConditionals();
2081
+ this.ApplyConditionalFormats(this.grid.active_sheet, false);
2082
+ this.grid.Update(true);
2083
+
1797
2084
  }
1798
2085
 
1799
2086
  /**
@@ -1919,19 +2206,29 @@ export class EmbeddedSpreadsheet {
1919
2206
  * @param formula - annotation formula. For charts, the chart formula.
1920
2207
  * @param type - annotation type. Defaults to `treb-chart`.
1921
2208
  * @param rect - coordinates, or a range reference for layout.
1922
- *
1923
- * @param argument_separator - the argument separator to use when evaluating
1924
- * the function. defaults to current locale.
2209
+ * @param options - evaluate options. because this function used to take
2210
+ * the argument separator, we allow that to be passed directly, but this
2211
+ * is deprecated. new code should use the options object.
1925
2212
  */
1926
- public InsertAnnotation(formula: string, type: AnnotationType = 'treb-chart', rect?: IRectangle|RangeReference, argument_separator?: ','|';'): void {
2213
+ public InsertAnnotation(formula: string, type: AnnotationType = 'treb-chart', rect?: IRectangle|RangeReference, options?: EvaluateOptions|','|';'): void {
1927
2214
 
1928
2215
  let target: IRectangle | Partial<Area> | undefined;
2216
+ let argument_separator: ','|';'|undefined = undefined;
2217
+ let r1c1 = false;
2218
+
2219
+ if (typeof options === 'object') {
2220
+ argument_separator = options.argument_separator;
2221
+ r1c1 = !!options.r1c1;
2222
+ }
2223
+ else if (options === ',' || options === ';') {
2224
+ argument_separator = options;
2225
+ }
1929
2226
 
1930
2227
  if (rect) {
1931
2228
  target = Rectangle.IsRectangle(rect) ? rect : this.model.ResolveArea(rect, this.grid.active_sheet);
1932
2229
  }
1933
2230
 
1934
- if (argument_separator && argument_separator !== this.parser.argument_separator) {
2231
+ if (argument_separator && argument_separator !== this.parser.argument_separator || r1c1) {
1935
2232
  const current = {
1936
2233
  argument_separator: this.parser.argument_separator,
1937
2234
  decimal_mark: this.parser.decimal_mark,
@@ -1946,11 +2243,17 @@ export class EmbeddedSpreadsheet {
1946
2243
  this.parser.decimal_mark = DecimalMarkType.Comma;
1947
2244
  }
1948
2245
 
2246
+ const r1c1_state = this.parser.flags.r1c1;
2247
+ if (r1c1) {
2248
+ this.parser.flags.r1c1 = r1c1;
2249
+ }
2250
+
1949
2251
  const result = this.parser.Parse(formula);
1950
2252
 
1951
2253
  // reset
1952
2254
  this.parser.argument_separator = current.argument_separator;
1953
2255
  this.parser.decimal_mark = current.decimal_mark;
2256
+ this.parser.flags.r1c1 = r1c1_state;
1954
2257
 
1955
2258
  if (result.expression) {
1956
2259
  formula = '=' + this.parser.Render(result.expression, { missing: '' });
@@ -2819,14 +3122,6 @@ export class EmbeddedSpreadsheet {
2819
3122
 
2820
3123
  if (text && filename) {
2821
3124
  const blob = new Blob([text as any], { type: 'text/plain;charset=utf-8' });
2822
- /*
2823
- // FileSaver.saveAs(blob, filename, { autoBom: false });
2824
- const a = document.createElement('a');
2825
- a.href = URL.createObjectURL(blob);
2826
- a.download = filename;
2827
- a.click();
2828
- URL.revokeObjectURL(a.href);
2829
- */
2830
3125
  this.SaveAs(blob, filename);
2831
3126
  }
2832
3127
 
@@ -2954,11 +3249,11 @@ export class EmbeddedSpreadsheet {
2954
3249
  // FIXME: optional? parameter? (...)
2955
3250
 
2956
3251
  if (data.rendered_values && !options.recalculate) {
2957
- this.grid.Update();
2958
3252
  this.calculator.RebuildClean(true);
3253
+ this.ApplyConditionalFormats(this.grid.active_sheet, false);
3254
+ this.grid.Update();
2959
3255
  }
2960
3256
  else {
2961
- // console.info('load recalc');
2962
3257
  this.Recalculate();
2963
3258
  }
2964
3259
 
@@ -3248,6 +3543,7 @@ export class EmbeddedSpreadsheet {
3248
3543
  }
3249
3544
 
3250
3545
  this.calculator.Calculate(area);
3546
+ this.ApplyConditionalFormats(this.grid.active_sheet, false);
3251
3547
 
3252
3548
  this.grid.Update(true); // , area);
3253
3549
  this.UpdateAnnotations();
@@ -3432,6 +3728,9 @@ export class EmbeddedSpreadsheet {
3432
3728
  ref = resolved;
3433
3729
  }
3434
3730
 
3731
+ return this.calculator.Unresolve(ref, this.grid.active_sheet, qualified, named);
3732
+
3733
+ /*
3435
3734
  let range = '';
3436
3735
  const area = IsCellAddress(ref) ? new Area(ref) : new Area(ref.start, ref.end);
3437
3736
 
@@ -3457,10 +3756,11 @@ export class EmbeddedSpreadsheet {
3457
3756
  // the active selection must be on the active sheet? (...)
3458
3757
 
3459
3758
  const sheet_id = area.start.sheet_id || this.grid.active_sheet.id;
3460
- const sheet_name = this.ResolveSheetName(sheet_id, true);
3759
+ const sheet_name = this.calculator.ResolveSheetName(sheet_id, true);
3461
3760
 
3462
3761
  return sheet_name ? sheet_name + '!' + range : range;
3463
-
3762
+ */
3763
+
3464
3764
  }
3465
3765
 
3466
3766
  /**
@@ -3482,7 +3782,7 @@ export class EmbeddedSpreadsheet {
3482
3782
 
3483
3783
  return this.calculator.Evaluate(
3484
3784
  expression, this.grid.active_sheet, options);
3485
-
3785
+
3486
3786
  }
3487
3787
 
3488
3788
  /**
@@ -3522,7 +3822,7 @@ export class EmbeddedSpreadsheet {
3522
3822
  // the active selection must be on the active sheet? (...)
3523
3823
 
3524
3824
  const sheet_id = ref.area.start.sheet_id || this.grid.active_sheet.id;
3525
- const sheet_name = this.ResolveSheetName(sheet_id, true);
3825
+ const sheet_name = this.calculator.ResolveSheetName(sheet_id, true);
3526
3826
 
3527
3827
  return sheet_name ? sheet_name + '!' + range : range;
3528
3828
 
@@ -3940,6 +4240,47 @@ export class EmbeddedSpreadsheet {
3940
4240
 
3941
4241
  // --- internal (protected) methods ------------------------------------------
3942
4242
 
4243
+ /**
4244
+ *
4245
+ */
4246
+ protected ApplyConditionalFormats(sheet: Sheet, call_update: boolean) {
4247
+
4248
+ // const sheet = this.grid.active_sheet;
4249
+ const areas: IArea[] = [];
4250
+
4251
+ if (sheet.conditional_formats.length || sheet.flush_conditional_formats) {
4252
+
4253
+ for (const entry of sheet.conditional_formats) {
4254
+ areas.push(entry.area);
4255
+
4256
+ // NOTE: we're (optionally) adding the gradient here, instead
4257
+ // of when it's created, because we might want to flush this
4258
+ // from time to time. specifically, because gradient might use
4259
+ // theme colors, we need to flush it when the theme is updated.
4260
+
4261
+ // so don't move it, even though it is tempting. or rewrite to
4262
+ // update on theme changes.
4263
+
4264
+ if (entry.type === 'gradient') {
4265
+ if (!entry.internal) {
4266
+ entry.internal = {};
4267
+ }
4268
+ if (!entry.internal.gradient) {
4269
+ entry.internal.gradient = new Gradient(entry.stops, this.grid.theme, entry.color_space);
4270
+ }
4271
+ }
4272
+ }
4273
+
4274
+ sheet.ApplyConditionalFormats();
4275
+
4276
+ }
4277
+
4278
+ if (call_update) {
4279
+ this.grid.Update(true, areas);
4280
+ }
4281
+
4282
+ }
4283
+
3943
4284
  protected ResolveTable(reference: RangeReference): Table|undefined {
3944
4285
 
3945
4286
  let table: Table|undefined = undefined;
@@ -3979,7 +4320,7 @@ export class EmbeddedSpreadsheet {
3979
4320
  */
3980
4321
  protected SaveAs(blob: Blob, filename: string) {
3981
4322
 
3982
- const a = document.createElement('a');
4323
+ const a = DOMUtilities.Create('a');
3983
4324
  a.href = URL.createObjectURL(blob);
3984
4325
  a.download = filename;
3985
4326
  a.click();
@@ -4044,6 +4385,11 @@ export class EmbeddedSpreadsheet {
4044
4385
 
4045
4386
  this.InflateAnnotations();
4046
4387
 
4388
+ // and conditional formats
4389
+
4390
+ this.calculator.UpdateConditionals();
4391
+ this.ApplyConditionalFormats(this.grid.active_sheet, false);
4392
+
4047
4393
  }
4048
4394
  else {
4049
4395
  return reject('unknown error (missing data)');
@@ -4099,7 +4445,7 @@ export class EmbeddedSpreadsheet {
4099
4445
  return;
4100
4446
  }
4101
4447
 
4102
- const a = document.createElement('a');
4448
+ const a = DOMUtilities.Create('a');
4103
4449
  a.setAttribute('target', this.options.hyperlinks);
4104
4450
  a.setAttribute('href', data);
4105
4451
  a.setAttribute('noreferrer', 'true');
@@ -4164,6 +4510,9 @@ export class EmbeddedSpreadsheet {
4164
4510
 
4165
4511
  this.UpdateAnnotations();
4166
4512
 
4513
+ this.calculator.UpdateConditionals();
4514
+ this.ApplyConditionalFormats(event.activate, true);
4515
+
4167
4516
  }
4168
4517
 
4169
4518
  protected HandleDrag(event: DragEvent): void {
@@ -4237,7 +4586,7 @@ export class EmbeddedSpreadsheet {
4237
4586
  protected SelectFile2(accept: string, operation: FileChooserOperation) {
4238
4587
 
4239
4588
  if (!this.file_chooser) {
4240
- this.file_chooser = document.createElement('input');
4589
+ this.file_chooser = DOMUtilities.Create('input');
4241
4590
  this.file_chooser.type = 'file';
4242
4591
 
4243
4592
  const file_chooser = this.file_chooser;
@@ -4270,79 +4619,6 @@ export class EmbeddedSpreadsheet {
4270
4619
 
4271
4620
  }
4272
4621
 
4273
- /* *
4274
- * show file chooser and resolve with the selected file, or undefined
4275
- * /
4276
- protected SelectFile(accept?: string): Promise<File | undefined> {
4277
-
4278
- return new Promise((resolve) => {
4279
-
4280
- const file_chooser = document.createElement('input');
4281
- file_chooser.type = 'file';
4282
-
4283
- if (accept) {
4284
- file_chooser.accept = accept;
4285
- }
4286
-
4287
- // so the thing here is there is no way to trap a "cancel" event
4288
- // from the file chooser. if you are waiting on a promise, that will
4289
- // just get orphaned forever.
4290
-
4291
- // it's not the end of the world, really, to leave a few of these
4292
- // dangling, but this should allow it to clean up.
4293
-
4294
- // the concept is that since file chooser is modal, there will never
4295
- // be a focus event until the modal is closed. unfortunately the focus
4296
- // event comes _before_ any input or change event from the file input,
4297
- // so we have to wait.
4298
-
4299
- // tested Cr, FF, IE11
4300
- // update: works in Safari, although oddly not if you call the API
4301
- // function from the console. not sure if that's a browserstack thing.
4302
-
4303
- // eslint-disable-next-line prefer-const
4304
- let finalize: (file?: File) => void;
4305
- let timeout: NodeJS.Timeout|undefined;
4306
-
4307
- // if you get a focus event, allow some reasonable time for the
4308
- // corresponding change event. realistically this should be immediate,
4309
- // but as long as there's not a lot of logic waiting on a cancel, it
4310
- // doesn't really matter.
4311
-
4312
- const window_focus = () => {
4313
-
4314
- // prevent this from accidentally being called more than once
4315
- window.removeEventListener('focus', window_focus);
4316
- timeout = setTimeout(finalize, 250);
4317
- }
4318
-
4319
- const change_handler = () => {
4320
- if (timeout) {
4321
- clearTimeout(timeout);
4322
- timeout = undefined; // necessary?
4323
- }
4324
- finalize(file_chooser.files ? file_chooser.files[0] : undefined);
4325
- }
4326
-
4327
- // our finalize method cleans up and resolves
4328
-
4329
- finalize = (file?: File) => {
4330
- file_chooser.removeEventListener('change', change_handler);
4331
- window.removeEventListener('focus', window_focus);
4332
- resolve(file);
4333
- };
4334
-
4335
- file_chooser.addEventListener('change', change_handler);
4336
- window.addEventListener('focus', window_focus);
4337
-
4338
- file_chooser.click();
4339
-
4340
-
4341
- });
4342
-
4343
- }
4344
- */
4345
-
4346
4622
  /**
4347
4623
  * Insert an image. This method will open a file chooser and (if an image
4348
4624
  * is selected) insert the image into the document.
@@ -4387,7 +4663,7 @@ export class EmbeddedSpreadsheet {
4387
4663
  }
4388
4664
  }
4389
4665
 
4390
- const img = document.createElement('img');
4666
+ const img = DOMUtilities.Create('img');
4391
4667
  img.src = contents;
4392
4668
 
4393
4669
  // this is to let the browser figure out the image size.
@@ -4556,7 +4832,7 @@ export class EmbeddedSpreadsheet {
4556
4832
  protected UpdateAnnotations(): void {
4557
4833
  for (const annotation of this.grid.active_sheet.annotations) {
4558
4834
  if (annotation.temp.vertex) {
4559
- const vertex = annotation.temp.vertex as LeafVertex;
4835
+ const vertex = annotation.temp.vertex as StateLeafVertex;
4560
4836
  if (vertex.state_id !== annotation.temp.state) {
4561
4837
  annotation.temp.state = vertex.state_id;
4562
4838
 
@@ -4752,7 +5028,7 @@ export class EmbeddedSpreadsheet {
4752
5028
  const reference = ValidateURI(annotation.data.data.src);
4753
5029
  if (reference) {
4754
5030
 
4755
- const img = document.createElement('img');
5031
+ const img = DOMUtilities.Create('img');
4756
5032
  img.src = reference;
4757
5033
 
4758
5034
  if (annotation.data.data.scale === 'fixed') {
@@ -5436,35 +5712,5 @@ export class EmbeddedSpreadsheet {
5436
5712
  }
5437
5713
  }
5438
5714
 
5439
- /**
5440
- * this is only used in one place, can we just inline?
5441
- * [A: seems like it might be useful, though]
5442
- *
5443
- * @param id
5444
- * @param quote
5445
- * @returns
5446
- */
5447
- protected ResolveSheetName(id: number, quote = false): string | undefined {
5448
- const sheet = this.model.sheets.Find(id);
5449
- if (sheet) {
5450
- if (quote && QuotedSheetNameRegex.test(sheet.name)) {
5451
- return `'${sheet.name}'`;
5452
- }
5453
- return sheet.name;
5454
- }
5455
-
5456
- /*
5457
- for (const sheet of this.grid.model.sheets) {
5458
- if (sheet.id === id) {
5459
- if (quote && QuotedSheetNameRegex.test(sheet.name)) {
5460
- return `'${sheet.name}'`;
5461
- }
5462
- return sheet.name;
5463
- }
5464
- }
5465
- */
5466
-
5467
- return undefined;
5468
- }
5469
5715
 
5470
5716
  }