@trebco/treb 31.4.0 → 31.7.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.
package/dist/treb.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v31.4. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v31.7. 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
@@ -498,6 +498,7 @@ export declare class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
498
498
  InsertTable(range?: RangeReference, options?: InsertTableOptions): void;
499
499
  RemoveTable(range?: RangeReference): void;
500
500
  UpdateTableStyle(range?: RangeReference, theme?: TableTheme | number): void;
501
+ SetTabColor(sheet?: number | string, color?: Color): void;
501
502
 
502
503
  /**
503
504
  * Add a sheet, optionally named.
@@ -2153,6 +2154,11 @@ export interface AnnotationChartData extends AnnotationDataBase {
2153
2154
  }
2154
2155
  export interface AnnotationTextBoxData extends AnnotationDataBase {
2155
2156
  type: 'textbox';
2157
+
2158
+ /**
2159
+ * @internalRemarks
2160
+ * what's with this weird structure? did we inherit it? can we clean it up?
2161
+ */
2156
2162
  data: {
2157
2163
  style?: CellStyle;
2158
2164
  paragraphs: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "31.4.0",
3
+ "version": "31.7.2",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -125,14 +125,22 @@ export const ColorFunctions = {
125
125
  ////////////////
126
126
 
127
127
 
128
- GetLuminance: (r: number, g: number, b: number): number => {
128
+ GetLuminance: ([r, g, b]: number[],): number => {
129
129
  const a = [r, g, b].map(v => {
130
130
  v /= 255;
131
131
  return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
132
132
  });
133
133
  return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];
134
134
  },
135
-
135
+
136
+ WeightedLuminance: ([r, g, b]: number[], weights: number[] = [1,1,1]): number => {
137
+ const a = [r, g, b].map(v => {
138
+ v /= 255;
139
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
140
+ });
141
+ return 0.2126 * a[0] * weights[0] + 0.7152 * a[1] * weights[1] + 0.0722 * a[2] * weights[2];
142
+ },
143
+
136
144
  GetContrastRatio: (data: [number, number]): number => {
137
145
  data.sort((a, b) => b - a);
138
146
  return (data[0] + 0.05) / (data[1] + 0.05);
@@ -140,14 +148,39 @@ export const ColorFunctions = {
140
148
 
141
149
  GetTextColor: (background: [number, number, number], a: [number, number, number], b: [number, number, number]) => {
142
150
 
143
- const luminance = ColorFunctions.GetLuminance(...background);
144
- const luminance_a = ColorFunctions.GetLuminance(...a);
145
- const luminance_b = ColorFunctions.GetLuminance(...b);
151
+ // weighted contrast ratio: assign more weight to the r channel.
152
+
153
+ const weights = [0.4, 0.3, 0.3];
154
+
155
+ const luminance = ColorFunctions.WeightedLuminance(background, weights);
156
+ const luminance_a = ColorFunctions.WeightedLuminance(a, weights);
157
+ const luminance_b = ColorFunctions.WeightedLuminance(b, weights);
146
158
 
147
159
  const contrast_a = ColorFunctions.GetContrastRatio([luminance_a, luminance]);
148
160
  const contrast_b = ColorFunctions.GetContrastRatio([luminance_b, luminance]);
149
161
 
150
162
  return contrast_a > contrast_b ? a : b;
163
+
164
+ // perceptual lightness (actually I like this one)
165
+
166
+ /*
167
+ const background_lightness = ColorFunctions.RGBToHSL(...background).l;
168
+ const a_lightness = ColorFunctions.RGBToHSL(...a).l;
169
+ const b_lightness = ColorFunctions.RGBToHSL(...b).l;
170
+
171
+ console.info("background", background_lightness, "a", a_lightness, "b", b_lightness);
172
+
173
+ const lighter = a_lightness > b_lightness ? a : b;
174
+ const darker = a_lightness > b_lightness ? b : a;
175
+
176
+ if (background_lightness < .6) {
177
+ return lighter;
178
+ }
179
+ else {
180
+ return darker;
181
+ }
182
+ */
183
+
151
184
  },
152
185
 
153
186
  };
@@ -21,7 +21,7 @@
21
21
 
22
22
  import { UA } from 'treb-grid';
23
23
  import { Measurement } from 'treb-utils';
24
- import { type FontSize, Style } from './style';
24
+ import { Style, type FontSize } from './style';
25
25
 
26
26
  /**
27
27
  * these are the stacks we're currently supporting.
@@ -37,6 +37,15 @@ import { Color } from './vertex';
37
37
  * FIXME: it might be better to have an intermediate class/interface and
38
38
  * have both leaf- and spreadsheet-vertex extend that.
39
39
  *
40
+ * UPDATE: we're removing the internal "state" representation because (1)
41
+ * it should be unnecessary, if we are only updating when dependencies
42
+ * change, and (2) it was broken anyway.
43
+ *
44
+ * Now we rely on the calculation graph to indicate when the leaf is
45
+ * dirty and needs to update. This will result in extra calculation when
46
+ * you do a hard recalc, but that seems reasonable (and we could possibly
47
+ * work around that).
48
+ *
40
49
  */
41
50
  export class StateLeafVertex extends SpreadsheetVertex {
42
51
 
@@ -54,42 +63,6 @@ export class StateLeafVertex extends SpreadsheetVertex {
54
63
  */
55
64
  public color = Color.black;
56
65
 
57
- protected state_representation = '';
58
-
59
- /**
60
- * construct the state, compare, and increment the state id if
61
- * it changes. this is expected to be called from Calculate(), but
62
- * we can also call it on init if we already know the state.
63
- *
64
- * FIXME: what's more expensive, generating this state field or
65
- * re-rendering a chart with the same data? (...?)
66
- * especially since it's only called on dirty...
67
- *
68
- * what is the case where the depenendency is dirty but state
69
- * does not change? you type in the same value? (...) or maybe
70
- * there's a volatile function that doesn't change value (e.g. Today())
71
- *
72
- * still, it seems like a waste here. let's test without the state.
73
- * (meaning just update the flag anytime it's dirty)
74
- *
75
- * Actually I think the case is manual recalc, when values don't change
76
- * (especially true for MC charts).
77
- *
78
- * TODO: perf
79
- */
80
- public UpdateState(): void {
81
-
82
- // FIXME: hash!
83
- //const state = JSON.stringify(this.edges_in.map((edge) => (edge as SpreadsheetVertex).result));
84
- const state = JSON.stringify(Array.from(this.edges_in).map((edge) => (edge as SpreadsheetVertex).result));
85
-
86
- if (state !== this.state_representation) {
87
- this.state_representation = state;
88
- this.state_id++;
89
- }
90
-
91
- }
92
-
93
66
  /** overrides calculate function */
94
67
  public Calculate(): void {
95
68
 
@@ -103,11 +76,15 @@ export class StateLeafVertex extends SpreadsheetVertex {
103
76
  }
104
77
  }
105
78
 
106
- // ok, we can evaluate... all we are doing here is checking state consistency
107
- this.UpdateState();
79
+ // ok update state so clients know they need to refresh
80
+ // (see note above re: internal state)
81
+
82
+ this.state_id++;
83
+
84
+ // and we're clean
85
+
108
86
  this.dirty = false;
109
87
 
110
- // we are not allowed to have edges out, so nothing to do
111
88
  }
112
89
 
113
90
  public AddDependent(): void {
@@ -190,6 +190,11 @@ export interface AnnotationChartData extends AnnotationDataBase {
190
190
 
191
191
  export interface AnnotationTextBoxData extends AnnotationDataBase {
192
192
  type: 'textbox';
193
+
194
+ /**
195
+ * @internalRemarks
196
+ * what's with this weird structure? did we inherit it? can we clean it up?
197
+ */
193
198
  data: {
194
199
  style?: CellStyle;
195
200
  paragraphs: {
@@ -945,6 +945,8 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
945
945
 
946
946
  switch (event.type) {
947
947
 
948
+ // these messages can stack, they don't have undo effect
949
+
948
950
  case 'error':
949
951
  this.dialog?.ShowDialog({
950
952
  type: DialogType.error,
@@ -965,6 +967,34 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
965
967
  this.UpdateSelectionStyle();
966
968
  break;
967
969
 
970
+ case 'scale':
971
+ this.RebuildAllAnnotations();
972
+ this.Publish({ type: 'view-change' });
973
+ break;
974
+
975
+ case 'cell-event':
976
+ this.HandleCellEvent(event);
977
+ break;
978
+
979
+ // messages that trigger undo need some special handling,
980
+ // because we don't want to stack a sequence of messages
981
+ // and push multiple undo events. that applies to data,
982
+ // style, structure, and (maybe?) annotations
983
+
984
+ // OK, temp we have a composite event for data+style
985
+
986
+ case 'composite':
987
+ {
988
+ const cached_selection = this.last_selection;
989
+ if (this.calculation === CalculationOptions.automatic) {
990
+ this.Recalculate(event);
991
+ }
992
+ this.DocumentChange(cached_selection);
993
+ this.UpdateDocumentStyles();
994
+ this.UpdateSelectionStyle();
995
+ }
996
+ break;
997
+
968
998
  case 'data':
969
999
  {
970
1000
  // because this is async (more than once), we can't expect the
@@ -973,13 +1003,6 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
973
1003
 
974
1004
  const cached_selection = this.last_selection;
975
1005
 
976
- /*
977
- ((this.calculation === CalculationOptions.automatic) ?
978
- this.Recalculate(event) : Promise.resolve()).then(() => {
979
- this.DocumentChange(cached_selection);
980
- });
981
- */
982
-
983
1006
  // recalc is no longer async
984
1007
 
985
1008
  if (this.calculation === CalculationOptions.automatic) {
@@ -988,17 +1011,6 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
988
1011
 
989
1012
  this.DocumentChange(cached_selection);
990
1013
 
991
- /*
992
- if (this.calculation === CalculationOptions.automatic) {
993
- this.Recalculate(event).then(() => {
994
- this.DocumentChange(cached_selection);
995
- });
996
- }
997
- else {
998
- Promise.resolve().then(() => this.DocumentChange(cached_selection));
999
- }
1000
- */
1001
-
1002
1014
  }
1003
1015
  break;
1004
1016
 
@@ -1008,11 +1020,6 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1008
1020
  this.UpdateSelectionStyle();
1009
1021
  break;
1010
1022
 
1011
- case 'scale':
1012
- this.RebuildAllAnnotations();
1013
- this.Publish({ type: 'view-change' });
1014
- break;
1015
-
1016
1023
  case 'annotation':
1017
1024
  // FIXME: maybe need to update vertices (on create, update, delete,
1018
1025
  // not on move or resize)
@@ -1083,13 +1090,6 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1083
1090
  if (event.rebuild_required) {
1084
1091
  this.calculator.Reset();
1085
1092
 
1086
- /*
1087
- ((this.calculation === CalculationOptions.automatic) ?
1088
- this.Recalculate(event) : Promise.resolve()).then(() => {
1089
- this.DocumentChange(cached_selection);
1090
- });
1091
- */
1092
-
1093
1093
  // recalculate is no longer async
1094
1094
 
1095
1095
  if (this.calculation === CalculationOptions.automatic) {
@@ -1105,10 +1105,6 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1105
1105
  this.UpdateSelectionStyle();
1106
1106
  break;
1107
1107
 
1108
- case 'cell-event':
1109
- this.HandleCellEvent(event);
1110
- break;
1111
-
1112
1108
  }
1113
1109
  });
1114
1110
 
@@ -1411,18 +1407,25 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1411
1407
  if (event.type === 'structure') {
1412
1408
  this.grid.EnsureActiveSheet();
1413
1409
  this.grid.UpdateLayout();
1414
- // (this.grid as any).tab_bar?.Update();
1415
1410
  this.grid.UpdateTabBar();
1411
+
1412
+ if (event.update_annotations) {
1413
+ this.grid.RefreshAnnotations();
1414
+ this.InflateAnnotations();
1415
+ }
1416
+
1416
1417
  }
1417
1418
  });
1418
1419
 
1419
1420
  view.Subscribe(event => {
1421
+
1420
1422
  switch (event.type) {
1421
1423
  case 'selection':
1422
1424
  break;
1423
1425
  default:
1424
1426
  view.UpdateAnnotations();
1425
1427
  this.grid.Update(true);
1428
+ this.grid.UpdateAnnotations();
1426
1429
  }
1427
1430
  });
1428
1431
 
@@ -1430,8 +1433,13 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1430
1433
  if (event.type === 'structure') {
1431
1434
  view.grid.EnsureActiveSheet();
1432
1435
  view.grid.UpdateLayout();
1433
- // (view.grid as any).tab_bar?.Update();
1434
1436
  view.grid.UpdateTabBar();
1437
+
1438
+ if (event.update_annotations) {
1439
+ view.grid.RefreshAnnotations();
1440
+ view.InflateAnnotations();
1441
+ }
1442
+
1435
1443
  }
1436
1444
  });
1437
1445
 
@@ -1444,12 +1452,14 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1444
1452
  case 'reset':
1445
1453
  view.grid.EnsureActiveSheet(true); // force update of annotations
1446
1454
  view.UpdateAnnotations();
1455
+ view.grid.UpdateAnnotations();
1447
1456
  view.grid.Update(true);
1448
1457
  break;
1449
1458
 
1450
1459
  default:
1451
1460
  view.UpdateAnnotations();
1452
1461
  view.grid.Update(true);
1462
+ view.grid.UpdateAnnotations();
1453
1463
  }
1454
1464
  });
1455
1465
 
@@ -1651,7 +1661,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1651
1661
  if (selection && !selection.empty) {
1652
1662
  const label = selection.area.spreadsheet_label;
1653
1663
 
1654
- if (func === 'Scatter.Plot' || func === 'Scatter.Line') {
1664
+ if (func === 'Scatter.Plot' || func === 'Scatter.Line' || func === 'Box.Plot') {
1655
1665
  this.InsertAnnotation(`=${func}(Series(,,${label}),"${label}")`, undefined, undefined, ',');
1656
1666
  }
1657
1667
  else {
@@ -1758,8 +1768,6 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1758
1768
 
1759
1769
  if (scale && !isNaN(scale)) {
1760
1770
 
1761
- console.info("FS", scale);
1762
-
1763
1771
  this.grid.ApplyStyle(undefined, {
1764
1772
  //font_size_unit: 'em', font_size_value: scale
1765
1773
  font_size: {
@@ -1843,6 +1851,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1843
1851
  case 'insert-bar-chart': insert_annotation('Bar.Chart'); break;
1844
1852
  case 'insert-line-chart': insert_annotation('Line.Chart'); break;
1845
1853
  case 'insert-scatter-plot': insert_annotation('Scatter.Plot'); break;
1854
+ case 'insert-box-plot': insert_annotation('Box.Plot'); break;
1846
1855
 
1847
1856
  case 'increase-precision':
1848
1857
  case 'decrease-precision':
@@ -2230,12 +2239,22 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
2230
2239
  // FIXME: sheet change events (affects annotations)
2231
2240
  // TODO: sheet change events (affects annotations)
2232
2241
 
2242
+ // console.info({events});
2243
+
2233
2244
  for (const event of events) {
2234
- if (event.type === 'data') {
2235
- recalc = true;
2236
- }
2237
- else if (event.type === 'structure') {
2238
- if (event.rebuild_required) reset = true;
2245
+ switch (event.type) {
2246
+ case 'data':
2247
+ recalc = true;
2248
+ break;
2249
+
2250
+ case 'structure':
2251
+ if (event.rebuild_required) {
2252
+ reset = true;
2253
+ }
2254
+ break;
2255
+
2256
+ default:
2257
+ // console.info('unhandled event:', event.type, { event });
2239
2258
  }
2240
2259
  }
2241
2260
 
@@ -2417,6 +2436,17 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
2417
2436
 
2418
2437
  }
2419
2438
 
2439
+ public SetTabColor(sheet?: number|string, color?: Color) {
2440
+ const target = (typeof sheet === 'undefined') ?
2441
+ this.grid.active_sheet :
2442
+ this.model.sheets.Find(sheet);
2443
+
2444
+ if (target) {
2445
+ this.grid.TabColor(target, color);
2446
+ }
2447
+
2448
+ }
2449
+
2420
2450
  /**
2421
2451
  * Add a sheet, optionally named.
2422
2452
  */
@@ -2585,9 +2615,13 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
2585
2615
  * the argument separator, we allow that to be passed directly, but this
2586
2616
  * is deprecated. new code should use the options object.
2587
2617
  */
2588
- public InsertAnnotation(formula: string, type: AnnotationType = 'treb-chart', rect?: IRectangle|RangeReference, options?: EvaluateOptions|','|';'): void {
2618
+ public InsertAnnotation(
2619
+ formula: string,
2620
+ type: AnnotationType = 'treb-chart',
2621
+ rect?: IRectangle|RangeReference,
2622
+ options?: EvaluateOptions|','|';'): void {
2589
2623
 
2590
- let target: IRectangle | Partial<Area> | undefined;
2624
+ let target: IRectangle | IArea | undefined;
2591
2625
  let argument_separator: ','|';'|undefined = undefined;
2592
2626
  let r1c1 = false;
2593
2627
 
@@ -2664,7 +2698,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
2664
2698
  type,
2665
2699
  formula,
2666
2700
  // class_name,
2667
- }, undefined, undefined, target || { top: y / scale + 30, left: x / scale + 30, ...auto_size });
2701
+ }, undefined, undefined, undefined, target || { top: y / scale + 30, left: x / scale + 30, ...auto_size });
2668
2702
 
2669
2703
  }
2670
2704
 
@@ -5353,7 +5387,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
5353
5387
  src: contents,
5354
5388
  original_size: { width: img.width || 300, height: img.height || 300 },
5355
5389
  },
5356
- }, undefined, undefined, {
5390
+ }, undefined, undefined, undefined, {
5357
5391
  top: 30,
5358
5392
  left: 30,
5359
5393
  width: img.width || 300,
@@ -5488,7 +5522,17 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
5488
5522
 
5489
5523
  // set all views dirty in this case
5490
5524
  for (const view of annotation.view) {
5491
- view.dirty = true;
5525
+
5526
+ // if !view, that usually means it was just created. it should
5527
+ // get taken care of soon, so don't worry about it.
5528
+
5529
+ if (view) {
5530
+ view.dirty = true;
5531
+ }
5532
+ // else {
5533
+ // console.info("empty view")
5534
+ // }
5535
+
5492
5536
  }
5493
5537
  }
5494
5538
 
@@ -5508,6 +5552,8 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
5508
5552
  // either we just set this dirty for all views, or another
5509
5553
  // view set it dirty for us: in either case, update
5510
5554
 
5555
+ view.dirty = false;
5556
+
5511
5557
  if (view.update_callback) {
5512
5558
  view.update_callback();
5513
5559
  }
@@ -5785,6 +5831,9 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
5785
5831
  */
5786
5832
  protected DocumentChange(undo_selection?: string): void {
5787
5833
 
5834
+ console.info("DC");
5835
+ console.trace();
5836
+
5788
5837
  Promise.resolve().then(() => {
5789
5838
 
5790
5839
  // console.info('serializing');
@@ -5874,6 +5923,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
5874
5923
  const selection = last_selection || this.last_selection;
5875
5924
 
5876
5925
  // console.info('push undo', JSON.stringify(selection));
5926
+ // console.trace();
5877
5927
 
5878
5928
  if (this.undo_stack[this.undo_pointer - 1]) {
5879
5929
  this.undo_stack[this.undo_pointer - 1].selection = selection;
@@ -6429,12 +6479,15 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
6429
6479
  */
6430
6480
  protected HandleKeyDown(event: KeyboardEvent): void {
6431
6481
 
6432
- // can we drop the event.code stuff in 2024? (YES)
6433
-
6434
- if (event.ctrlKey && (event.code === 'KeyZ' || event.key === 'z')) {
6435
- event.stopPropagation();
6436
- event.preventDefault();
6437
- this.Undo();
6482
+ // handle osx properly, but leave the old combination on osx
6483
+ // in case anyone is using it. we can maybe drop it in the future
6484
+
6485
+ if (event.key === 'z') {
6486
+ if ((UA.is_mac && event.metaKey) || event.ctrlKey) {
6487
+ event.stopPropagation();
6488
+ event.preventDefault();
6489
+ this.Undo();
6490
+ }
6438
6491
  }
6439
6492
  else if (event.key === 'F9' && this.options.recalculate_on_f9) {
6440
6493
  event.stopPropagation();
@@ -23,7 +23,7 @@ export interface BorderToolbarMessage {
23
23
  }
24
24
 
25
25
  export interface AnnotationToolbarMessage {
26
- command: 'insert-image'|'insert-donut-chart'|'insert-line-chart'|'insert-column-chart'|'insert-bar-chart'|'insert-scatter-plot';
26
+ command: 'insert-image'|'insert-donut-chart'|'insert-line-chart'|'insert-column-chart'|'insert-bar-chart'|'insert-scatter-plot'|'insert-box-plot';
27
27
  }
28
28
 
29
29
  export interface LayoutToolbarMessage {