@trebco/treb 27.5.3 → 27.9.0

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 (48) hide show
  1. package/dist/treb-spreadsheet.mjs +14 -14
  2. package/dist/treb.d.ts +37 -23
  3. package/notes/conditional-fomratring.md +29 -0
  4. package/package.json +3 -3
  5. package/treb-base-types/src/area.ts +181 -0
  6. package/treb-base-types/src/evaluate-options.ts +21 -0
  7. package/treb-base-types/src/gradient.ts +97 -0
  8. package/treb-base-types/src/import.ts +2 -1
  9. package/treb-base-types/src/index.ts +2 -0
  10. package/treb-calculator/src/calculator.ts +205 -132
  11. package/treb-calculator/src/dag/calculation_leaf_vertex.ts +97 -0
  12. package/treb-calculator/src/dag/graph.ts +10 -22
  13. package/treb-calculator/src/dag/{leaf_vertex.ts → state_leaf_vertex.ts} +3 -3
  14. package/treb-calculator/src/descriptors.ts +10 -3
  15. package/treb-calculator/src/expression-calculator.ts +1 -1
  16. package/treb-calculator/src/function-library.ts +25 -22
  17. package/treb-calculator/src/functions/base-functions.ts +166 -5
  18. package/treb-calculator/src/index.ts +6 -6
  19. package/treb-calculator/src/notifier-types.ts +1 -1
  20. package/treb-calculator/src/utilities.ts +2 -2
  21. package/treb-charts/src/util.ts +2 -2
  22. package/treb-embed/src/embedded-spreadsheet.ts +382 -71
  23. package/treb-embed/style/formula-bar.scss +2 -0
  24. package/treb-embed/style/theme-defaults.scss +46 -15
  25. package/treb-export/src/export-worker/export-worker.ts +0 -13
  26. package/treb-export/src/export2.ts +187 -2
  27. package/treb-export/src/import2.ts +169 -4
  28. package/treb-export/src/workbook-style2.ts +56 -8
  29. package/treb-export/src/workbook2.ts +10 -1
  30. package/treb-grid/src/editors/editor.ts +1276 -0
  31. package/treb-grid/src/editors/external_editor.ts +113 -0
  32. package/treb-grid/src/editors/formula_bar.ts +450 -474
  33. package/treb-grid/src/editors/overlay_editor.ts +437 -512
  34. package/treb-grid/src/index.ts +2 -1
  35. package/treb-grid/src/layout/base_layout.ts +24 -16
  36. package/treb-grid/src/render/tile_renderer.ts +2 -1
  37. package/treb-grid/src/types/conditional_format.ts +168 -0
  38. package/treb-grid/src/types/data_model.ts +130 -3
  39. package/treb-grid/src/types/external_editor_config.ts +47 -0
  40. package/treb-grid/src/types/grid.ts +96 -45
  41. package/treb-grid/src/types/grid_base.ts +187 -35
  42. package/treb-grid/src/types/scale-control.ts +1 -1
  43. package/treb-grid/src/types/sheet.ts +330 -26
  44. package/treb-grid/src/types/sheet_types.ts +4 -0
  45. package/treb-grid/src/util/dom_utilities.ts +58 -25
  46. package/treb-grid/src/editors/formula_editor_base.ts +0 -912
  47. package/treb-grid/src/types/external_editor.ts +0 -27
  48. /package/{README-shadow-DOM.md → notes/shadow-DOM.md} +0 -0
@@ -31,34 +31,48 @@ import type {
31
31
  GridSelection, CellEvent, FunctionDescriptor,
32
32
  AnnotationViewData,
33
33
  AnnotationType,
34
- ExternalEditorType,
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,
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
 
@@ -355,7 +370,7 @@ export class EmbeddedSpreadsheet {
355
370
 
356
371
  /** localized parser instance. we're sharing. */
357
372
  protected get parser(): Parser {
358
- return this.calculator.parser;
373
+ return this.model.parser;
359
374
  }
360
375
 
361
376
  /** for destruction */
@@ -659,7 +674,7 @@ export class EmbeddedSpreadsheet {
659
674
  // if we don't have a container. that's distinct (at the moment)
660
675
  // from headless, which is a state that can change.
661
676
 
662
- this.grid = new Grid(grid_options, this.parser, this.model, undefined, !!container);
677
+ this.grid = new Grid(grid_options, this.model, undefined, !!container);
663
678
 
664
679
  if (this.options.headless) {
665
680
  this.grid.headless = true; // FIXME: move into grid options
@@ -1177,8 +1192,8 @@ export class EmbeddedSpreadsheet {
1177
1192
  * to support outside tooling by highlighting a list of arguments and
1178
1193
  * responding to selection.
1179
1194
  */
1180
- public ExternalEditor(editor?: Partial<ExternalEditorType>) {
1181
- this.grid.ExternalEditor(editor);
1195
+ public ExternalEditor(config?: Partial<ExternalEditorConfig>) {
1196
+ this.grid.ExternalEditor(config);
1182
1197
  }
1183
1198
 
1184
1199
  /**
@@ -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
  /**
@@ -1846,7 +2133,7 @@ export class EmbeddedSpreadsheet {
1846
2133
  * @param reference
1847
2134
  */
1848
2135
  public InsertTable(range?: RangeReference, options: InsertTableOptions = {}) {
1849
- const area = range ? this.calculator.ResolveArea(range, this.grid.active_sheet) : this.GetSelectionReference().area;
2136
+ const area = range ? this.model.ResolveArea(range, this.grid.active_sheet) : this.GetSelectionReference().area;
1850
2137
 
1851
2138
  let theme = options.theme;
1852
2139
  if (typeof theme === 'number') {
@@ -1928,7 +2215,7 @@ export class EmbeddedSpreadsheet {
1928
2215
  let target: IRectangle | Partial<Area> | undefined;
1929
2216
 
1930
2217
  if (rect) {
1931
- target = Rectangle.IsRectangle(rect) ? rect : this.calculator.ResolveArea(rect, this.grid.active_sheet);
2218
+ target = Rectangle.IsRectangle(rect) ? rect : this.model.ResolveArea(rect, this.grid.active_sheet);
1932
2219
  }
1933
2220
 
1934
2221
  if (argument_separator && argument_separator !== this.parser.argument_separator) {
@@ -2273,7 +2560,7 @@ export class EmbeddedSpreadsheet {
2273
2560
 
2274
2561
  // API v1 OK
2275
2562
 
2276
- this.grid.MergeCells(range ? this.calculator.ResolveArea(range, this.grid.active_sheet) : undefined);
2563
+ this.grid.MergeCells(range ? this.model.ResolveArea(range, this.grid.active_sheet) : undefined);
2277
2564
  }
2278
2565
 
2279
2566
  /**
@@ -2287,7 +2574,7 @@ export class EmbeddedSpreadsheet {
2287
2574
 
2288
2575
  // API v1 OK
2289
2576
 
2290
- this.grid.UnmergeCells(range ? this.calculator.ResolveArea(range, this.grid.active_sheet) : undefined);
2577
+ this.grid.UnmergeCells(range ? this.model.ResolveArea(range, this.grid.active_sheet) : undefined);
2291
2578
  }
2292
2579
 
2293
2580
  /**
@@ -2954,11 +3241,11 @@ export class EmbeddedSpreadsheet {
2954
3241
  // FIXME: optional? parameter? (...)
2955
3242
 
2956
3243
  if (data.rendered_values && !options.recalculate) {
2957
- this.grid.Update();
2958
3244
  this.calculator.RebuildClean(true);
3245
+ this.ApplyConditionalFormats(this.grid.active_sheet, false);
3246
+ this.grid.Update();
2959
3247
  }
2960
3248
  else {
2961
- // console.info('load recalc');
2962
3249
  this.Recalculate();
2963
3250
  }
2964
3251
 
@@ -2996,7 +3283,7 @@ export class EmbeddedSpreadsheet {
2996
3283
  // API v1 OK
2997
3284
 
2998
3285
  if (typeof address === 'string') {
2999
- const reference = this.calculator.ResolveAddress(address, this.grid.active_sheet);
3286
+ const reference = this.model.ResolveAddress(address, this.grid.active_sheet);
3000
3287
  address = IsCellAddress(reference) ? reference : reference.start;
3001
3288
  }
3002
3289
 
@@ -3016,7 +3303,7 @@ export class EmbeddedSpreadsheet {
3016
3303
  public SetValidation(address: AddressReference, validation?: RangeReference|CellValue[], error?: boolean) {
3017
3304
 
3018
3305
  if (typeof address === 'string') {
3019
- const reference = this.calculator.ResolveAddress(address, this.grid.active_sheet);
3306
+ const reference = this.model.ResolveAddress(address, this.grid.active_sheet);
3020
3307
  address = IsCellAddress(reference) ? reference : reference.start;
3021
3308
  }
3022
3309
 
@@ -3024,7 +3311,7 @@ export class EmbeddedSpreadsheet {
3024
3311
  this.grid.SetValidation(address, validation, error);
3025
3312
  }
3026
3313
  else {
3027
- const range = this.calculator.ResolveArea(validation, this.grid.active_sheet);
3314
+ const range = this.model.ResolveArea(validation, this.grid.active_sheet);
3028
3315
  this.grid.SetValidation(address, range, error);
3029
3316
  }
3030
3317
 
@@ -3034,7 +3321,7 @@ export class EmbeddedSpreadsheet {
3034
3321
  public RemoveValidation(address: AddressReference) {
3035
3322
 
3036
3323
  if (typeof address === 'string') {
3037
- const reference = this.calculator.ResolveAddress(address);
3324
+ const reference = this.model.ResolveAddress(address);
3038
3325
  address = IsCellAddress(reference) ? reference : reference.start;
3039
3326
  }
3040
3327
 
@@ -3045,7 +3332,7 @@ export class EmbeddedSpreadsheet {
3045
3332
  public SetValidationList(address: AddressReference, list: CellValue[]) {
3046
3333
 
3047
3334
  if (typeof address === 'string') {
3048
- const reference = this.calculator.ResolveAddress(address);
3335
+ const reference = this.model.ResolveAddress(address);
3049
3336
  address = IsCellAddress(reference) ? reference : reference.start;
3050
3337
  }
3051
3338
 
@@ -3056,11 +3343,11 @@ export class EmbeddedSpreadsheet {
3056
3343
  public SetValidationRange(address: AddressReference, range: RangeReference) {
3057
3344
 
3058
3345
  if (typeof address === 'string') {
3059
- const reference = this.calculator.ResolveAddress(address);
3346
+ const reference = this.model.ResolveAddress(address);
3060
3347
  address = IsCellAddress(reference) ? reference : reference.start;
3061
3348
  }
3062
3349
 
3063
- range = this.calculator.ResolveArea(range);
3350
+ range = this.model.ResolveArea(range);
3064
3351
  this.grid.SetValidation(address, range);
3065
3352
 
3066
3353
  }
@@ -3248,6 +3535,7 @@ export class EmbeddedSpreadsheet {
3248
3535
  }
3249
3536
 
3250
3537
  this.calculator.Calculate(area);
3538
+ this.ApplyConditionalFormats(this.grid.active_sheet, false);
3251
3539
 
3252
3540
  this.grid.Update(true); // , area);
3253
3541
  this.UpdateAnnotations();
@@ -3364,7 +3652,7 @@ export class EmbeddedSpreadsheet {
3364
3652
  // API v1 OK
3365
3653
 
3366
3654
  if (typeof address === 'string') {
3367
- const reference = this.calculator.ResolveAddress(address, this.grid.active_sheet);
3655
+ const reference = this.model.ResolveAddress(address, this.grid.active_sheet);
3368
3656
  address = IsCellAddress(reference) ? reference : reference.start;
3369
3657
  }
3370
3658
 
@@ -3404,7 +3692,7 @@ export class EmbeddedSpreadsheet {
3404
3692
  // FIXME: we're using the sheet EnsureAddress method, but that should
3405
3693
  // move either in here or into some sort of helper class
3406
3694
 
3407
- const result = this.calculator.ResolveAddress(reference, this.grid.active_sheet);
3695
+ const result = this.model.ResolveAddress(reference, this.grid.active_sheet);
3408
3696
 
3409
3697
  if (IsCellAddress(result)) {
3410
3698
  return result.sheet_id ? result : undefined;
@@ -3432,6 +3720,9 @@ export class EmbeddedSpreadsheet {
3432
3720
  ref = resolved;
3433
3721
  }
3434
3722
 
3723
+ return this.calculator.Unresolve(ref, this.grid.active_sheet, qualified, named);
3724
+
3725
+ /*
3435
3726
  let range = '';
3436
3727
  const area = IsCellAddress(ref) ? new Area(ref) : new Area(ref.start, ref.end);
3437
3728
 
@@ -3457,10 +3748,11 @@ export class EmbeddedSpreadsheet {
3457
3748
  // the active selection must be on the active sheet? (...)
3458
3749
 
3459
3750
  const sheet_id = area.start.sheet_id || this.grid.active_sheet.id;
3460
- const sheet_name = this.ResolveSheetName(sheet_id, true);
3751
+ const sheet_name = this.calculator.ResolveSheetName(sheet_id, true);
3461
3752
 
3462
3753
  return sheet_name ? sheet_name + '!' + range : range;
3463
-
3754
+ */
3755
+
3464
3756
  }
3465
3757
 
3466
3758
  /**
@@ -3482,7 +3774,7 @@ export class EmbeddedSpreadsheet {
3482
3774
 
3483
3775
  return this.calculator.Evaluate(
3484
3776
  expression, this.grid.active_sheet, options);
3485
-
3777
+
3486
3778
  }
3487
3779
 
3488
3780
  /**
@@ -3522,7 +3814,7 @@ export class EmbeddedSpreadsheet {
3522
3814
  // the active selection must be on the active sheet? (...)
3523
3815
 
3524
3816
  const sheet_id = ref.area.start.sheet_id || this.grid.active_sheet.id;
3525
- const sheet_name = this.ResolveSheetName(sheet_id, true);
3817
+ const sheet_name = this.calculator.ResolveSheetName(sheet_id, true);
3526
3818
 
3527
3819
  return sheet_name ? sheet_name + '!' + range : range;
3528
3820
 
@@ -3600,7 +3892,7 @@ export class EmbeddedSpreadsheet {
3600
3892
 
3601
3893
  // still for now we can take advantage of that and skip the check.
3602
3894
 
3603
- this.grid.ApplyBorders2(range ? this.calculator.ResolveArea(range, this.grid.active_sheet) : undefined, borders, {}, width);
3895
+ this.grid.ApplyBorders2(range ? this.model.ResolveArea(range, this.grid.active_sheet) : undefined, borders, {}, width);
3604
3896
 
3605
3897
  }
3606
3898
 
@@ -3623,7 +3915,7 @@ export class EmbeddedSpreadsheet {
3623
3915
  // translate old-style alignment constants (UPDATE: moved to grid)
3624
3916
 
3625
3917
  this.grid.ApplyStyle(
3626
- range ? this.calculator.ResolveArea(range, this.grid.active_sheet) : undefined, style, delta);
3918
+ range ? this.model.ResolveArea(range, this.grid.active_sheet) : undefined, style, delta);
3627
3919
  }
3628
3920
 
3629
3921
  /**
@@ -3687,7 +3979,7 @@ export class EmbeddedSpreadsheet {
3687
3979
 
3688
3980
  if (typeof value === 'object') {
3689
3981
  if (IsCellAddress(value) || IsArea(value)) {
3690
- this.grid.SetName(name, this.calculator.ResolveArea(value, this.grid.active_sheet));
3982
+ this.grid.SetName(name, this.model.ResolveArea(value, this.grid.active_sheet));
3691
3983
  return;
3692
3984
  }
3693
3985
  }
@@ -3704,7 +3996,7 @@ export class EmbeddedSpreadsheet {
3704
3996
  switch (parse_result.expression.type) {
3705
3997
  case 'address':
3706
3998
  case 'range':
3707
- this.grid.SetName(name, this.calculator.ResolveArea(parse_result.expression, this.grid.active_sheet));
3999
+ this.grid.SetName(name, this.model.ResolveArea(parse_result.expression, this.grid.active_sheet));
3708
4000
  return;
3709
4001
  }
3710
4002
  this.grid.SetName(name, undefined, value);
@@ -3730,7 +4022,7 @@ export class EmbeddedSpreadsheet {
3730
4022
 
3731
4023
  // NOTE: AC is handled internally
3732
4024
 
3733
- this.grid.SetName(name, this.calculator.ResolveArea(range, this.grid.active_sheet));
4025
+ this.grid.SetName(name, this.model.ResolveArea(range, this.grid.active_sheet));
3734
4026
  */
3735
4027
  }
3736
4028
 
@@ -3756,7 +4048,7 @@ export class EmbeddedSpreadsheet {
3756
4048
  // API v1 OK
3757
4049
 
3758
4050
  if (typeof address === 'string') {
3759
- const reference = this.calculator.ResolveAddress(address, this.grid.active_sheet);
4051
+ const reference = this.model.ResolveAddress(address, this.grid.active_sheet);
3760
4052
  address = IsCellAddress(reference) ? reference : reference.start;
3761
4053
  }
3762
4054
 
@@ -3784,7 +4076,7 @@ export class EmbeddedSpreadsheet {
3784
4076
  let resolved: Area|undefined = undefined;
3785
4077
 
3786
4078
  if (!!range) {
3787
- resolved = this.calculator.ResolveArea(range, this.grid.active_sheet);
4079
+ resolved = this.model.ResolveArea(range, this.grid.active_sheet);
3788
4080
  if (resolved.start.sheet_id) {
3789
4081
  if (resolved.start.sheet_id !== this.grid.active_sheet.id) {
3790
4082
  this.grid.ActivateSheetID(resolved.start.sheet_id);
@@ -3826,7 +4118,7 @@ export class EmbeddedSpreadsheet {
3826
4118
  }
3827
4119
  }
3828
4120
 
3829
- return this.grid.GetRange(this.calculator.ResolveAddress(range, this.grid.active_sheet), options.type);
4121
+ return this.grid.GetRange(this.model.ResolveAddress(range, this.grid.active_sheet), options.type);
3830
4122
 
3831
4123
  }
3832
4124
 
@@ -3855,7 +4147,7 @@ export class EmbeddedSpreadsheet {
3855
4147
 
3856
4148
  if (!range) { return undefined; }
3857
4149
 
3858
- return this.grid.GetRangeStyle(this.calculator.ResolveAddress(range, this.grid.active_sheet), apply_theme);
4150
+ return this.grid.GetRangeStyle(this.model.ResolveAddress(range, this.grid.active_sheet), apply_theme);
3859
4151
 
3860
4152
  }
3861
4153
 
@@ -3884,7 +4176,7 @@ export class EmbeddedSpreadsheet {
3884
4176
  }
3885
4177
 
3886
4178
  if (range) {
3887
- const area = this.calculator.ResolveArea(range, this.grid.active_sheet);
4179
+ const area = this.model.ResolveArea(range, this.grid.active_sheet);
3888
4180
 
3889
4181
  if (options.spill && Array.isArray(data)) {
3890
4182
  const rows = data.length;
@@ -3940,6 +4232,47 @@ export class EmbeddedSpreadsheet {
3940
4232
 
3941
4233
  // --- internal (protected) methods ------------------------------------------
3942
4234
 
4235
+ /**
4236
+ *
4237
+ */
4238
+ protected ApplyConditionalFormats(sheet: Sheet, call_update: boolean) {
4239
+
4240
+ // const sheet = this.grid.active_sheet;
4241
+ const areas: IArea[] = [];
4242
+
4243
+ if (sheet.conditional_formats.length || sheet.flush_conditional_formats) {
4244
+
4245
+ for (const entry of sheet.conditional_formats) {
4246
+ areas.push(entry.area);
4247
+
4248
+ // NOTE: we're (optionally) adding the gradient here, instead
4249
+ // of when it's created, because we might want to flush this
4250
+ // from time to time. specifically, because gradient might use
4251
+ // theme colors, we need to flush it when the theme is updated.
4252
+
4253
+ // so don't move it, even though it is tempting. or rewrite to
4254
+ // update on theme changes.
4255
+
4256
+ if (entry.type === 'gradient') {
4257
+ if (!entry.internal) {
4258
+ entry.internal = {};
4259
+ }
4260
+ if (!entry.internal.gradient) {
4261
+ entry.internal.gradient = new Gradient(entry.stops, this.grid.theme, entry.color_space);
4262
+ }
4263
+ }
4264
+ }
4265
+
4266
+ sheet.ApplyConditionalFormats();
4267
+
4268
+ }
4269
+
4270
+ if (call_update) {
4271
+ this.grid.Update(true, areas);
4272
+ }
4273
+
4274
+ }
4275
+
3943
4276
  protected ResolveTable(reference: RangeReference): Table|undefined {
3944
4277
 
3945
4278
  let table: Table|undefined = undefined;
@@ -3953,7 +4286,7 @@ export class EmbeddedSpreadsheet {
3953
4286
 
3954
4287
  if (!table) {
3955
4288
 
3956
- let address = this.calculator.ResolveAddress(reference, this.grid.active_sheet);
4289
+ let address = this.model.ResolveAddress(reference, this.grid.active_sheet);
3957
4290
 
3958
4291
  if (!IsCellAddress(address)) {
3959
4292
  address = address.start;
@@ -4044,6 +4377,11 @@ export class EmbeddedSpreadsheet {
4044
4377
 
4045
4378
  this.InflateAnnotations();
4046
4379
 
4380
+ // and conditional formats
4381
+
4382
+ this.calculator.UpdateConditionals();
4383
+ this.ApplyConditionalFormats(this.grid.active_sheet, false);
4384
+
4047
4385
  }
4048
4386
  else {
4049
4387
  return reject('unknown error (missing data)');
@@ -4164,6 +4502,9 @@ export class EmbeddedSpreadsheet {
4164
4502
 
4165
4503
  this.UpdateAnnotations();
4166
4504
 
4505
+ this.calculator.UpdateConditionals();
4506
+ this.ApplyConditionalFormats(event.activate, true);
4507
+
4167
4508
  }
4168
4509
 
4169
4510
  protected HandleDrag(event: DragEvent): void {
@@ -4556,7 +4897,7 @@ export class EmbeddedSpreadsheet {
4556
4897
  protected UpdateAnnotations(): void {
4557
4898
  for (const annotation of this.grid.active_sheet.annotations) {
4558
4899
  if (annotation.temp.vertex) {
4559
- const vertex = annotation.temp.vertex as LeafVertex;
4900
+ const vertex = annotation.temp.vertex as StateLeafVertex;
4560
4901
  if (vertex.state_id !== annotation.temp.state) {
4561
4902
  annotation.temp.state = vertex.state_id;
4562
4903
 
@@ -4703,7 +5044,7 @@ export class EmbeddedSpreadsheet {
4703
5044
 
4704
5045
  this.parser.Walk(parse_result.expression, (unit) => {
4705
5046
  if (unit.type === 'address' || unit.type === 'range') {
4706
- this.calculator.ResolveSheetID(unit, undefined, this.grid.active_sheet);
5047
+ this.model.ResolveSheetID(unit, undefined, this.grid.active_sheet);
4707
5048
  }
4708
5049
  return true;
4709
5050
  });
@@ -5436,35 +5777,5 @@ export class EmbeddedSpreadsheet {
5436
5777
  }
5437
5778
  }
5438
5779
 
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
5780
 
5470
5781
  }