@trebco/treb 28.7.0 → 28.10.5
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-light.mjs +14 -14
- package/dist/treb-spreadsheet.mjs +12 -12
- package/dist/treb.d.ts +39 -6
- package/notes/connected-elements.md +37 -0
- package/package.json +1 -1
- package/treb-base-types/src/gradient.ts +1 -1
- package/treb-base-types/src/localization.ts +6 -0
- package/treb-calculator/src/calculator.ts +72 -30
- package/treb-calculator/src/dag/calculation_leaf_vertex.ts +7 -0
- package/treb-calculator/src/dag/graph.ts +8 -0
- package/treb-calculator/src/functions/base-functions.ts +30 -1
- package/treb-calculator/src/index.ts +1 -1
- package/treb-charts/src/chart-functions.ts +14 -0
- package/treb-charts/src/chart-types.ts +25 -1
- package/treb-charts/src/chart-utils.ts +195 -9
- package/treb-charts/src/chart.ts +4 -0
- package/treb-charts/src/default-chart-renderer.ts +17 -1
- package/treb-charts/src/renderer.ts +182 -9
- package/treb-charts/style/charts.scss +39 -0
- package/treb-embed/markup/toolbar.html +35 -34
- package/treb-embed/src/custom-element/treb-global.ts +10 -2
- package/treb-embed/src/embedded-spreadsheet.ts +209 -106
- package/treb-embed/src/options.ts +7 -0
- package/treb-embed/style/layout.scss +4 -0
- package/treb-embed/style/toolbar.scss +37 -0
- package/treb-grid/src/index.ts +1 -1
- package/treb-grid/src/types/conditional_format.ts +1 -1
- package/treb-grid/src/types/data_model.ts +32 -0
- package/treb-grid/src/types/grid.ts +11 -1
- package/treb-grid/src/types/grid_base.ts +161 -5
- package/treb-grid/src/types/grid_command.ts +32 -0
- package/treb-grid/src/types/grid_events.ts +7 -0
- package/treb-grid/src/types/grid_options.ts +8 -0
- package/treb-grid/src/types/sheet.ts +0 -56
- package/treb-grid/src/types/update_flags.ts +1 -0
- package/treb-parser/src/parser-types.ts +6 -0
- package/treb-parser/src/parser.ts +48 -1
|
@@ -10,7 +10,7 @@ import type { EmbeddedSpreadsheet } from '../embedded-spreadsheet';
|
|
|
10
10
|
export class TREBGlobal {
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Package version
|
|
14
14
|
*
|
|
15
15
|
* @privateRemarks
|
|
16
16
|
*
|
|
@@ -25,7 +25,15 @@ export class TREBGlobal {
|
|
|
25
25
|
public version = process.env.BUILD_VERSION || '';
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* Create a spreadsheet. The `USER_DATA_TYPE` template parameter is the type
|
|
29
|
+
* assigned to the `user_data` field of the spreadsheet instance -- it can
|
|
30
|
+
* help simplify typing if you are storing extra data in spreadsheet
|
|
31
|
+
* files.
|
|
32
|
+
*
|
|
33
|
+
* Just ignore this parameter if you don't need it.
|
|
34
|
+
*
|
|
35
|
+
* @typeParam USER_DATA_TYPE - type for the `user_data` field in the
|
|
36
|
+
* spreadsheet instance
|
|
29
37
|
*/
|
|
30
38
|
public CreateSpreadsheet<USER_DATA_TYPE = unknown>(options: EmbeddedSpreadsheetOptions): EmbeddedSpreadsheet<USER_DATA_TYPE> {
|
|
31
39
|
const container = options.container;
|
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
} from 'treb-base-types';
|
|
70
70
|
|
|
71
71
|
import { EventSource, ValidateURI } from 'treb-utils';
|
|
72
|
-
import { NumberFormatCache, ValueParser, NumberFormat } from 'treb-format';
|
|
72
|
+
import { NumberFormatCache, ValueParser, NumberFormat, LotusDate, UnlotusDate } from 'treb-format';
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
|
|
@@ -87,6 +87,10 @@ import type { BorderToolbarMessage, ToolbarMessage } from './toolbar-message';
|
|
|
87
87
|
import { Chart, ChartFunctions } from 'treb-charts';
|
|
88
88
|
import type { SetRangeOptions } from 'treb-grid';
|
|
89
89
|
|
|
90
|
+
import type { StateLeafVertex } from 'treb-calculator';
|
|
91
|
+
import type { ConnectedElementType } from 'treb-grid';
|
|
92
|
+
|
|
93
|
+
|
|
90
94
|
// --- worker ------------------------------------------------------------------
|
|
91
95
|
|
|
92
96
|
/**
|
|
@@ -95,7 +99,6 @@ import type { SetRangeOptions } from 'treb-grid';
|
|
|
95
99
|
* the script so we can run it as a worker.
|
|
96
100
|
*/
|
|
97
101
|
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';
|
|
99
102
|
|
|
100
103
|
// --- types -------------------------------------------------------------------
|
|
101
104
|
|
|
@@ -486,13 +489,19 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
486
489
|
this.DocumentChange();
|
|
487
490
|
}
|
|
488
491
|
|
|
489
|
-
/**
|
|
492
|
+
/**
|
|
493
|
+
* opaque user data (metadata). `USER_DATA_TYPE` is a template
|
|
494
|
+
* parameter you can set when creating the spreadsheet.
|
|
495
|
+
*/
|
|
490
496
|
public get user_data(): USER_DATA_TYPE|undefined {
|
|
491
497
|
return this.grid.model.user_data;
|
|
492
498
|
}
|
|
493
499
|
|
|
494
|
-
/**
|
|
495
|
-
|
|
500
|
+
/**
|
|
501
|
+
* opaque user data (metadata). `USER_DATA_TYPE` is a template
|
|
502
|
+
* parameter you can set when creating the spreadsheet.
|
|
503
|
+
*/
|
|
504
|
+
public set user_data(data: USER_DATA_TYPE|undefined) {
|
|
496
505
|
this.grid.model.user_data = data;
|
|
497
506
|
this.DocumentChange();
|
|
498
507
|
}
|
|
@@ -883,6 +892,8 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
883
892
|
|
|
884
893
|
this.grid.grid_events.Subscribe((event) => {
|
|
885
894
|
|
|
895
|
+
// console.info({event});
|
|
896
|
+
|
|
886
897
|
switch (event.type) {
|
|
887
898
|
|
|
888
899
|
case 'error':
|
|
@@ -986,7 +997,14 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
986
997
|
|
|
987
998
|
case 'structure':
|
|
988
999
|
{
|
|
1000
|
+
// console.info("S event", event);
|
|
1001
|
+
|
|
989
1002
|
const cached_selection = this.last_selection;
|
|
1003
|
+
if (event.conditional_format) {
|
|
1004
|
+
this.calculator.UpdateConditionals();
|
|
1005
|
+
this.ApplyConditionalFormats(this.grid.active_sheet, false);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
990
1008
|
if (event.rebuild_required) {
|
|
991
1009
|
this.calculator.Reset();
|
|
992
1010
|
|
|
@@ -1412,12 +1430,6 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
1412
1430
|
...options,
|
|
1413
1431
|
};
|
|
1414
1432
|
|
|
1415
|
-
// we need to calculate the formula once, to get an initial state
|
|
1416
|
-
// update: internal
|
|
1417
|
-
// let result = this.Evaluate(this.Unresolve(area, true, false) + ' ' + options.expression, options.options);
|
|
1418
|
-
|
|
1419
|
-
// ... apply ...
|
|
1420
|
-
|
|
1421
1433
|
this.AddConditionalFormat(format);
|
|
1422
1434
|
return format;
|
|
1423
1435
|
|
|
@@ -1445,18 +1457,6 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
1445
1457
|
...options,
|
|
1446
1458
|
};
|
|
1447
1459
|
|
|
1448
|
-
/*
|
|
1449
|
-
// we need to calculate the formula once, to get an initial state
|
|
1450
|
-
let result = this.Evaluate(options.expression, options.options);
|
|
1451
|
-
|
|
1452
|
-
if (Array.isArray(result)) {
|
|
1453
|
-
result = result[0][0];
|
|
1454
|
-
}
|
|
1455
|
-
const applied = !!result;
|
|
1456
|
-
|
|
1457
|
-
this.AddConditionalFormat({...format, applied });
|
|
1458
|
-
*/
|
|
1459
|
-
|
|
1460
1460
|
this.AddConditionalFormat(format);
|
|
1461
1461
|
|
|
1462
1462
|
return format;
|
|
@@ -1470,20 +1470,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
1470
1470
|
*/
|
|
1471
1471
|
public AddConditionalFormat(format: ConditionalFormat) {
|
|
1472
1472
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
if (!sheet) {
|
|
1476
|
-
throw new Error('invalid reference in format');
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
sheet.conditional_formats.push(format);
|
|
1480
|
-
|
|
1481
|
-
this.calculator.UpdateConditionals(format, sheet);
|
|
1482
|
-
|
|
1483
|
-
// call update if it's the current sheet
|
|
1484
|
-
this.ApplyConditionalFormats(sheet, sheet === this.grid.active_sheet);
|
|
1485
|
-
|
|
1486
|
-
this.PushUndo();
|
|
1473
|
+
this.grid.AddConditionalFormat(format);
|
|
1487
1474
|
|
|
1488
1475
|
// fluent
|
|
1489
1476
|
return format;
|
|
@@ -1496,40 +1483,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
1496
1483
|
* @internal
|
|
1497
1484
|
*/
|
|
1498
1485
|
public RemoveConditionalFormat(format: ConditionalFormat) {
|
|
1499
|
-
|
|
1500
|
-
const sheet = area.start.sheet_id ? this.model.sheets.Find(area.start.sheet_id) : this.grid.active_sheet;
|
|
1501
|
-
|
|
1502
|
-
if (!sheet) {
|
|
1503
|
-
throw new Error('invalid reference in format');
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
let count = 0;
|
|
1507
|
-
sheet.conditional_formats = sheet.conditional_formats.filter(test => {
|
|
1508
|
-
if (test === format) {
|
|
1509
|
-
count++;
|
|
1510
|
-
this.calculator.RemoveConditional(test);
|
|
1511
|
-
return false;
|
|
1512
|
-
}
|
|
1513
|
-
return true;
|
|
1514
|
-
});
|
|
1515
|
-
|
|
1516
|
-
if (count) {
|
|
1517
|
-
sheet.FlushConditionalFormats();
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
// we want to call update if it's the current sheet,
|
|
1521
|
-
// but we want a full repaint
|
|
1522
|
-
|
|
1523
|
-
this.ApplyConditionalFormats(sheet, false);
|
|
1524
|
-
|
|
1525
|
-
if (sheet === this.grid.active_sheet) {
|
|
1526
|
-
this.grid.Update(true);
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
if (count) {
|
|
1530
|
-
this.PushUndo();
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1486
|
+
this.grid.RemoveConditionalFormat({ format });
|
|
1533
1487
|
}
|
|
1534
1488
|
|
|
1535
1489
|
/**
|
|
@@ -1548,37 +1502,9 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
1548
1502
|
}
|
|
1549
1503
|
range = ref.area;
|
|
1550
1504
|
}
|
|
1551
|
-
const area = this.model.ResolveArea(range, this.grid.active_sheet);
|
|
1552
|
-
const sheet = area.start.sheet_id ? this.model.sheets.Find(area.start.sheet_id) : this.grid.active_sheet;
|
|
1553
|
-
|
|
1554
|
-
if (sheet) {
|
|
1555
|
-
let count = 0;
|
|
1556
|
-
|
|
1557
|
-
sheet.conditional_formats = sheet.conditional_formats.filter(test => {
|
|
1558
|
-
const compare = new Area(test.area.start, test.area.end);
|
|
1559
|
-
if (compare.Intersects(area)) {
|
|
1560
|
-
count++;
|
|
1561
|
-
this.calculator.RemoveConditional(test);
|
|
1562
|
-
return false;
|
|
1563
|
-
}
|
|
1564
|
-
return true;
|
|
1565
|
-
});
|
|
1566
|
-
|
|
1567
|
-
if (count) {
|
|
1568
|
-
|
|
1569
|
-
sheet.FlushConditionalFormats();
|
|
1570
|
-
|
|
1571
|
-
this.ApplyConditionalFormats(sheet, false);
|
|
1572
|
-
|
|
1573
|
-
if (sheet === this.grid.active_sheet) {
|
|
1574
|
-
this.grid.Update(true);
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
this.PushUndo();
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
}
|
|
1581
1505
|
|
|
1506
|
+
const area = this.model.ResolveArea(range, this.grid.active_sheet);
|
|
1507
|
+
this.grid.RemoveConditionalFormat({ area });
|
|
1582
1508
|
|
|
1583
1509
|
}
|
|
1584
1510
|
|
|
@@ -2274,6 +2200,114 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2274
2200
|
|
|
2275
2201
|
}
|
|
2276
2202
|
|
|
2203
|
+
public RemoveConnectedChart(id: number) {
|
|
2204
|
+
const element = this.model.RemoveConnectedElement(id);
|
|
2205
|
+
if (element) {
|
|
2206
|
+
const removed = this.calculator.RemoveConnectedELement(element);
|
|
2207
|
+
if (removed) {
|
|
2208
|
+
// ... actually don't need to update here
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
public UpdateConnectedChart(id: number, formula: string) {
|
|
2214
|
+
const element = this.model.connected_elements.get(id);
|
|
2215
|
+
if (element) {
|
|
2216
|
+
element.formula = formula;
|
|
2217
|
+
const internal = (element.internal) as { vertex: StateLeafVertex, state: any };
|
|
2218
|
+
|
|
2219
|
+
if (internal?.state) {
|
|
2220
|
+
internal.state = undefined;
|
|
2221
|
+
}
|
|
2222
|
+
this.calculator.UpdateConnectedElements(this.grid.active_sheet, element);
|
|
2223
|
+
|
|
2224
|
+
if (element.update) {
|
|
2225
|
+
element.update.call(0, element);
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
/**
|
|
2232
|
+
* @internal
|
|
2233
|
+
*
|
|
2234
|
+
* @returns an id that can be used to manage the reference
|
|
2235
|
+
*/
|
|
2236
|
+
public CreateConnectedChart(formula: string, target: HTMLElement, options: EvaluateOptions): number {
|
|
2237
|
+
|
|
2238
|
+
// FIXME: merge w/ insert annotation?
|
|
2239
|
+
|
|
2240
|
+
let r1c1 = options?.r1c1 || false;
|
|
2241
|
+
let argument_separator = options?.argument_separator || this.parser.argument_separator; // default to current
|
|
2242
|
+
|
|
2243
|
+
this.parser.Save();
|
|
2244
|
+
this.parser.flags.r1c1 = r1c1;
|
|
2245
|
+
|
|
2246
|
+
if (argument_separator === ',') {
|
|
2247
|
+
this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
2248
|
+
this.parser.decimal_mark = DecimalMarkType.Period;
|
|
2249
|
+
}
|
|
2250
|
+
else {
|
|
2251
|
+
this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
2252
|
+
this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
const result = this.parser.Parse(formula);
|
|
2256
|
+
|
|
2257
|
+
this.parser.Restore();
|
|
2258
|
+
|
|
2259
|
+
if (result.expression) {
|
|
2260
|
+
formula = '=' + this.parser.Render(result.expression, { missing: '' });
|
|
2261
|
+
}
|
|
2262
|
+
else {
|
|
2263
|
+
console.warn("invalid formula", result.error);
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
const chart = this.CreateChart();
|
|
2267
|
+
chart.Initialize(target);
|
|
2268
|
+
|
|
2269
|
+
const id = this.model.AddConnectedElement({
|
|
2270
|
+
formula,
|
|
2271
|
+
|
|
2272
|
+
// this is circular, but I want to leave `this` bound to the sheet
|
|
2273
|
+
// instance in case we need it -- so what's a better approach? pass
|
|
2274
|
+
// in the formula explicitly, and update if we need to make changes?
|
|
2275
|
+
|
|
2276
|
+
update: (instance: ConnectedElementType) => {
|
|
2277
|
+
|
|
2278
|
+
const parse_result = this.parser.Parse(instance.formula);
|
|
2279
|
+
|
|
2280
|
+
if (parse_result &&
|
|
2281
|
+
parse_result.expression &&
|
|
2282
|
+
parse_result.expression.type === 'call') {
|
|
2283
|
+
|
|
2284
|
+
// FIXME: make a method for doing this
|
|
2285
|
+
|
|
2286
|
+
this.parser.Walk(parse_result.expression, (unit) => {
|
|
2287
|
+
if (unit.type === 'address' || unit.type === 'range') {
|
|
2288
|
+
this.model.ResolveSheetID(unit, undefined, this.grid.active_sheet);
|
|
2289
|
+
}
|
|
2290
|
+
return true;
|
|
2291
|
+
});
|
|
2292
|
+
|
|
2293
|
+
const expr_name = parse_result.expression.name.toLowerCase();
|
|
2294
|
+
const result = this.calculator.CalculateExpression(parse_result.expression);
|
|
2295
|
+
chart.Exec(expr_name, result as ExtendedUnion); // FIXME: type?
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
chart.Update();
|
|
2299
|
+
|
|
2300
|
+
},
|
|
2301
|
+
|
|
2302
|
+
});
|
|
2303
|
+
|
|
2304
|
+
this.calculator.UpdateConnectedElements(this.grid.active_sheet);
|
|
2305
|
+
this.UpdateConnectedElements();
|
|
2306
|
+
|
|
2307
|
+
return id;
|
|
2308
|
+
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2277
2311
|
/**
|
|
2278
2312
|
* Insert an annotation node. Usually this means inserting a chart. Regarding
|
|
2279
2313
|
* the argument separator, see the Evaluate function.
|
|
@@ -2303,11 +2337,21 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2303
2337
|
target = Rectangle.IsRectangle(rect) ? rect : this.model.ResolveArea(rect, this.grid.active_sheet);
|
|
2304
2338
|
}
|
|
2305
2339
|
|
|
2340
|
+
// FIXME: with the new parser save/restore semantics we should
|
|
2341
|
+
// just always do this. also I don't think the r1c1 logic works
|
|
2342
|
+
// properly here... unless we're assuming that the default state
|
|
2343
|
+
// is always off
|
|
2344
|
+
|
|
2306
2345
|
if (argument_separator && argument_separator !== this.parser.argument_separator || r1c1) {
|
|
2346
|
+
|
|
2347
|
+
this.parser.Save();
|
|
2348
|
+
|
|
2349
|
+
/*
|
|
2307
2350
|
const current = {
|
|
2308
2351
|
argument_separator: this.parser.argument_separator,
|
|
2309
2352
|
decimal_mark: this.parser.decimal_mark,
|
|
2310
2353
|
};
|
|
2354
|
+
*/
|
|
2311
2355
|
|
|
2312
2356
|
if (argument_separator === ',') {
|
|
2313
2357
|
this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
@@ -2318,17 +2362,21 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2318
2362
|
this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
2319
2363
|
}
|
|
2320
2364
|
|
|
2321
|
-
const r1c1_state = this.parser.flags.r1c1;
|
|
2322
|
-
if (r1c1)
|
|
2323
|
-
|
|
2324
|
-
|
|
2365
|
+
// const r1c1_state = this.parser.flags.r1c1;
|
|
2366
|
+
// if (r1c1)
|
|
2367
|
+
// {
|
|
2368
|
+
this.parser.flags.r1c1 = !!r1c1;
|
|
2369
|
+
// }
|
|
2325
2370
|
|
|
2326
2371
|
const result = this.parser.Parse(formula);
|
|
2327
2372
|
|
|
2373
|
+
/*
|
|
2328
2374
|
// reset
|
|
2329
2375
|
this.parser.argument_separator = current.argument_separator;
|
|
2330
2376
|
this.parser.decimal_mark = current.decimal_mark;
|
|
2331
2377
|
this.parser.flags.r1c1 = r1c1_state;
|
|
2378
|
+
*/
|
|
2379
|
+
this.parser.Restore();
|
|
2332
2380
|
|
|
2333
2381
|
if (result.expression) {
|
|
2334
2382
|
formula = '=' + this.parser.Render(result.expression, { missing: '' });
|
|
@@ -3977,6 +4025,23 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
3977
4025
|
return NumberFormatCache.Get(format).Format(value);
|
|
3978
4026
|
}
|
|
3979
4027
|
|
|
4028
|
+
/**
|
|
4029
|
+
* convert a javascript date (or timestamp) to a spreadsheet date
|
|
4030
|
+
*/
|
|
4031
|
+
public SpreadsheetDate(javascript_date: number|Date) {
|
|
4032
|
+
if (javascript_date instanceof Date) {
|
|
4033
|
+
javascript_date = javascript_date.getTime();
|
|
4034
|
+
}
|
|
4035
|
+
return UnlotusDate(javascript_date, true);
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
/**
|
|
4039
|
+
* convert a spreadsheet date to a javascript date
|
|
4040
|
+
*/
|
|
4041
|
+
public JavascriptDate(spreadsheet_date: number) {
|
|
4042
|
+
return LotusDate(spreadsheet_date).getTime();
|
|
4043
|
+
}
|
|
4044
|
+
|
|
3980
4045
|
/**
|
|
3981
4046
|
* Apply borders to range.
|
|
3982
4047
|
*
|
|
@@ -4611,7 +4676,13 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
4611
4676
|
|
|
4612
4677
|
this.UpdateAnnotations();
|
|
4613
4678
|
|
|
4679
|
+
// we don't really need to call UpdateConditionals here unless
|
|
4680
|
+
// something has changed in a previously inactive sheet -- right?
|
|
4681
|
+
|
|
4614
4682
|
this.calculator.UpdateConditionals();
|
|
4683
|
+
|
|
4684
|
+
// we do need to call apply (I think)
|
|
4685
|
+
|
|
4615
4686
|
this.ApplyConditionalFormats(event.activate, true);
|
|
4616
4687
|
|
|
4617
4688
|
}
|
|
@@ -4910,6 +4981,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
4910
4981
|
* (just sparklines atm) and update if necessary.
|
|
4911
4982
|
*/
|
|
4912
4983
|
protected UpdateAnnotations(): void {
|
|
4984
|
+
|
|
4913
4985
|
for (const annotation of this.grid.active_sheet.annotations) {
|
|
4914
4986
|
if (annotation.temp.vertex) {
|
|
4915
4987
|
const vertex = annotation.temp.vertex as StateLeafVertex;
|
|
@@ -4947,6 +5019,27 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
4947
5019
|
}
|
|
4948
5020
|
|
|
4949
5021
|
}
|
|
5022
|
+
|
|
5023
|
+
this.UpdateConnectedElements();
|
|
5024
|
+
|
|
5025
|
+
}
|
|
5026
|
+
|
|
5027
|
+
protected UpdateConnectedElements() {
|
|
5028
|
+
for (const element of this.model.connected_elements.values()) {
|
|
5029
|
+
const internal = (element.internal) as { vertex: StateLeafVertex, state: any };
|
|
5030
|
+
if (internal?.vertex && internal.vertex.state_id !== internal.state ) {
|
|
5031
|
+
internal.state = internal.vertex.state_id;
|
|
5032
|
+
const fn = element.update;
|
|
5033
|
+
if (fn) {
|
|
5034
|
+
|
|
5035
|
+
// FIXME: what if there are multiple calls pending? some
|
|
5036
|
+
// kind of a dispatch system might be useful
|
|
5037
|
+
|
|
5038
|
+
Promise.resolve().then(() => fn.call(0, element));
|
|
5039
|
+
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
}
|
|
4950
5043
|
}
|
|
4951
5044
|
|
|
4952
5045
|
/*
|
|
@@ -5798,15 +5891,25 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
5798
5891
|
|
|
5799
5892
|
/**
|
|
5800
5893
|
* handle key down to intercept ctrl+z (undo)
|
|
5801
|
-
*
|
|
5894
|
+
* UPDATE: we're also handling F9 for recalc (optionally)
|
|
5895
|
+
*
|
|
5802
5896
|
* FIXME: redo (ctrl+y or ctrl+shift+z)
|
|
5803
5897
|
*/
|
|
5804
5898
|
protected HandleKeyDown(event: KeyboardEvent): void {
|
|
5899
|
+
|
|
5900
|
+
// can we drop the event.code stuff in 2024? (YES)
|
|
5901
|
+
|
|
5805
5902
|
if (event.ctrlKey && (event.code === 'KeyZ' || event.key === 'z')) {
|
|
5806
5903
|
event.stopPropagation();
|
|
5807
5904
|
event.preventDefault();
|
|
5808
5905
|
this.Undo();
|
|
5809
5906
|
}
|
|
5907
|
+
else if (event.key === 'F9' && this.options.recalculate_on_f9) {
|
|
5908
|
+
event.stopPropagation();
|
|
5909
|
+
event.preventDefault();
|
|
5910
|
+
this.Recalculate();
|
|
5911
|
+
}
|
|
5912
|
+
|
|
5810
5913
|
}
|
|
5811
5914
|
|
|
5812
5915
|
|
|
@@ -304,6 +304,13 @@ export interface EmbeddedSpreadsheetOptions {
|
|
|
304
304
|
*/
|
|
305
305
|
preload?: (instance: unknown) => void;
|
|
306
306
|
|
|
307
|
+
/**
|
|
308
|
+
* handle the F9 key and recalculate the spreadsheet. for compatibility.
|
|
309
|
+
* we're leaving this option to default `false` for now, but that may
|
|
310
|
+
* change in the future. key modifiers have no effect.
|
|
311
|
+
*/
|
|
312
|
+
recalculate_on_f9?: boolean;
|
|
313
|
+
|
|
307
314
|
}
|
|
308
315
|
|
|
309
316
|
/**
|
|
@@ -64,6 +64,10 @@
|
|
|
64
64
|
|
|
65
65
|
font-family: var(--treb-default-font, system-ui, $font-stack);
|
|
66
66
|
|
|
67
|
+
// reset in case we inherit something
|
|
68
|
+
|
|
69
|
+
line-height: normal;
|
|
70
|
+
|
|
67
71
|
div, button, input, ul, ol, li, a, textarea, svg {
|
|
68
72
|
|
|
69
73
|
// maybe this is being too aggressive. we could be a little
|
|
@@ -411,6 +411,43 @@ $swatch-size: 18px;
|
|
|
411
411
|
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
+
.treb-font-scale {
|
|
415
|
+
padding-left: 2em;
|
|
416
|
+
width: 5em;
|
|
417
|
+
text-align: right;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
[composite][font-scale] {
|
|
421
|
+
position: relative;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.treb-font-scale-icon {
|
|
425
|
+
position: absolute;
|
|
426
|
+
top: 50%;
|
|
427
|
+
left: .5em;
|
|
428
|
+
transform: translateY(-50%);
|
|
429
|
+
opacity: .9;
|
|
430
|
+
// border: 1px solid color-mix(in srgb, currentColor 25%, transparent);
|
|
431
|
+
border-radius: 3px;
|
|
432
|
+
// background: color-mix(in srgb, currentColor 10%, transparent);
|
|
433
|
+
|
|
434
|
+
pointer-events: none;
|
|
435
|
+
line-height: 1;
|
|
436
|
+
|
|
437
|
+
&::before, &::after {
|
|
438
|
+
content: 'A';
|
|
439
|
+
position: relative;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
&::before {
|
|
443
|
+
font-size: 1.2em;
|
|
444
|
+
}
|
|
445
|
+
&::after {
|
|
446
|
+
font-size: .9em;
|
|
447
|
+
left: -.125em;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
414
451
|
}
|
|
415
452
|
|
|
416
453
|
}
|
package/treb-grid/src/index.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
export { Grid } from './types/grid';
|
|
23
23
|
export { GridBase } from './types/grid_base';
|
|
24
24
|
export { Sheet } from './types/sheet';
|
|
25
|
-
export { DataModel, type MacroFunction } from './types/data_model';
|
|
25
|
+
export { DataModel, type MacroFunction, type ConnectedElementType } from './types/data_model';
|
|
26
26
|
export type { SerializedNamedExpression, SerializedModel } from './types/data_model';
|
|
27
27
|
export * from './types/grid_events';
|
|
28
28
|
export type { SerializedSheet, FreezePane } from './types/sheet_types';
|
|
@@ -26,6 +26,12 @@ import { NamedRangeCollection } from './named_range';
|
|
|
26
26
|
import { type ExpressionUnit, type UnitAddress, type UnitStructuredReference, type UnitRange, Parser, QuotedSheetNameRegex } from 'treb-parser';
|
|
27
27
|
import { Area, IsCellAddress, Style } from 'treb-base-types';
|
|
28
28
|
|
|
29
|
+
export interface ConnectedElementType {
|
|
30
|
+
formula: string;
|
|
31
|
+
update?: (instance: ConnectedElementType) => void;
|
|
32
|
+
internal?: unknown; // opaque type to prevent circular dependencies
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
export interface SerializedMacroFunction {
|
|
30
36
|
name: string;
|
|
31
37
|
function_def: string;
|
|
@@ -444,6 +450,32 @@ export class DataModel {
|
|
|
444
450
|
return address; // already range or address
|
|
445
451
|
|
|
446
452
|
}
|
|
453
|
+
|
|
454
|
+
public AddConnectedElement(connected_element: ConnectedElementType): number {
|
|
455
|
+
const id = this.connected_element_id++;
|
|
456
|
+
this.connected_elements.set(id, connected_element);
|
|
457
|
+
return id;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
public RemoveConnectedElement(id: number) {
|
|
461
|
+
const element = this.connected_elements.get(id);
|
|
462
|
+
this.connected_elements.delete(id);
|
|
463
|
+
return element;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* identifier for connected elements, used to manage. these need to be
|
|
468
|
+
* unique in the lifetime of a model instance, but no more than that.
|
|
469
|
+
*/
|
|
470
|
+
protected connected_element_id = 100;
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* these are intentionally NOT serialized. they're ephemeral, created
|
|
474
|
+
* at runtime and not persistent.
|
|
475
|
+
*
|
|
476
|
+
* @internal
|
|
477
|
+
*/
|
|
478
|
+
public connected_elements: Map<number, ConnectedElementType> = new Map();
|
|
447
479
|
|
|
448
480
|
}
|
|
449
481
|
|
|
@@ -4627,6 +4627,13 @@ export class Grid extends GridBase {
|
|
|
4627
4627
|
}
|
|
4628
4628
|
}
|
|
4629
4629
|
else {
|
|
4630
|
+
|
|
4631
|
+
// ignore function keys
|
|
4632
|
+
|
|
4633
|
+
if (/^F\d+$/.test(event.key)) {
|
|
4634
|
+
return;
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4630
4637
|
switch (event.key) {
|
|
4631
4638
|
case 'Tab':
|
|
4632
4639
|
if (event.shiftKey) delta.columns--;
|
|
@@ -4687,7 +4694,10 @@ export class Grid extends GridBase {
|
|
|
4687
4694
|
return;
|
|
4688
4695
|
|
|
4689
4696
|
default:
|
|
4690
|
-
|
|
4697
|
+
|
|
4698
|
+
// FIXME: we're handling F9 (optionally) in the embedded
|
|
4699
|
+
// component. this handler should ignore all function keys.
|
|
4700
|
+
// not sure there's a good global for that, though. regex?
|
|
4691
4701
|
|
|
4692
4702
|
if (!selection.empty) {
|
|
4693
4703
|
if (event.key !== 'Escape') {
|