@trebco/treb 30.15.0 → 31.0.2

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 (52) hide show
  1. package/api-generator/api-generator.ts +3 -1
  2. package/api-generator/package.json +2 -1
  3. package/dist/treb-export-worker.mjs +2 -2
  4. package/dist/treb-spreadsheet.mjs +13 -13
  5. package/dist/treb.d.ts +19 -2
  6. package/package.json +8 -7
  7. package/treb-base-types/src/font-stack.ts +144 -0
  8. package/treb-base-types/src/style.ts +121 -11
  9. package/treb-base-types/src/theme.ts +53 -8
  10. package/treb-calculator/src/calculator.ts +13 -13
  11. package/treb-calculator/src/descriptors.ts +12 -4
  12. package/treb-calculator/src/expression-calculator.ts +17 -4
  13. package/treb-calculator/src/functions/base-functions.ts +57 -4
  14. package/treb-calculator/src/functions/statistics-functions.ts +9 -6
  15. package/treb-calculator/tsconfig.json +11 -0
  16. package/treb-charts/src/chart-functions.ts +41 -4
  17. package/treb-charts/src/chart-types.ts +20 -1
  18. package/treb-charts/src/chart-utils.ts +86 -9
  19. package/treb-charts/src/default-chart-renderer.ts +40 -1
  20. package/treb-charts/src/renderer.ts +3 -3
  21. package/treb-charts/style/charts.scss +7 -1
  22. package/treb-data-model/src/annotation.ts +6 -0
  23. package/treb-data-model/src/data_model.ts +14 -3
  24. package/treb-data-model/src/sheet.ts +57 -56
  25. package/treb-embed/markup/toolbar.html +15 -1
  26. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +38 -0
  27. package/treb-embed/src/embedded-spreadsheet.ts +119 -29
  28. package/treb-embed/src/options.ts +3 -0
  29. package/treb-embed/src/selection-state.ts +1 -0
  30. package/treb-embed/src/toolbar-message.ts +6 -0
  31. package/treb-embed/src/types.ts +9 -0
  32. package/treb-embed/style/defaults.scss +12 -1
  33. package/treb-embed/style/font-stacks.scss +105 -0
  34. package/treb-embed/style/layout.scss +1 -0
  35. package/treb-embed/style/theme-defaults.scss +12 -2
  36. package/treb-embed/style/toolbar.scss +16 -0
  37. package/treb-grid/src/editors/overlay_editor.ts +36 -3
  38. package/treb-grid/src/layout/base_layout.ts +52 -37
  39. package/treb-grid/src/layout/grid_layout.ts +7 -0
  40. package/treb-grid/src/render/tile_renderer.ts +154 -148
  41. package/treb-grid/src/types/grid.ts +188 -54
  42. package/treb-grid/src/types/grid_events.ts +1 -1
  43. package/treb-grid/src/types/grid_options.ts +3 -0
  44. package/treb-grid/src/util/fontmetrics.ts +134 -0
  45. package/treb-parser/src/parser.ts +12 -3
  46. package/treb-utils/src/measurement.ts +2 -3
  47. package/tsproject.json +1 -1
  48. package/treb-calculator/modern.tsconfig.json +0 -11
  49. package/treb-grid/src/util/fontmetrics2.ts +0 -182
  50. package/treb-parser/src/parser.test.ts +0 -298
  51. /package/treb-embed/{modern.tsconfig.json → tsconfig.json} +0 -0
  52. /package/treb-export/{modern.tsconfig.json → tsconfig.json} +0 -0
package/dist/treb.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v30.15. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v31.0. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
2
2
 
3
3
  /**
4
4
  * add our tag to the map
@@ -170,6 +170,9 @@ export interface EmbeddedSpreadsheetOptions {
170
170
  /** include the font scale control in the toolbar */
171
171
  font_scale?: boolean;
172
172
 
173
+ /** include the font stack control in the toolbar */
174
+ font_stack?: boolean;
175
+
173
176
  /** include the insert/remove table button in the toolbar */
174
177
  table_button?: boolean;
175
178
 
@@ -1601,7 +1604,7 @@ export declare type LoadSource = "drag-and-drop" | "local-file" | "network-file"
1601
1604
  * EmbeddedSheetEvent is a discriminated union. Switch on the `type` field
1602
1605
  * of the event.
1603
1606
  */
1604
- export type EmbeddedSheetEvent = DocumentChangeEvent | DocumentResetEvent | DocumentLoadEvent | ThemeChangeEvent | ViewChangeEvent | DataChangeEvent | FocusViewEvent | SelectionEvent | ResizeEvent;
1607
+ export type EmbeddedSheetEvent = DocumentChangeEvent | DocumentResetEvent | DocumentLoadEvent | ThemeChangeEvent | ViewChangeEvent | DataChangeEvent | FocusViewEvent | SelectionEvent | ResizeEvent | AnnotationSelectionEvent;
1605
1608
 
1606
1609
  /**
1607
1610
  * options when inserting a table into a sheet
@@ -1691,6 +1694,14 @@ export interface SelectionEvent {
1691
1694
  type: 'selection';
1692
1695
  }
1693
1696
 
1697
+ /**
1698
+ * this event is used when an annotation is selected. we're not changing
1699
+ * the original selection event, because I don't want to break anything.
1700
+ */
1701
+ export interface AnnotationSelectionEvent {
1702
+ type: 'annotation-selection';
1703
+ }
1704
+
1694
1705
  /**
1695
1706
  * This event is sent when the focused view changes, if you have more
1696
1707
  * than one view.
@@ -1973,6 +1984,12 @@ export interface AnnotationDataBase {
1973
1984
  /** the new layout, persisted and takes preference over the old one */
1974
1985
  layout?: AnnotationLayout;
1975
1986
 
1987
+ /**
1988
+ * adding cell style as a convenient store for font stack; atm we are
1989
+ * ignoring everything but the font_face attribute
1990
+ */
1991
+ style?: CellStyle;
1992
+
1976
1993
  /**
1977
1994
  * the old layout used rectangles, and we need to keep support for
1978
1995
  * that. this is not the layout rectangle. this rectangle is just
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "30.15.0",
3
+ "version": "31.0.2",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -30,8 +30,9 @@
30
30
  "treb-grid": "file:treb-grid",
31
31
  "treb-parser": "file:treb-parser",
32
32
  "treb-utils": "file:treb-utils",
33
- "ts-node-dev": "^2.0.0",
33
+ "ts-node": "^10.9.2",
34
34
  "tslib": "^2.7.0",
35
+ "tsx": "^4.19.1",
35
36
  "typescript": "^5.3.3",
36
37
  "typescript-eslint": "^8.3.0",
37
38
  "uzip": "^0.20201231.0"
@@ -42,11 +43,11 @@
42
43
  "clean": "rm -fr build dist declaration",
43
44
  "watch": "node --watch esbuild-composite.mjs --watch --dev",
44
45
  "watch-production": "node --watch esbuild-composite.mjs --watch",
45
- "tsc": "tsc -b treb-embed/modern.tsconfig.json",
46
- "rebuild-tsc": "tsc -b --force treb-embed/modern.tsconfig.json",
47
- "watch-tsc": "tsc -b treb-embed/modern.tsconfig.json -w",
48
- "watch-api": "ts-node-dev --respawn api-generator/api-generator.ts --config api-config.json",
49
- "generate-api": "ts-node-dev api-generator/api-generator.ts --config api-config.json",
46
+ "tsc": "tsc -b treb-embed/tsconfig.json",
47
+ "rebuild-tsc": "tsc -b --force treb-embed/tsconfig.json",
48
+ "watch-tsc": "tsc -b treb-embed/tsconfig.json -w",
49
+ "watch-api": "tsx --watch api-generator/api-generator.ts --config api-config.json",
50
+ "generate-api": "tsx api-generator/api-generator.ts --config api-config.json",
50
51
  "release": "npm run rebuild-tsc && npm run build && npm run generate-api"
51
52
  }
52
53
  }
@@ -0,0 +1,144 @@
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-2024 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ import { UA } from 'treb-grid';
23
+ import { Measurement } from 'treb-utils';
24
+ import { type FontSize, Style } from './style';
25
+
26
+ /**
27
+ * these are the stacks we're currently supporting.
28
+ */
29
+ export const font_stack_names = [
30
+ 'default',
31
+ 'old-style',
32
+ 'transitional',
33
+ 'handwritten',
34
+ 'monospace',
35
+ 'industrial',
36
+ 'ui',
37
+ ] as const;
38
+
39
+ export type FontStackType = typeof font_stack_names[number];
40
+
41
+ export const font_stack_labels: Record<FontStackType, string> = {
42
+ 'default': 'Sans-serif',
43
+ 'old-style': 'Old style',
44
+ 'transitional': 'Serif',
45
+ 'handwritten': 'Handwritten',
46
+ 'monospace': 'Monospace',
47
+ 'industrial': 'Industrial sans',
48
+ 'ui': 'System UI',
49
+ };
50
+
51
+ /**
52
+ * representation of a font stack as an object we can manage in themes.
53
+ * the impetus for this is having some font "sets" we can use in a sheet
54
+ * that we know will render in a reasonable cross-platform way.
55
+ *
56
+ * we're also taking advantage of chrome supporting font features in
57
+ * canvas. we have to do a little bit of carve-out for ffx browsers on
58
+ * windows but that should be it, and the fallback is OK.
59
+ */
60
+
61
+ export interface FontStack {
62
+
63
+ /** the font family from css. this will usually be a list. */
64
+ family: string;
65
+
66
+ /** the actual font used */
67
+ font?: string;
68
+
69
+ /** default size for grid cells. may be different for different fonts. */
70
+ size: FontSize;
71
+
72
+ /**
73
+ * font variants. we used this for sitka text to apply lining-nums and
74
+ * tabular-nums, although we're not using sitka anymore. atm only chrome
75
+ * supports font variants in canvas (boo).
76
+ */
77
+ variants?: string;
78
+
79
+ }
80
+
81
+ const font_cache: Map<string, boolean> = new Map();
82
+
83
+ /**
84
+ *
85
+ * @param name - for reporting purposes only
86
+ * @param computed - computed css to resolve variables
87
+ * @returns
88
+ */
89
+ export const GenerateFontStack = (name: string, computed: CSSStyleDeclaration): FontStack => {
90
+
91
+ const family = computed.fontFamily;
92
+ let font = '';
93
+
94
+ const elements = family.split(/,/);
95
+
96
+ for (let element of elements) {
97
+ element = element.replace(/'"/g, '').trim();
98
+ const lc = element.toLowerCase();
99
+
100
+ //
101
+ // platform-specific hacks. this is kind of unfortunate.
102
+ //
103
+
104
+ if (UA.is_firefox && /sitka text/i.test(lc)) {
105
+ continue;
106
+ }
107
+
108
+ let check = font_cache.get(lc);
109
+ if (typeof check === 'undefined') {
110
+ check = Measurement.FontLoaded(element);
111
+ font_cache.set(lc, check);
112
+ }
113
+
114
+ if (check) {
115
+ font = element;
116
+ break;
117
+ }
118
+ }
119
+
120
+ if (!font) {
121
+ console.warn('no font found for font stack', name);
122
+ }
123
+
124
+ // check the base size, and any adjustments for this font
125
+
126
+ const variable_name = font.toLowerCase().replace(/['"]/g, '').replace(/\W+/g, '-');
127
+
128
+ const base = computed.getPropertyValue('--treb-font-stack-default-size');
129
+ const adjusted = computed.getPropertyValue(`--treb-font-stack-${variable_name}-size`);
130
+ const variants = computed.getPropertyValue(`--treb-font-stack-${variable_name}-variant`);
131
+ const size = Style.ParseFontSize(adjusted || base || '10pt').font_size || { unit: 'pt', value: 10 };
132
+
133
+ // console.info({stack: name, family, font, base, adjusted, size, variants});
134
+
135
+ const stack = {
136
+ family,
137
+ font,
138
+ variants,
139
+ size,
140
+ };
141
+
142
+ return stack;
143
+
144
+ };
@@ -21,6 +21,8 @@
21
21
 
22
22
  const empty_json = JSON.stringify({}); // we could probably hard-code this
23
23
 
24
+ import type { Theme } from './theme';
25
+
24
26
  /** horizontal align constants for cell style */
25
27
  export type HorizontalAlign = '' | 'left' | 'center' | 'right';
26
28
 
@@ -271,8 +273,9 @@ export const Style = {
271
273
  number_format: 'General', // '0.######', // use symbolic, e.g. "general"
272
274
  nan: 'NaN',
273
275
 
274
- font_size: { unit: 'pt', value: 10.5 },
275
- font_face: 'sans-serif',
276
+ font_size: { unit: 'em', value: 1 },
277
+ // font_size: { unit: 'pt', value: 10.5 },
278
+ // font_face: 'sans-serif',
276
279
 
277
280
  bold: false, // drop "font_"
278
281
  italic: false, // ...
@@ -439,7 +442,9 @@ export const Style = {
439
442
 
440
443
  },
441
444
 
442
- /** @internal */
445
+ /**
446
+ * @internal
447
+ */
443
448
  FontSize: (properties: CellStyle, prefer_points = true): string => {
444
449
 
445
450
  const value = properties.font_size?.value;
@@ -466,10 +471,49 @@ export const Style = {
466
471
  return '';
467
472
  },
468
473
 
469
- /**
470
- * returns a string representation suitable for canvas (or style)
474
+ /**
475
+ * @internal
476
+ *
477
+ * generate a font size based on a base size (hopefully in actual units)
478
+ * and a relative size (em, %, or possibly a static unit). also optionally
479
+ * apply a scale.
480
+ *
471
481
  */
472
- Font: (properties: CellStyle, scale = 1) => {
482
+ CompositeFontSize: (base: FontSize, relative: FontSize, scale = 1, prefer_points = false) => {
483
+
484
+ let composite: FontSize = { ...base };
485
+
486
+ // maybe it's actually not relative
487
+
488
+ if (relative.unit === 'pt' || relative.unit === 'px') {
489
+ composite = { ...relative };
490
+ }
491
+ else {
492
+ composite.value = relative.value * base.value;
493
+ if (relative.unit === '%') {
494
+ composite.value /= 100;
495
+ }
496
+ }
497
+
498
+ if (composite.unit === 'px' && prefer_points) {
499
+ composite.value = Math.round((composite.value||16) * 300 / 4) / 100;
500
+ }
501
+
502
+ composite.value *= scale;
503
+
504
+ return composite;
505
+
506
+ },
507
+
508
+ /**
509
+ * return a font string suitable for canvas. because our font sizes are
510
+ * (probably) in ems, we need a base size to bounce off.
511
+ */
512
+ CompositeFont: (base: FontSize, properties: CellStyle, scale: number, theme: Theme) => {
513
+
514
+ let variants: string|undefined;
515
+ let stack_size: FontSize|undefined; // for reporting only
516
+ let font_size: FontSize|undefined;
473
517
 
474
518
  const parts: string[] = [];
475
519
 
@@ -481,13 +525,79 @@ export const Style = {
481
525
  parts.push('italic');
482
526
  }
483
527
 
484
- parts.push(((properties.font_size?.value || 0) * scale).toFixed(2) +
485
- (properties.font_size?.unit || 'pt'));
486
528
 
487
- parts.push(properties.font_face || '');
529
+ const font_face = properties.font_face || 'stack:default';
530
+ // let stack_scale = 1;
531
+
532
+ // check if this is a stack
533
+ if (font_face.startsWith('stack:')) {
534
+ let stack = theme.font_stacks[font_face.substring(6) || 'default'];
535
+
536
+ // default to default (not just a clever name). the rationale is we
537
+ // want to support environments that don't have fonts turned on. in
538
+ // that case, we just don't create the mappings, so everything shows
539
+ // as the default font.
540
+
541
+ if (!stack) {
542
+ stack = theme.font_stacks.default;
543
+ }
544
+
545
+ if (stack) {
546
+ stack_size = properties.font_size;
547
+ font_size = Style.CompositeFontSize(stack.size, properties.font_size || { unit: 'pt', value: 10 }, scale);
548
+ parts.push(font_size.value.toFixed(2) + font_size.unit);
549
+ parts.push(stack.font || '');
550
+ variants = stack.variants;
551
+ }
552
+ }
553
+ else {
554
+ font_size = Style.CompositeFontSize(base, properties.font_size || { unit: 'pt', value: 10 }, scale);
555
+ parts.push(font_size.value.toFixed(2) + font_size.unit);
556
+ parts.push(font_face || '');
557
+ }
558
+
559
+ return { font: parts.join(' '), variants, base, size: properties.font_size, scale, stack_size, font_size };
560
+
561
+ },
562
+
563
+ /*
564
+ Font2: (properties: CellStyle, scale: number, theme: Theme) => {
565
+
566
+ let features = false;
488
567
 
489
- return parts.join(' ');
490
-
568
+ const parts: string[] = [];
569
+
570
+ if (properties.bold) {
571
+ parts.push('bold');
572
+ }
573
+
574
+ if (properties.italic) {
575
+ parts.push('italic');
576
+ }
577
+
578
+ const font_face = properties.font_face || 'stack:default';
579
+ let stack_scale = 1;
580
+
581
+ // check if this is a stack
582
+ if (font_face.startsWith('stack:')) {
583
+ const stack = theme.font_stacks[font_face.substring(6) || 'default'];
584
+ if (stack) {
585
+ stack_scale = stack.scale;
586
+ parts.push(((properties.font_size?.value || 0) * (scale || 1) * (stack.scale || 1)).toFixed(2) +
587
+ (properties.font_size?.unit || 'pt'));
588
+ parts.push(stack.font || '');
589
+ features = !!stack.apply_num_features;
590
+ }
591
+ }
592
+ else {
593
+ parts.push(((properties.font_size?.value || 0) * (scale || 1)).toFixed(2) +
594
+ (properties.font_size?.unit || 'pt'));
595
+ parts.push(font_face || '');
596
+ }
597
+
598
+ return { font: parts.join(' '), features, base_size: properties.font_size, scale, stack_scale };
599
+
491
600
  },
601
+ */
492
602
 
493
603
  };
@@ -19,12 +19,13 @@
19
19
  *
20
20
  */
21
21
 
22
- import { type Color, type CellStyle, IsHTMLColor, IsThemeColor, ThemeColorIndex, type ThemeColor } from './style';
22
+ import { type Color, type CellStyle, IsHTMLColor, IsThemeColor, ThemeColorIndex, type ThemeColor, type FontSize } from './style';
23
23
  import { ColorFunctions } from './color';
24
24
  // import * as LCHColorFunctions from './color2';
25
25
 
26
26
  import { DOMContext } from './dom-utilities';
27
27
  import { Measurement } from 'treb-utils';
28
+ import { font_stack_names, type FontStack, GenerateFontStack } from './font-stack';
28
29
 
29
30
  /*
30
31
  * so this is a little strange. we use CSS to populate a theme object,
@@ -90,6 +91,12 @@ export interface Theme {
90
91
  /** grid cell defaults (composite: size, font face, color, background) */
91
92
  grid_cell?: CellStyle;
92
93
 
94
+ /**
95
+ * base font size for grid cell. we try to specify things in ems, but
96
+ * we do need to know this in order to measure
97
+ */
98
+ grid_cell_font_size: FontSize;
99
+
93
100
  /** gridlines color */
94
101
  grid_color: string;
95
102
 
@@ -150,6 +157,9 @@ export interface Theme {
150
157
  /** dark color for offset (against light background) */
151
158
  offset_dark: string;
152
159
 
160
+ /** precalculated font stacks */
161
+ font_stacks: Record<string, FontStack>;
162
+
153
163
  }
154
164
 
155
165
  /**
@@ -162,6 +172,8 @@ export const DefaultTheme: Theme = {
162
172
  offset_cache: {},
163
173
  offset_light: '#fff',
164
174
  offset_dark: '#000',
175
+ font_stacks: {},
176
+ grid_cell_font_size: { value: 10, unit: 'pt' },
165
177
  };
166
178
 
167
179
  /* *
@@ -428,19 +440,32 @@ const TableStyleFromCSS = (base: CSSStyleDeclaration, style: CSSStyleDeclaration
428
440
  */
429
441
 
430
442
  // testing
431
- const StyleFromCSS = (css: CSSStyleDeclaration): CellStyle => {
443
+ const StyleFromCSS = (css: CSSStyleDeclaration, include_font_face = false): CellStyle => {
432
444
 
433
- const { value, unit } = ParseFontSize(css.fontSize||'');
445
+ // const { value, unit } = ParseFontSize(css.fontSize||'');
434
446
 
435
447
  const style: CellStyle = {
436
448
  fill: { text: css.backgroundColor }, // || 'none',
437
449
  text: { text: css.color },
450
+
451
+ /*
438
452
  font_size: {
439
453
  unit, value,
440
454
  },
441
- font_face: css.fontFamily,
455
+ */
456
+
457
+ // use container size unless we scale. the reason we do this is
458
+ // because if we set scale, we always wind up with em units.
459
+
460
+ font_size: { unit: 'em', value: 1 },
461
+
462
+ // font_face: css.fontFamily,
442
463
  };
443
464
 
465
+ if (include_font_face) {
466
+ style.font_face = css.fontFamily;
467
+ }
468
+
444
469
  // the default border comes from the "theme colors", not from
445
470
  // the CSS property (it used to come from the CSS property, which
446
471
  // is why we have the CSS property set).
@@ -544,7 +569,7 @@ export const ThemeColorTable = (theme_color: number, tint = .7): TableTheme => {
544
569
  *
545
570
  * @internal
546
571
  */
547
- export const LoadThemeProperties = (container: HTMLElement): Theme => {
572
+ export const LoadThemeProperties = (container: HTMLElement, use_font_stacks = false): Theme => {
548
573
 
549
574
  const theme: Theme = JSON.parse(JSON.stringify(DefaultTheme));
550
575
  const DOM = DOMContext.GetInstance(container.ownerDocument);
@@ -558,14 +583,34 @@ export const LoadThemeProperties = (container: HTMLElement): Theme => {
558
583
  }
559
584
 
560
585
  const node = Append(container, '');
561
- const CSS = ElementCSS.bind(0, node);
586
+ const CSS: (classes: string) => CSSStyleDeclaration = ElementCSS.bind(0, node);
562
587
 
563
588
  let css = CSS('grid-cells');
564
- theme.grid_cell = StyleFromCSS(css);
589
+ theme.grid_cell = StyleFromCSS(css, false);
565
590
  theme.grid_color = css.stroke || '';
591
+ theme.grid_cell_font_size = ParseFontSize(css.fontSize||'');
592
+
593
+ // console.info({theme});
594
+
595
+ if (use_font_stacks) {
596
+ for (const key of font_stack_names) {
597
+ css = CSS(`treb-font-stack-${key}`);
598
+ theme.font_stacks[key] = GenerateFontStack(key, css);
599
+ }
600
+ }
601
+ else {
566
602
 
603
+ // default only
604
+
605
+ css = CSS(`treb-font-stack-default`);
606
+ theme.font_stacks.default = GenerateFontStack('default', css);
607
+
608
+ }
609
+ // console.info(theme.font_stacks);
610
+
611
+
567
612
  css = CSS('grid-headers');
568
- theme.headers = StyleFromCSS(css);
613
+ theme.headers = StyleFromCSS(css, true);
569
614
  theme.headers_grid_color = css.stroke;
570
615
  if (!theme.headers_grid_color || theme.headers_grid_color === 'none') {
571
616
  theme.headers_grid_color = theme.grid_color;
@@ -1569,7 +1569,7 @@ export class Calculator extends Graph {
1569
1569
  };
1570
1570
  }
1571
1571
 
1572
- return this.expression_calculator.Calculate(vertex.expression, vertex.address); // <- this one
1572
+ return this.expression_calculator.Calculate(vertex.expression, vertex.address, vertex.reference?.area); // <- this one
1573
1573
  }
1574
1574
 
1575
1575
 
@@ -1837,6 +1837,11 @@ export class Calculator extends Graph {
1837
1837
  for (const name of Object.keys(map)) {
1838
1838
 
1839
1839
  const descriptor = map[name];
1840
+
1841
+ // the way we call this now, this is unecessary
1842
+ // @see `CallExpression` in `expression-calculator.ts`.
1843
+
1844
+ /*
1840
1845
  const original_function = descriptor.fn;
1841
1846
 
1842
1847
  // we don't bind to the actual context because that would allow
@@ -1844,28 +1849,23 @@ export class Calculator extends Graph {
1844
1849
  // that rely on it. which is a pretty far-fetched scenario, but we might
1845
1850
  // as well protect against it.
1846
1851
 
1852
+ console.info('wrapping...');
1853
+
1847
1854
  descriptor.fn = (...args: unknown[]) => {
1855
+
1856
+ console.info("wrapped?");
1857
+
1848
1858
  return original_function.apply({
1849
1859
  address: { ...this.expression_calculator.context.address},
1850
1860
  }, args);
1851
1861
  };
1862
+ */
1852
1863
 
1853
1864
  this.library.Register({[name]: descriptor});
1854
1865
  }
1855
1866
 
1856
1867
  }
1857
1868
 
1858
- /* *
1859
- * wrap the attachdata function so we can update the expression calculator
1860
- * at the same time (we should unwind this a little bit, it's an artifact
1861
- * of graph being a separate class)
1862
- * /
1863
- public AttachModel(): void {
1864
- // this.RebuildMap();
1865
- // this.expression_calculator.SetModel(this.model);
1866
- }
1867
- */
1868
-
1869
1869
  /**
1870
1870
  * wrapper method for calculation
1871
1871
  */
@@ -2076,7 +2076,7 @@ export class Calculator extends Graph {
2076
2076
  address: ICellAddress = {row: -1, column: -1},
2077
2077
  preserve_flags = false): UnionValue {
2078
2078
 
2079
- return this.expression_calculator.Calculate(expression, address, preserve_flags).value; // dropping volatile flag
2079
+ return this.expression_calculator.Calculate(expression, address, undefined, preserve_flags).value; // dropping volatile flag
2080
2080
  }
2081
2081
 
2082
2082
  /**
@@ -19,7 +19,15 @@
19
19
  *
20
20
  */
21
21
 
22
- import type { RenderFunction, ClickFunction, UnionValue } from 'treb-base-types';
22
+ import type { RenderFunction, ClickFunction, UnionValue, ICellAddress, IArea } from 'treb-base-types';
23
+
24
+ /**
25
+ * FIXME: possible to add stuff in here if we need it
26
+ */
27
+ export interface FunctionContext {
28
+ address: ICellAddress;
29
+ area?: IArea;
30
+ }
23
31
 
24
32
  // FIXME: at least some of this could move to base types
25
33
 
@@ -122,12 +130,12 @@ export interface CompositeFunctionDescriptor {
122
130
  * the actual function. if this is an object member and needs access
123
131
  * to the containing instance, make sure to bind it to that instance.
124
132
  *
125
- * FIXME: this should change to unknown, but that's going to cause
126
- * a lot of issues
133
+ * otherwise, `this` will be bound to a `CalculationContext` object
134
+ * if your function is defined as a function (i.e. not an arrow function).
127
135
  *
128
136
  */
129
137
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
130
- fn: (...args: any[]) => UnionValue;
138
+ fn: (this: FunctionContext|undefined, ...args: any[]) => UnionValue;
131
139
 
132
140
  /**
133
141
  * limited visibility
@@ -26,7 +26,8 @@ import type { Cell, ICellAddress,
26
26
  NumberUnion,
27
27
  UndefinedUnion,
28
28
  ComplexUnion,
29
- DimensionedQuantityUnion} from 'treb-base-types';
29
+ DimensionedQuantityUnion,
30
+ IArea} from 'treb-base-types';
30
31
  import { ValueType, GetValueType, Area } from 'treb-base-types';
31
32
  import type { Parser, ExpressionUnit, UnitBinary, UnitIdentifier,
32
33
  UnitGroup, UnitUnary, UnitAddress, UnitRange, UnitCall, UnitDimensionedQuantity, UnitStructuredReference } from 'treb-parser';
@@ -83,6 +84,7 @@ export interface ReferenceMetadata {
83
84
 
84
85
  export interface CalculationContext {
85
86
  address: ICellAddress;
87
+ area?: IArea;
86
88
  volatile: boolean;
87
89
  }
88
90
 
@@ -104,12 +106,13 @@ export class ExpressionCalculator {
104
106
  * there's a case where we are calling this from within a function
105
107
  * (which is weird, but hey) and to do that we need to preserve flags.
106
108
  */
107
- public Calculate(expr: ExpressionUnit, addr: ICellAddress, preserve_flags = false): {
109
+ public Calculate(expr: ExpressionUnit, addr: ICellAddress, area?: IArea, preserve_flags = false): {
108
110
  value: UnionValue /*UnionOrArray*/, volatile: boolean }{
109
111
 
110
112
  if (!preserve_flags) {
111
113
  this.context.address = addr;
112
114
  this.context.volatile = false;
115
+ this.context.area = area;
113
116
  }
114
117
 
115
118
  return {
@@ -560,9 +563,19 @@ export class ExpressionCalculator {
560
563
  return argument_error;
561
564
  }
562
565
 
566
+ // cloning, out of an abundance of caution
567
+ // const ctx = JSON.parse(JSON.stringify({ address: this.context.address, area: this.context.area }));
568
+ const ctx = {
569
+ address: { ...this.context.address },
570
+ area: this.context.area ? {
571
+ start: { ...this.context.area.start, },
572
+ end: { ...this.context.area.end, },
573
+ } : undefined,
574
+ };
575
+
563
576
  if (func.return_type === 'reference') {
564
577
 
565
- const result = func.fn.apply(null, mapped_args);
578
+ const result = func.fn.apply(ctx, mapped_args);
566
579
 
567
580
  if (return_reference) {
568
581
  return result;
@@ -581,7 +594,7 @@ export class ExpressionCalculator {
581
594
 
582
595
  }
583
596
 
584
- return func.fn.apply(null, mapped_args);
597
+ return func.fn.apply(ctx, mapped_args);
585
598
 
586
599
  };
587
600