@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
@@ -19,13 +19,13 @@
19
19
  *
20
20
  */
21
21
 
22
- import type { Cell, ICellAddress, ICellAddress2, UnionValue,
23
- ArrayUnion, IArea, CellDataWithAddress} from 'treb-base-types';
22
+ import type { Cell, ICellAddress, ICellAddress2, UnionValue, EvaluateOptions,
23
+ ArrayUnion, IArea, CellDataWithAddress, CellValue} from 'treb-base-types';
24
24
  import { Localization, Area, ValueType, IsCellAddress} from 'treb-base-types';
25
25
 
26
26
  import type { ExpressionUnit, DependencyList, UnitRange, UnitAddress, UnitIdentifier } from 'treb-parser';
27
27
  import { Parser,
28
- DecimalMarkType, ArgumentSeparatorType } from 'treb-parser';
28
+ DecimalMarkType, ArgumentSeparatorType, QuotedSheetNameRegex } from 'treb-parser';
29
29
 
30
30
  import { Graph } from './dag/graph';
31
31
  import type { SpreadsheetVertex } from './dag/spreadsheet_vertex';
@@ -49,10 +49,12 @@ import { Variance } from './functions/statistics-functions';
49
49
 
50
50
  import * as Primitives from './primitives';
51
51
 
52
- import type { DataModel, Annotation, FunctionDescriptor, Sheet } from 'treb-grid';
53
- import { LeafVertex } from './dag/leaf_vertex';
52
+ import type { DataModel, Annotation, FunctionDescriptor, Sheet, ConditionalFormat } from 'treb-grid';
53
+ import type { LeafVertex } from './dag/graph';
54
54
 
55
55
  import { ArgumentError, ReferenceError, UnknownError, ValueError, ExpressionError, NAError, DivideByZeroError } from './function-error';
56
+ import { StateLeafVertex } from './dag/state_leaf_vertex';
57
+ import { CalculationLeafVertex } from './dag/calculation_leaf_vertex';
56
58
 
57
59
  /**
58
60
  * breaking this out so we can use it for export (TODO)
@@ -120,27 +122,6 @@ const TranslateSubtotalType = (type: string|number): number => {
120
122
 
121
123
  };
122
124
 
123
- /**
124
- * options for the evaluate function
125
- */
126
- export interface EvaluateOptions {
127
-
128
- /**
129
- * argument separator to use when parsing input. set this option to
130
- * use a consistent argument separator independent of current locale.
131
- */
132
- argument_separator?: ','|';';
133
-
134
- /**
135
- * allow R1C1-style references. the Evaluate function cannot use
136
- * relative references (e.g. R[-1]C[0]), so those will always fail.
137
- * however it may be useful to use direct R1C1 references (e.g. R3C4),
138
- * so we optionally support that behind this flag.
139
- */
140
- r1c1?: boolean;
141
-
142
- }
143
-
144
125
  /**
145
126
  * we're providing a runtime option for how to handle complex numbers.
146
127
  * we will need to pass that into the calculator when it's created to
@@ -180,7 +161,11 @@ export class Calculator extends Graph {
180
161
 
181
162
  // FIXME: need a way to share/pass parser flags
182
163
 
183
- public readonly parser: Parser = new Parser();
164
+ // public readonly parser: Parser = new Parser();
165
+ /** localized parser instance. we're sharing. */
166
+ protected get parser(): Parser {
167
+ return this.model.parser;
168
+ }
184
169
 
185
170
  protected readonly library = new FunctionLibrary();
186
171
 
@@ -192,10 +177,14 @@ export class Calculator extends Graph {
192
177
  // protected graph: Graph = new Graph(); // |null = null;
193
178
  // protected status: GraphStatus = GraphStatus.OK;
194
179
 
180
+ /*
195
181
  // FIXME: why is this a separate class? [actually is this a composition issue?]
196
182
  protected expression_calculator = new ExpressionCalculator(
197
183
  this.library,
198
184
  this.parser);
185
+ */
186
+
187
+ protected expression_calculator: ExpressionCalculator;
199
188
 
200
189
  /** the next calculation must do a full rebuild -- set on reset */
201
190
  protected full_rebuild_required = false;
@@ -204,6 +193,9 @@ export class Calculator extends Graph {
204
193
 
205
194
  super();
206
195
 
196
+ this.expression_calculator = new ExpressionCalculator(this.library, this.parser);
197
+
198
+
207
199
  // at the moment options are only used here; in the future
208
200
  // we may need to extend handling.
209
201
 
@@ -1298,6 +1290,7 @@ export class Calculator extends Graph {
1298
1290
  if (this.full_rebuild_required) {
1299
1291
  subset = undefined;
1300
1292
  this.UpdateAnnotations();
1293
+ this.UpdateConditionals();
1301
1294
  // this.UpdateNotifiers();
1302
1295
  this.full_rebuild_required = false; // unset
1303
1296
  }
@@ -1380,67 +1373,11 @@ export class Calculator extends Graph {
1380
1373
  return map;
1381
1374
  }
1382
1375
 
1383
- /** wrapper method ensures it always returns an Area (instance, not interface) */
1384
- public ResolveArea(address: string|ICellAddress|IArea, active_sheet: Sheet): Area {
1385
- const resolved = this.ResolveAddress(address, active_sheet);
1386
- return IsCellAddress(resolved) ? new Area(resolved) : new Area(resolved.start, resolved.end);
1387
- }
1388
-
1389
- /**
1390
- * moved from embedded sheet. also modified to preserve ranges, so it
1391
- * might return a range (area). if you are expecting the old behavior
1392
- * you need to check (perhaps we could have a wrapper, or make it optional?)
1393
- *
1394
- * Q: why does this not go in grid? or model? (...)
1395
- * Q: why are we not preserving absoute/relative? (...)
1396
- *
1397
- */
1398
- public ResolveAddress(address: string|ICellAddress|IArea, active_sheet: Sheet): ICellAddress|IArea {
1399
-
1400
- if (typeof address === 'string') {
1401
- const parse_result = this.parser.Parse(address);
1402
- if (parse_result.expression && parse_result.expression.type === 'address') {
1403
- this.ResolveSheetID(parse_result.expression, undefined, active_sheet);
1404
- return {
1405
- row: parse_result.expression.row,
1406
- column: parse_result.expression.column,
1407
- sheet_id: parse_result.expression.sheet_id,
1408
- };
1409
- }
1410
- else if (parse_result.expression && parse_result.expression.type === 'range') {
1411
- this.ResolveSheetID(parse_result.expression, undefined, active_sheet);
1412
- return {
1413
- start: {
1414
- row: parse_result.expression.start.row,
1415
- column: parse_result.expression.start.column,
1416
- sheet_id: parse_result.expression.start.sheet_id,
1417
- },
1418
- end: {
1419
- row: parse_result.expression.end.row,
1420
- column: parse_result.expression.end.column,
1421
- }
1422
- };
1423
- }
1424
- else if (parse_result.expression && parse_result.expression.type === 'identifier') {
1425
-
1426
- // is named range guaranteed to have a sheet ID? (I think yes...)
1427
-
1428
- const named_range = this.model.named_ranges.Get(parse_result.expression.name);
1429
- if (named_range) {
1430
- return named_range;
1431
- }
1432
- }
1433
-
1434
- return { row: 0, column: 0 }; // default for string types -- broken
1435
-
1436
- }
1437
-
1438
- return address; // already range or address
1439
-
1440
- }
1376
+ public Evaluate(expression: string, active_sheet?: Sheet, options?: EvaluateOptions, raw_result?: false): CellValue|CellValue[][];
1377
+ public Evaluate(expression: string, active_sheet?: Sheet, options?: EvaluateOptions, raw_result?: true): UnionValue;
1441
1378
 
1442
1379
  /** moved from embedded sheet */
1443
- public Evaluate(expression: string, active_sheet?: Sheet, options: EvaluateOptions = {}) {
1380
+ public Evaluate(expression: string, active_sheet?: Sheet, options: EvaluateOptions = {}, raw_result = false) {
1444
1381
 
1445
1382
  const current = this.parser.argument_separator;
1446
1383
  const r1c1_state = this.parser.flags.r1c1;
@@ -1487,13 +1424,16 @@ export class Calculator extends Graph {
1487
1424
  }
1488
1425
  }
1489
1426
 
1490
- this.ResolveSheetID(unit, undefined, active_sheet);
1427
+ this.model.ResolveSheetID(unit, undefined, active_sheet);
1491
1428
  }
1492
1429
  return true;
1493
1430
  });
1494
1431
 
1495
1432
  // console.info({expression: parse_result.expression})
1496
1433
  const result = this.CalculateExpression(parse_result.expression);
1434
+ if (raw_result) {
1435
+ return result;
1436
+ }
1497
1437
 
1498
1438
  if (result.type === ValueType.array) {
1499
1439
  return result.value.map(row => row.map(value => value.value));
@@ -1547,6 +1487,7 @@ export class Calculator extends Graph {
1547
1487
  // add leaf vertices for annotations
1548
1488
 
1549
1489
  this.UpdateAnnotations(); // all
1490
+ this.UpdateConditionals();
1550
1491
 
1551
1492
  // and notifiers
1552
1493
 
@@ -1769,6 +1710,180 @@ export class Calculator extends Graph {
1769
1710
  }
1770
1711
  */
1771
1712
 
1713
+ public Unresolve(ref: IArea|ICellAddress, context: Sheet, qualified = true, named = true) {
1714
+
1715
+ let range = '';
1716
+ const area = IsCellAddress(ref) ? new Area(ref) : new Area(ref.start, ref.end);
1717
+
1718
+ if (named) {
1719
+ const named_range = this.model.named_ranges.MatchSelection(area);
1720
+ if (named_range) {
1721
+ return named_range;
1722
+ }
1723
+ }
1724
+
1725
+ if (area.count > 1) {
1726
+ range = Area.CellAddressToLabel(area.start) + ':' + Area.CellAddressToLabel(area.end);
1727
+ }
1728
+ else {
1729
+ range = Area.CellAddressToLabel(area.start);
1730
+ }
1731
+
1732
+ if (!qualified) {
1733
+ return range;
1734
+ }
1735
+
1736
+ // is there a function to resolve sheet? actually, don't we know that
1737
+ // the active selection must be on the active sheet? (...)
1738
+
1739
+ const sheet_id = area.start.sheet_id || context?.id;
1740
+ const sheet_name = this.ResolveSheetName(sheet_id, true);
1741
+
1742
+ return sheet_name ? sheet_name + '!' + range : range;
1743
+
1744
+ }
1745
+
1746
+ /**
1747
+ *
1748
+ */
1749
+ public ResolveSheetName(id: number, quote = false): string | undefined {
1750
+ const sheet = this.model.sheets.Find(id);
1751
+ if (sheet) {
1752
+ if (quote && QuotedSheetNameRegex.test(sheet.name)) {
1753
+ return `'${sheet.name}'`;
1754
+ }
1755
+ return sheet.name;
1756
+ }
1757
+ return undefined;
1758
+ }
1759
+
1760
+ public RemoveConditional(conditional: ConditionalFormat): void {
1761
+ if (conditional.type === 'expression') {
1762
+ const vertex = conditional.internal?.vertex as LeafVertex;
1763
+ if (vertex) {
1764
+ vertex.Reset();
1765
+ this.RemoveLeafVertex(vertex);
1766
+ }
1767
+ }
1768
+ }
1769
+
1770
+ public UpdateConditionals(list?: ConditionalFormat|ConditionalFormat[], context?: Sheet): void {
1771
+
1772
+ if (!list) {
1773
+ for (const sheet of this.model.sheets.list) {
1774
+ if (sheet.conditional_formats?.length) {
1775
+ this.UpdateConditionals(sheet.conditional_formats, sheet);
1776
+ }
1777
+ }
1778
+ return;
1779
+ }
1780
+
1781
+ if (!context) {
1782
+ throw new Error('invalid call to update conditionals without context');
1783
+ }
1784
+
1785
+ if (list && !Array.isArray(list)) {
1786
+ list = [list];
1787
+ }
1788
+
1789
+ for (const entry of list) {
1790
+
1791
+ let expression = '';
1792
+
1793
+ switch (entry.type) {
1794
+ case 'cell-match':
1795
+ expression = this.Unresolve(entry.area, context, true, false) + ' ' + entry.expression;
1796
+ break;
1797
+
1798
+ case 'expression':
1799
+ expression = entry.expression;
1800
+ break;
1801
+
1802
+ case 'duplicate-values':
1803
+ expression = `UniqueValues(${
1804
+ this.Unresolve(entry.area, context, true, false)
1805
+ })`;
1806
+ if (!entry.unique) {
1807
+ expression = `NOT(${expression})`;
1808
+ }
1809
+ break;
1810
+
1811
+ case 'gradient':
1812
+ expression = `=Gradient(${
1813
+ [
1814
+ this.Unresolve(entry.area, context, true, false),
1815
+ entry.min ?? '',
1816
+ entry.max ?? '',
1817
+
1818
+ ].join(this.parser.argument_separator)
1819
+ })`;
1820
+ break;
1821
+
1822
+ default:
1823
+ continue;
1824
+ }
1825
+
1826
+ if (!expression) {
1827
+ continue; // FIXME: warn?
1828
+ }
1829
+
1830
+ // console.info({type: entry.type, expression});
1831
+
1832
+ if (!entry.internal) {
1833
+ entry.internal = {};
1834
+ }
1835
+ if (!entry.internal.vertex) {
1836
+ entry.internal.vertex = new CalculationLeafVertex();
1837
+
1838
+ let options: EvaluateOptions|undefined;
1839
+ if (entry.type !== 'gradient' && entry.type !== 'duplicate-values') {
1840
+ options = entry.options;
1841
+ }
1842
+
1843
+ // first pass, run the calculation
1844
+ const check = this.Evaluate(expression, context, options, true);
1845
+ entry.internal.vertex.result = check;
1846
+ entry.internal.vertex.updated = true;
1847
+
1848
+ }
1849
+
1850
+ const vertex = entry.internal.vertex as LeafVertex;
1851
+ this.AddLeafVertex(vertex);
1852
+ this.UpdateLeafVertex(vertex, expression, context);
1853
+
1854
+ /*
1855
+ if (entry.type === 'cell-match') {
1856
+ if (!entry.internal) {
1857
+ entry.internal = {};
1858
+ }
1859
+ if (!entry.internal.vertex) {
1860
+ entry.internal.vertex = new CalculationLeafVertex();
1861
+ }
1862
+ const vertex = entry.internal.vertex as LeafVertex;
1863
+ this.AddLeafVertex(vertex);
1864
+ this.UpdateLeafVertex(vertex, entry.expression, context);
1865
+ }
1866
+ else if (entry.type === 'expression') {
1867
+ if (!entry.internal) {
1868
+ entry.internal = {};
1869
+ }
1870
+ if (!entry.internal.vertex) {
1871
+ entry.internal.vertex = new CalculationLeafVertex();
1872
+
1873
+ // set initial state based on current state
1874
+ entry.internal.vertex.result = { type: ValueType.boolean, value: !!entry.applied };
1875
+
1876
+ }
1877
+ const vertex = entry.internal.vertex as LeafVertex;
1878
+ this.AddLeafVertex(vertex);
1879
+ this.UpdateLeafVertex(vertex, entry.expression, context);
1880
+ }
1881
+ */
1882
+
1883
+ }
1884
+
1885
+ }
1886
+
1772
1887
  public RemoveAnnotation(annotation: Annotation): void {
1773
1888
  const vertex = (annotation.temp.vertex as LeafVertex);
1774
1889
  if (!vertex) { return; }
@@ -1803,7 +1918,7 @@ export class Calculator extends Graph {
1803
1918
  for (const entry of list) {
1804
1919
  if (entry.data.formula) {
1805
1920
  if (!entry.temp.vertex) {
1806
- entry.temp.vertex = new LeafVertex();
1921
+ entry.temp.vertex = new StateLeafVertex();
1807
1922
  }
1808
1923
  const vertex = entry.temp.vertex as LeafVertex;
1809
1924
  this.AddLeafVertex(vertex);
@@ -1813,48 +1928,6 @@ export class Calculator extends Graph {
1813
1928
 
1814
1929
  }
1815
1930
 
1816
- /**
1817
- * returns false if the sheet cannot be resolved, which probably
1818
- * means the name changed (that's the case we are working on with
1819
- * this fix).
1820
- */
1821
- public ResolveSheetID(expr: UnitAddress|UnitRange, context?: ICellAddress, active_sheet?: Sheet): boolean {
1822
-
1823
- const target = expr.type === 'address' ? expr : expr.start;
1824
-
1825
- if (target.sheet_id) {
1826
- return true;
1827
- }
1828
-
1829
- if (target.sheet) {
1830
- const sheet = this.model.sheets.Find(target.sheet);
1831
- if (sheet) {
1832
- target.sheet_id = sheet.id;
1833
- return true;
1834
- }
1835
-
1836
- /*
1837
- const lc = target.sheet.toLowerCase();
1838
- for (const sheet of this.model.sheets.list) {
1839
- if (sheet.name.toLowerCase() === lc) {
1840
- target.sheet_id = sheet.id;
1841
- return true;
1842
- }
1843
- }
1844
- */
1845
- }
1846
- else if (context?.sheet_id) {
1847
- target.sheet_id = context.sheet_id;
1848
- return true;
1849
- }
1850
- else if (active_sheet?.id) {
1851
- target.sheet_id = active_sheet.id;
1852
- return true;
1853
- }
1854
-
1855
- return false; // the error
1856
-
1857
- }
1858
1931
 
1859
1932
  // --- protected -------------------------------------------------------------
1860
1933
 
@@ -1866,13 +1939,13 @@ export class Calculator extends Graph {
1866
1939
 
1867
1940
  switch (expr.type) {
1868
1941
  case 'address':
1869
- if (this.ResolveSheetID(expr, context)) {
1942
+ if (this.model.ResolveSheetID(expr, context)) {
1870
1943
  return new Area(expr);
1871
1944
  }
1872
1945
  break;
1873
1946
 
1874
1947
  case 'range':
1875
- if (this.ResolveSheetID(expr, context)) {
1948
+ if (this.model.ResolveSheetID(expr, context)) {
1876
1949
  return new Area(expr.start, expr.end);
1877
1950
  }
1878
1951
  break;
@@ -0,0 +1,97 @@
1
+ /*
2
+ * This file is part of TREB.
3
+ *
4
+ * TREB is free software: you can redistribute it and/or modify it under the
5
+ * terms of the GNU General Public License as published by the Free Software
6
+ * Foundation, either version 3 of the License, or (at your option) any
7
+ * later version.
8
+ *
9
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
10
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
+ * details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License along
15
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ * Copyright 2022-2023 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ import type { GraphCallbacks } from './spreadsheet_vertex_base';
23
+ import { SpreadsheetVertex } from './spreadsheet_vertex';
24
+ import type { Vertex} from './vertex';
25
+ import { Color } from './vertex';
26
+
27
+ /**
28
+ * adding a new leaf vertex type that actually does a calculation;
29
+ * but it has no spreadsheet context (address) and by definition it
30
+ * has no dependendents.
31
+ *
32
+ * this is intended for managing conditional formats, if they have
33
+ * an expression. we only want to calculate these when necessary
34
+ * (i.e. dependencies have updated, or they are volatile).
35
+ *
36
+ */
37
+ export class CalculationLeafVertex extends SpreadsheetVertex {
38
+
39
+ public static type = 'calculation-leaf-vertex';
40
+
41
+ public type = CalculationLeafVertex.type; // for type guard
42
+
43
+ public address = { row: -1, column: -1 }; // fake address
44
+
45
+ /**
46
+ * flag, to reduce unecessary application. work in progress. this
47
+ * indicates that we reached the calculation step. that means either
48
+ * (1) dependencies changed, or (2) we were marked dirty in some global
49
+ * operation, probably a full-recalc.
50
+ *
51
+ * (2) is a waste but we're still going to save some cycles here. if you
52
+ * want you could add a state check like the other leaf vertex.
53
+ */
54
+ public updated = false;
55
+
56
+ /**
57
+ * leaf vertex defaults to black (i.e. tested) because leaf nodes cannot have
58
+ * outbound edges. it is still possible to change this, because it's a property
59
+ * and we can't override the set accessor, but making it an accessor in the
60
+ * superclass just for this purpose is not worthwhile since regular vertices
61
+ * should vastly outnumber leaves.
62
+ */
63
+ public color = Color.black;
64
+
65
+ /** overrides calculate function */
66
+ public Calculate(graph: GraphCallbacks): void {
67
+
68
+ // if we are not dirty, nothing to do
69
+ if (!this.dirty) return;
70
+
71
+ // check deps
72
+ for (const edge of this.edges_in) {
73
+ if ((edge as SpreadsheetVertex).dirty) {
74
+ return;
75
+ }
76
+ }
77
+
78
+ // ...
79
+
80
+ const result = graph.CalculationCallback.call(graph, this);
81
+
82
+ this.result = result.value;
83
+ this.dirty = false;
84
+
85
+ // set flag
86
+
87
+ this.updated = true;
88
+
89
+ // we are not allowed to have edges out, so nothing to do
90
+
91
+ }
92
+
93
+ public AddDependent(edge: Vertex): void {
94
+ throw(new Error('leaf vertex cannot have dependents'));
95
+ }
96
+
97
+ }
@@ -24,10 +24,13 @@ import { Color } from './vertex';
24
24
  import { SpreadsheetVertex } from './spreadsheet_vertex';
25
25
  import { ArrayVertex } from './array-vertex';
26
26
  import type { SpreadsheetVertexBase, CalculationResult, GraphCallbacks } from './spreadsheet_vertex_base';
27
- import type { LeafVertex } from './leaf_vertex';
27
+ import type { StateLeafVertex } from './state_leaf_vertex';
28
28
  import type { ICellAddress, ICellAddress2, IArea, UnionValue } from 'treb-base-types';
29
29
  import { Area } from 'treb-base-types';
30
30
  import type { DataModel } from 'treb-grid';
31
+ import { CalculationLeafVertex } from './calculation_leaf_vertex';
32
+
33
+ export type LeafVertex = StateLeafVertex|CalculationLeafVertex;
31
34
 
32
35
  // FIXME: this is a bad habit if you're testing on falsy for OK.
33
36
 
@@ -61,7 +64,8 @@ export abstract class Graph implements GraphCallbacks {
61
64
  public loop_hint?: string;
62
65
 
63
66
  // special
64
- public leaf_vertices: LeafVertex[] = [];
67
+ // public leaf_vertices: LeafVertex[] = [];
68
+ public leaf_vertices: Set<LeafVertex> = new Set();
65
69
 
66
70
  /** lock down access */
67
71
  private dirty_list: SpreadsheetVertexBase[] = [];
@@ -102,7 +106,7 @@ export abstract class Graph implements GraphCallbacks {
102
106
  this.dirty_list = [];
103
107
  this.volatile_list = [];
104
108
  this.vertices = [[]];
105
- this.leaf_vertices = [];
109
+ this.leaf_vertices.clear();
106
110
  // this.cells_map = {};
107
111
 
108
112
  /** array vertex maintains its own list */
@@ -812,28 +816,12 @@ export abstract class Graph implements GraphCallbacks {
812
816
  * managing and maintaining these vertices: we only need references.
813
817
  */
814
818
  public AddLeafVertex(vertex: LeafVertex): void {
815
-
816
- // ... don't add more than once. this is expensive but
817
- // the list should (generally speaking) be short, so not
818
- // a serious problem atm
819
-
820
- /*
821
- if (this.leaf_vertices.some((test) => test === vertex)) {
822
- return;
823
- }
824
- */
825
- for (const test of this.leaf_vertices) {
826
- if (test === vertex) {
827
- return;
828
- }
829
- }
830
-
831
- this.leaf_vertices.push(vertex);
819
+ this.leaf_vertices.add(vertex);
832
820
  }
833
821
 
834
- /** removes vertex, by match */
822
+ /** removes vertex */
835
823
  public RemoveLeafVertex(vertex: LeafVertex): void {
836
- this.leaf_vertices = this.leaf_vertices.filter((test) => test !== vertex);
824
+ this.leaf_vertices.delete(vertex);
837
825
  }
838
826
 
839
827
  /**
@@ -40,12 +40,12 @@ import { Color } from './vertex';
40
40
  * have both leaf- and spreadsheet-vertex extend that.
41
41
  *
42
42
  */
43
- export class LeafVertex extends SpreadsheetVertex {
43
+ export class StateLeafVertex extends SpreadsheetVertex {
44
44
 
45
- public static type = 'leaf-vertex';
45
+ public static type = 'state-leaf-vertex';
46
46
 
47
47
  public state_id = 0;
48
- public type = LeafVertex.type; // for type guard
48
+ public type = StateLeafVertex.type; // for type guard
49
49
 
50
50
  /**
51
51
  * leaf vertex defaults to black (i.e. tested) because leaf nodes cannot have
@@ -112,10 +112,17 @@ export interface CompositeFunctionDescriptor {
112
112
  fn: (...args: any[]) => UnionValue; // UnionOrArray; // |UnitAddress|UnitRange;
113
113
 
114
114
  /**
115
- * for the future. some functions should not be available in
116
- * spreadsheet cells (charts, basically)
115
+ * limited visibility
116
+ *
117
+ * internal functions do not show up in the spreadsheet. we have an
118
+ * annotation value which should be usef in the future but it's not
119
+ * implemented yet.
120
+ *
121
+ * should we allow these functions to be used, and just not tooltip them;
122
+ * or block them entirely? for now we'll do the former as it's helpful to
123
+ * defug.
117
124
  */
118
- visibility?: string;
125
+ visibility?: 'internal'|'annotation';
119
126
 
120
127
  /**
121
128
  * for the future
@@ -821,7 +821,7 @@ export class ExpressionCalculator {
821
821
  const value: UnionValue[][] = [];
822
822
 
823
823
  for (let c = 0; c < columns; c++) {
824
- const col = [];
824
+ const col: UnionValue[] = [];
825
825
  for (let r = 0; r < rows; r++ ) {
826
826
  col[r] = fn(left_values[c][r], right_values[c][r]);
827
827
  }