@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.
- package/dist/treb-spreadsheet.mjs +14 -14
- package/dist/treb.d.ts +28 -26
- package/notes/conditional-fomratring.md +29 -0
- package/package.json +1 -1
- package/treb-base-types/src/area.ts +181 -0
- package/{treb-grid/src/util/dom_utilities.ts → treb-base-types/src/dom-utilities.ts} +29 -20
- package/treb-base-types/src/evaluate-options.ts +21 -0
- package/treb-base-types/src/gradient.ts +97 -0
- package/treb-base-types/src/import.ts +2 -1
- package/treb-base-types/src/index.ts +3 -0
- package/treb-base-types/src/theme.ts +3 -5
- package/treb-calculator/src/calculator.ts +190 -28
- package/treb-calculator/src/dag/calculation_leaf_vertex.ts +97 -0
- package/treb-calculator/src/dag/graph.ts +10 -22
- package/treb-calculator/src/dag/{leaf_vertex.ts → state_leaf_vertex.ts} +3 -3
- package/treb-calculator/src/descriptors.ts +10 -3
- package/treb-calculator/src/expression-calculator.ts +1 -1
- package/treb-calculator/src/function-library.ts +25 -22
- package/treb-calculator/src/functions/base-functions.ts +166 -5
- package/treb-calculator/src/index.ts +6 -6
- package/treb-calculator/src/notifier-types.ts +1 -1
- package/treb-calculator/src/utilities.ts +2 -2
- package/treb-charts/src/util.ts +2 -2
- package/treb-embed/src/custom-element/global.d.ts +3 -1
- package/treb-embed/src/custom-element/spreadsheet-constructor.ts +13 -19
- package/treb-embed/src/embedded-spreadsheet.ts +378 -132
- package/treb-embed/src/spinner.ts +3 -3
- package/treb-embed/style/layout.scss +1 -1
- package/treb-export/src/drawing2/chart2.ts +11 -2
- package/treb-export/src/export-worker/export-worker.ts +0 -13
- package/treb-export/src/export2.ts +197 -2
- package/treb-export/src/import2.ts +169 -4
- package/treb-export/src/workbook-style2.ts +59 -10
- package/treb-export/src/workbook2.ts +10 -1
- package/treb-grid/src/editors/autocomplete.ts +28 -24
- package/treb-grid/src/editors/editor.ts +3 -4
- package/treb-grid/src/editors/formula_bar.ts +1 -1
- package/treb-grid/src/index.ts +2 -1
- package/treb-grid/src/layout/base_layout.ts +34 -31
- package/treb-grid/src/layout/grid_layout.ts +17 -28
- package/treb-grid/src/render/selection-renderer.ts +2 -3
- package/treb-grid/src/render/svg_header_overlay.ts +4 -11
- package/treb-grid/src/render/svg_selection_block.ts +27 -34
- package/treb-grid/src/render/tile_renderer.ts +8 -6
- package/treb-grid/src/types/conditional_format.ts +168 -0
- package/treb-grid/src/types/grid.ts +37 -47
- package/treb-grid/src/types/grid_base.ts +188 -33
- package/treb-grid/src/types/scale-control.ts +2 -2
- package/treb-grid/src/types/sheet.ts +332 -28
- package/treb-grid/src/types/sheet_types.ts +4 -0
- package/treb-grid/src/types/tab_bar.ts +4 -8
- package/treb-utils/src/index.ts +0 -1
- package/treb-utils/src/resizable.ts +26 -27
- package/treb-utils/src/template.ts +0 -70
- /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
|
|
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
|
-
*
|
|
1924
|
-
*
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
}
|