@trebco/treb 32.7.1 → 32.9.3

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 v32.7. Copyright 2018-2025 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v32.9. Copyright 2018-2025 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
2
2
  /*
3
3
  * This file is part of TREB.
4
4
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "32.7.1",
3
+ "version": "32.9.3",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -273,13 +273,32 @@ export class Calculator extends Graph {
273
273
  * this is a function that does sumif/averageif/countif.
274
274
  * args is one or more sets of [criteria_range, criteria]
275
275
  */
276
- const XIf = (type: 'sum'|'count'|'average', value_range: CellValue[][], ...args: unknown[]): UnionValue => {
276
+ const XIf = (type: 'sum'|'count'|'average', value_range: CellValue[][]|CellValue, ...args: unknown[]): UnionValue => {
277
+
278
+ // there's a bug here if the value range is a single value?
279
+ // that happens if countif passes in a one-cell range... we should
280
+ // handle this in the caller, or here?
281
+
282
+ // NOTE we also have to address this in the set of
283
+ // arguments, in which each pair could have a single
284
+ // value as the criterion
285
+
286
+ if (!Array.isArray(value_range)) {
287
+ value_range = [[value_range]];
288
+ }
277
289
 
278
290
  const filter: boolean[] = [];
279
291
 
280
292
  for (let i = 0; i < args.length; i += 2) {
281
- if (Array.isArray(args[i])) {
282
- const step = CountIfInternal(args[i] as CellValue[][], args[i+1] as CellValue);
293
+
294
+ let criteria_range = args[i] as (CellValue|CellValue[][]);
295
+ if (!Array.isArray(criteria_range)) {
296
+ criteria_range = [[criteria_range]];
297
+ }
298
+
299
+ { // if (Array.isArray(args[i])) {
300
+
301
+ const step = CountIfInternal(criteria_range, args[i+1] as CellValue);
283
302
  if (step.type !== ValueType.array) {
284
303
  return step;
285
304
  }
@@ -995,65 +1014,141 @@ export class Calculator extends Graph {
995
1014
 
996
1015
  /**
997
1016
  * FIXME: there are cases we are not handling
1017
+ *
1018
+ * update to return a reference so you can use it as part of a
1019
+ * range. we're not handling literal arrays atm.
998
1020
  */
999
1021
  Index: {
1022
+ return_type: 'reference',
1000
1023
  arguments: [
1001
- { name: 'range', boxed: true },
1024
+ { name: 'range', metadata: true, },
1002
1025
  { name: 'row', },
1003
1026
  { name: 'column', }
1004
1027
  ],
1028
+
1029
+ // volatile: true, // not sure this is necessary bc input is the range
1005
1030
  volatile: false,
1006
1031
 
1007
1032
  // FIXME: handle full row, full column calls
1008
- fn: (data: UnionValue, row?: number, column?: number) => {
1033
+ fn: (range: UnionValue, row: number|undefined, column: number|undefined) => {
1009
1034
 
1010
- // ensure array
1011
- if (data && data.type !== ValueType.array) {
1012
- data = {
1013
- type: ValueType.array,
1014
- value: [[data]],
1015
- };
1035
+ if (!range) {
1036
+ return ArgumentError();
1016
1037
  }
1017
1038
 
1018
- if (row && column) {
1039
+ // this is illegal, although we could just default to zeros
1019
1040
 
1020
- // simple case: 2 indexes
1041
+ if (row === undefined && column === undefined) {
1042
+ return ArgumentError();
1043
+ }
1044
+
1045
+ row = row || 0;
1046
+ column = column || 0;
1047
+
1048
+ let arr: { value: {address: UnitAddress }}[][] = [];
1049
+
1050
+ // NOTE: could be a single cell. in that case it won't be passed
1051
+ // as an array so we'll need a second branch (or convert it)
1052
+
1053
+ if (range.type === ValueType.array) {
1054
+
1055
+ // FIXME: validate these are addresses (shouldn't be necessary
1056
+ // if we're marking the argument as metadata? what about literals?)
1057
+
1058
+ // check rows and columns. we might need to return an array.
1059
+
1060
+ arr = range.value as unknown as { value: {address: UnitAddress }}[][];
1021
1061
 
1022
- const c = data.value[column - 1];
1023
- if (c) {
1024
- const cell = c[row - 1];
1025
- if (cell) {
1026
- return cell;
1027
- }
1028
- }
1029
1062
  }
1030
- else if (row) {
1063
+ else if (range.type === ValueType.object) {
1031
1064
 
1032
- // return an array
1065
+ const metadata = range.value as { type: string, address?: UnitAddress };
1033
1066
 
1034
- const value: UnionValue[][] = [];
1035
- for (const c of data.value) {
1036
- if (!c[row - 1]) {
1037
- return ArgumentError();
1038
- }
1039
- value.push([c[row-1]]);
1067
+ if (metadata.type === 'metadata' && metadata.address) {
1068
+ arr.push([range as { value: { address: UnitAddress }}]);
1040
1069
  }
1070
+
1071
+ }
1072
+
1073
+ const columns = arr.length;
1074
+ const rows = arr[0]?.length || 0;
1075
+
1076
+ if (rows <= 0 || columns <= 0 || row > rows || column > columns || row < 0 || column < 0) {
1077
+ return ArgumentError();
1078
+ }
1079
+
1080
+
1081
+ // return everything if arguments are (0, 0)
1082
+
1083
+ if (column === 0 && row === 0) {
1084
+
1085
+ // because of the way we're structured this we might be
1086
+ // returning a range of length 1; in that case we want
1087
+ // to return it as an address
1088
+
1089
+ const expression: ExpressionUnit = (columns === 1 && rows === 1) ? {
1090
+ ...arr[0][0].value.address,
1091
+ } : {
1092
+ type: 'range',
1093
+ start: arr[0][0].value.address,
1094
+ end: arr[columns-1][rows-1].value.address,
1095
+ label: '', position: 0,
1096
+ id: 0,
1097
+ };
1098
+
1041
1099
  return {
1042
- type: ValueType.array,
1043
- value,
1100
+ type: ValueType.object,
1101
+ value: expression,
1102
+ };
1103
+
1104
+ }
1105
+
1106
+ // single cell
1107
+
1108
+ if ((row || rows === 1) && (column || columns === 1)) {
1109
+ return {
1110
+ type: ValueType.object,
1111
+ value: arr[column ? column - 1 : 0][row ? row - 1 : 0].value.address,
1044
1112
  };
1045
1113
  }
1114
+
1115
+ // sub array
1116
+
1117
+ if (row) {
1118
+
1119
+ // return column
1120
+
1121
+ const expression: ExpressionUnit = {
1122
+ type: 'range',
1123
+ start: arr[0][row - 1].value.address,
1124
+ end: arr[columns-1][row-1].value.address,
1125
+ label: '', position: 0,
1126
+ id: 0,
1127
+ };
1128
+
1129
+ return {
1130
+ type: ValueType.object,
1131
+ value: expression,
1132
+ };
1133
+
1134
+ }
1046
1135
  else if (column) {
1047
1136
 
1048
- // return an array
1137
+ // return row
1138
+
1139
+ const expression: ExpressionUnit = {
1140
+ type: 'range',
1141
+ start: arr[column - 1][0].value.address,
1142
+ end: arr[column-1][rows-1].value.address,
1143
+ label: '', position: 0,
1144
+ id: 0,
1145
+ };
1146
+
1147
+ return {
1148
+ type: ValueType.object,
1149
+ value: expression,
1150
+ };
1049
1151
 
1050
- const c = data.value[column - 1];
1051
- if (c) {
1052
- return {
1053
- type: ValueType.array,
1054
- value: [c],
1055
- };
1056
- }
1057
1152
  }
1058
1153
 
1059
1154
  return ArgumentError();
@@ -959,6 +959,26 @@ export const BaseFunctionLibrary: FunctionMap = {
959
959
  },
960
960
  },
961
961
 
962
+ Factdouble: {
963
+ description: 'Returns the double factorial of a number',
964
+ arguments: [
965
+ { name: 'number', unroll: true },
966
+ ],
967
+ fn: (number: number): UnionValue => {
968
+ number = Math.round(number);
969
+
970
+ let value = 1;
971
+ while (number > 1) {
972
+ value *= number;
973
+ number -= 2;
974
+ }
975
+ return {
976
+ type: ValueType.number,
977
+ value,
978
+ }
979
+ },
980
+ },
981
+
962
982
  Power: {
963
983
  description: 'Returns base raised to the given power',
964
984
  arguments: [
@@ -32,7 +32,7 @@ const Log1p = (x: number) => {
32
32
  /**
33
33
  * continued fraction expansion
34
34
  */
35
- const BetaContFrac = (a: number, b: number, x: number, epsabs: number) => {
35
+ export const BetaContFrac = (a: number, b: number, x: number, epsabs: number) => {
36
36
 
37
37
  const cutoff = 2.0 * Number.MIN_VALUE;
38
38
 
@@ -242,3 +242,17 @@ export const BetaCDF = (x: number, a: number, b: number) => {
242
242
 
243
243
  };
244
244
 
245
+ export const BetaInc = (x: number, a: number, b: number): number => {
246
+ if (x < 0 || x > 1) return 0; // throw new Error("Invalid x in betainc");
247
+ const bt =
248
+ x === 0 || x === 1
249
+ ? 0
250
+ : Math.exp(LnGamma(a + b) - LnGamma(a) - LnGamma(b) + a * Math.log(x) + b * Math.log(1 - x));
251
+ if (x < (a + 1) / (a + b + 2)) {
252
+ return bt * BetaContFrac(a, b, x, 0) / a;
253
+ } else {
254
+ return 1 - bt * BetaContFrac(b, a, 1 - x, 0) / b;
255
+ }
256
+ };
257
+
258
+
@@ -0,0 +1,37 @@
1
+
2
+ /*
3
+ * This file is part of TREB.
4
+ *
5
+ * TREB is free software: you can redistribute it and/or modify it under the
6
+ * terms of the GNU General Public License as published by the Free Software
7
+ * Foundation, either version 3 of the License, or (at your option) any
8
+ * later version.
9
+ *
10
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13
+ * details.
14
+ *
15
+ * You should have received a copy of the GNU General Public License along
16
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
17
+ *
18
+ * Copyright 2022-2025 trebco, llc.
19
+ * info@treb.app
20
+ *
21
+ */
22
+
23
+ /** imprecise but reasonably fast normsinv function */
24
+ export const InverseNormal = (q: number): number => {
25
+
26
+ if (q === 0.50) {
27
+ return 0;
28
+ }
29
+
30
+ const p = (q < 1.0 && q > 0.5) ? (1 - q) : q;
31
+ const t = Math.sqrt(Math.log(1.0 / Math.pow(p, 2.0)));
32
+ const x = t - (2.515517 + 0.802853 * t + 0.010328 * Math.pow(t, 2.0)) /
33
+ (1.0 + 1.432788 * t + 0.189269 * Math.pow(t, 2.0) + 0.001308 * Math.pow(t, 3.0));
34
+
35
+ return (q > 0.5 ? x : -x);
36
+
37
+ };
@@ -27,6 +27,8 @@ import * as ComplexMath from '../complex-math';
27
27
 
28
28
  import { BetaCDF, BetaPDF, InverseBeta, LnGamma } from './beta';
29
29
  import { gamma_p } from './gamma';
30
+ import { InverseNormal } from './normal';
31
+ import { tCDF, tInverse, tPDF } from './students-t';
30
32
 
31
33
  /** error function (for gaussian distribution) */
32
34
  const erf = (x: number): number => {
@@ -62,21 +64,7 @@ const norm_dist = (x: number, mean: number, stdev: number, cumulative: boolean)
62
64
 
63
65
  }
64
66
 
65
- /** imprecise but reasonably fast normsinv function */
66
- const inverse_normal = (q: number): number => {
67
67
 
68
- if (q === 0.50) {
69
- return 0;
70
- }
71
-
72
- const p = (q < 1.0 && q > 0.5) ? (1 - q) : q;
73
- const t = Math.sqrt(Math.log(1.0 / Math.pow(p, 2.0)));
74
- const x = t - (2.515517 + 0.802853 * t + 0.010328 * Math.pow(t, 2.0)) /
75
- (1.0 + 1.432788 * t + 0.189269 * Math.pow(t, 2.0) + 0.001308 * Math.pow(t, 3.0));
76
-
77
- return (q > 0.5 ? x : -x);
78
-
79
- };
80
68
 
81
69
  const Median = (data: number[]) => {
82
70
  const n = data.length;
@@ -478,7 +466,7 @@ export const StatisticsFunctionLibrary: FunctionMap = {
478
466
  fn: (q: number, mean = 0, stdev = 1): UnionValue => {
479
467
  return {
480
468
  type: ValueType.number,
481
- value: inverse_normal(q) * stdev + mean,
469
+ value: InverseNormal(q) * stdev + mean,
482
470
  }
483
471
  }
484
472
  },
@@ -492,7 +480,7 @@ export const StatisticsFunctionLibrary: FunctionMap = {
492
480
  fn: (q: number): UnionValue => {
493
481
  return {
494
482
  type: ValueType.number,
495
- value: inverse_normal(q),
483
+ value: InverseNormal(q),
496
484
  }
497
485
  }
498
486
  },
@@ -831,6 +819,71 @@ export const StatisticsFunctionLibrary: FunctionMap = {
831
819
  }
832
820
  },
833
821
 
822
+ 'T.DIST': {
823
+ description: `Returns the left-tailed Student's t-distribution`,
824
+ arguments: [
825
+ { name: 'X', unroll: true, boxed: true, },
826
+ { name: 'degrees of freedom', unroll: true, boxed: true, },
827
+ { name: 'cumulative', unroll: true, boxed: true, },
828
+ ],
829
+ fn: (x: UnionValue, df: UnionValue, cumulative?: UnionValue) => {
830
+
831
+ const cum = cumulative ? !!cumulative.value : false;
832
+
833
+ if (df.type !== ValueType.number || x.type !== ValueType.number || df.value < 1) {
834
+ return ArgumentError();
835
+ }
836
+
837
+ return {
838
+ type: ValueType.number,
839
+ value: cum ? tCDF(x.value, df.value) : tPDF(x.value, df.value),
840
+ }
841
+
842
+ },
843
+ },
844
+
845
+ 'T.Inv': {
846
+ description: `Returns the left-tailed inverse of the Student's t-distribution`,
847
+ arguments: [
848
+ {
849
+ name: 'Probability', boxed: true, unroll: true,
850
+ },
851
+ {
852
+ name: 'Degrees of freedom', boxed: true, unroll: true,
853
+ },
854
+ ],
855
+ fn: (p: UnionValue, df: UnionValue) => {
856
+ if (df.type !== ValueType.number || df.value < 1 || p.type !== ValueType.number || p.value <= 0 || p.value >= 1) {
857
+ return ArgumentError();
858
+ }
859
+ return {
860
+ type: ValueType.number,
861
+ value: tInverse(p.value, df.value),
862
+ };
863
+ },
864
+ },
865
+
866
+ 'T.Inv.2T': {
867
+ description: `Returns the two-tailed inverse of the Student's t-distribution`,
868
+ arguments: [
869
+ {
870
+ name: 'Probability', boxed: true, unroll: true,
871
+ },
872
+ {
873
+ name: 'Degrees of freedom', boxed: true, unroll: true,
874
+ },
875
+ ],
876
+ fn: (p: UnionValue, df: UnionValue) => {
877
+ if (df.type !== ValueType.number || df.value < 1 ||p.type !== ValueType.number || p.value <= 0 || p.value >= 1) {
878
+ return ArgumentError();
879
+ }
880
+ return {
881
+ type: ValueType.number,
882
+ value: Math.abs(tInverse(1 - p.value/2, df.value)),
883
+ };
884
+ },
885
+ },
886
+
834
887
  GammaLn: {
835
888
  description: 'Returns the natural log of the gamma function',
836
889
  arguments: [{ name: 'value', boxed: true, unroll: true }],
@@ -0,0 +1,85 @@
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-2025 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ import { BetaInc, LnGamma } from './beta';
23
+ import { InverseNormal } from './normal';
24
+
25
+ export const tCDF = (t: number, df: number) => {
26
+ const x = df / (df + t * t);
27
+ const a = df / 2;
28
+ const b = 0.5;
29
+ const ib = BetaInc(x, a, b);
30
+ return t >= 0 ? 1 - 0.5 * ib : 0.5 * ib;
31
+ };
32
+
33
+ export const tPDF = (t: number, nu: number): number => {
34
+
35
+ const logNumerator = LnGamma((nu + 1) / 2);
36
+ const logDenominator = LnGamma(nu / 2) + 0.5 * Math.log(nu * Math.PI);
37
+ const logFactor = -((nu + 1) / 2) * Math.log(1 + (t * t) / nu);
38
+
39
+ const logPDF = logNumerator - logDenominator + logFactor;
40
+
41
+ return Math.exp(logPDF);
42
+
43
+ }
44
+
45
+ export const tInverse = (p: number, df: number) => {
46
+
47
+ const EPS = 1e-12;
48
+
49
+ let x = 1;
50
+
51
+ // Hill's approximation for initial guess
52
+
53
+ {
54
+ const t = InverseNormal(p);
55
+ const g1 = (Math.pow(t, 3) + t) / 4;
56
+ const g2 = ((5 * Math.pow(t, 5)) + (16 * Math.pow(t, 3)) + (3 * t)) / 96;
57
+ const g3 = ((3 * Math.pow(t, 7)) + (19 * Math.pow(t, 5)) + (17 * Math.pow(t, 3)) - 15 * t) / 384;
58
+ const g4 = ((79 * Math.pow(t, 9)) + (776 * Math.pow(t, 7)) + (1482 * Math.pow(t, 5)) - (1920 * Math.pow(t, 3)) - (945 * t)) / 92160;
59
+
60
+ x = t + g1 / df + g2 / Math.pow(df, 2) + g3 / Math.pow(df, 3) + g4 / Math.pow(df, 4);
61
+ }
62
+
63
+ for (let i = 0; i < 16; i++) {
64
+
65
+ const error = tCDF(x, df) - p;
66
+ if (Math.abs(error) < EPS) {
67
+ break;
68
+ }
69
+
70
+ const derivative = tPDF(x, df);
71
+ if (derivative === 0) {
72
+ break;
73
+ }
74
+
75
+ const delta = error / derivative;
76
+ const step = Math.max(-1, Math.min(1, delta));
77
+
78
+ x -= step;
79
+
80
+ }
81
+
82
+ return x;
83
+
84
+ };
85
+
@@ -894,6 +894,11 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
894
894
  else if (UA.is_mac) {
895
895
  container.parentElement?.classList.add('treb-ua-osx');
896
896
  }
897
+
898
+ if (UA.is_iphone) {
899
+ container.parentElement?.classList.add('treb-ua-iphone');
900
+ }
901
+
897
902
  }
898
903
 
899
904
  // container is "treb-views", which contains individual "treb-view"
@@ -43,6 +43,9 @@
43
43
  position: absolute;
44
44
  pointer-events: none;
45
45
 
46
+ // patch for ios safari bug
47
+ transform: translateZ(0);
48
+
46
49
  rect {
47
50
  stroke: var(--treb-spill-border-color, rgb(92, 92, 224));
48
51
  stroke-dasharray: var(--treb-spill-border-dasharray, 0);
@@ -311,6 +314,7 @@
311
314
  background: transparent;
312
315
  position: absolute;
313
316
  z-index: $z-index-grid-selection;
317
+ transform: translateZ(0); // patch for ios safari bug
314
318
  -moz-transform: scale(1); // firefox anti-blur
315
319
  }
316
320
 
@@ -323,6 +327,7 @@
323
327
  position: absolute;
324
328
  z-index: $z-index-frozen-selection;
325
329
  overflow: hidden; // needed for IE11 (put in legacy?)
330
+ transform: translateZ(0); // patch for ios safari bug
326
331
  -moz-transform: scale(1); // firefox anti-blur
327
332
  pointer-events: none;
328
333
 
@@ -7889,6 +7889,10 @@ export class Grid extends GridBase {
7889
7889
  */
7890
7890
  protected ResizeColumnsInternal(command: ResizeColumnsCommand) {
7891
7891
 
7892
+ if (this.headless) {
7893
+ return super.ResizeColumnsInternal(command);
7894
+ }
7895
+
7892
7896
  const sheet = command.sheet_id ? this.FindSheet(command.sheet_id) : this.active_sheet;
7893
7897
 
7894
7898
  // normalize
@@ -39,6 +39,9 @@ class UAType {
39
39
  /** more testing. ios safari doesn't support grid+sticky (apparently) */
40
40
  public readonly is_ipad = /iPad|iPhone/.test(user_agent);
41
41
 
42
+ /** for iphone so we can change font size to prevent auto-zoom */
43
+ public readonly is_iphone = /iPhone/.test(user_agent);
44
+
42
45
  /** more testing. firefox android doesn't support grid+sticky (apparently) */
43
46
  public readonly is_android = /android|samsung/i.test(user_agent);
44
47
 
@@ -84,19 +87,20 @@ class UAType {
84
87
  /webkit|firefox/i.test(user_agent);
85
88
  }
86
89
 
87
- const null_ua = {
90
+ const null_ua: UAType = {
88
91
 
89
92
  is_edge: false,
90
93
  is_ipad: false,
94
+ is_iphone: false,
91
95
  is_android: false,
92
96
  is_firefox: false,
93
97
  is_safari: false,
94
98
  is_mac: false,
95
99
  is_chrome: false,
96
- trident: false,
100
+ // trident: false,
97
101
  is_windows: false,
98
102
  is_modern: true,
99
- is_node: true,
103
+ // is_node: true,
100
104
  is_mobile: false,
101
105
 
102
106
  };
@@ -630,13 +630,23 @@ export class Parser {
630
630
  }).join(', ')).join('; ') + '}';
631
631
 
632
632
  case 'binary':
633
- return (
634
- this.Render(unit.left, options) +
635
- ' ' +
636
- unit.operator +
637
- ' ' +
638
- this.Render(unit.right, options)
639
- );
633
+
634
+ // in some cases we might see range constructs as binary units
635
+ // because one side (or maybe both sides) of the range is a
636
+ // function. in that case we don't want a space in front of the
637
+ // operator.
638
+
639
+ {
640
+ const separator = (unit.operator === ':' ? '': ' ');
641
+
642
+ return (
643
+ this.Render(unit.left, options) +
644
+ separator +
645
+ unit.operator +
646
+ separator +
647
+ this.Render(unit.right, options)
648
+ );
649
+ }
640
650
 
641
651
  case 'unary':
642
652
  return (