@trebco/treb 28.13.2 → 28.15.1

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 v28.13. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v28.15. 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
@@ -1077,7 +1077,7 @@ export interface FreezePane {
1077
1077
  rows: number;
1078
1078
  columns: number;
1079
1079
  }
1080
- export type AnnotationType = 'treb-chart' | 'image' | 'external';
1080
+ export type AnnotationType = 'treb-chart' | 'image' | 'textbox' | 'external';
1081
1081
  export declare type BorderConstants = "none" | "all" | "outside" | "top" | "bottom" | "left" | "right";
1082
1082
 
1083
1083
  /**
@@ -1274,6 +1274,9 @@ export interface CellStyle {
1274
1274
  /** border color */
1275
1275
  border_bottom_fill?: Color;
1276
1276
 
1277
+ /** text indent */
1278
+ indent?: number;
1279
+
1277
1280
  /**
1278
1281
  * cell is locked for editing
1279
1282
  */
@@ -1805,7 +1808,7 @@ export interface SerializedGridSelection {
1805
1808
  /** for cacheing addtional selections. optimally don't serialize */
1806
1809
  rendered?: boolean;
1807
1810
  }
1808
- export type AnnotationData = AnnotationChartData | AnnotationImageData | AnnotationExternalData;
1811
+ export type AnnotationData = AnnotationChartData | AnnotationImageData | AnnotationExternalData | AnnotationTextBoxData;
1809
1812
  export interface ImageSize {
1810
1813
  width: number;
1811
1814
  height: number;
@@ -1888,6 +1891,19 @@ export interface AnnotationImageData extends AnnotationDataBase {
1888
1891
  export interface AnnotationChartData extends AnnotationDataBase {
1889
1892
  type: 'treb-chart';
1890
1893
  }
1894
+ export interface AnnotationTextBoxData extends AnnotationDataBase {
1895
+ type: 'textbox';
1896
+ data: {
1897
+ style?: CellStyle;
1898
+ paragraphs: {
1899
+ style?: CellStyle;
1900
+ content: {
1901
+ text: string;
1902
+ style?: CellStyle;
1903
+ }[];
1904
+ }[];
1905
+ };
1906
+ }
1891
1907
  export interface AnnotationExternalData extends AnnotationDataBase {
1892
1908
  type: 'external';
1893
1909
  data: Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "28.13.2",
3
+ "version": "28.15.1",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -145,6 +145,9 @@ export interface CellStyle {
145
145
  /** border color */
146
146
  border_bottom_fill?: Color;
147
147
 
148
+ /** text indent */
149
+ indent?: number;
150
+
148
151
  /**
149
152
  * cell is locked for editing
150
153
  *
@@ -806,15 +806,11 @@ export class ExpressionCalculator {
806
806
 
807
807
  }
808
808
 
809
- //protected ElementwiseBinaryExpression(fn: Primitives.PrimitiveBinaryExpression, left: UnionValue[][], right: UnionValue[][]): UnionValue[][] {
810
809
  protected ElementwiseBinaryExpression(fn: Primitives.PrimitiveBinaryExpression, left: ArrayUnion, right: ArrayUnion): ArrayUnion {
811
810
 
812
811
  const columns = Math.max(left.value.length, right.value.length);
813
812
  const rows = Math.max(left.value[0].length, right.value[0].length);
814
813
 
815
- // const columns = Math.max(left.length, right.length);
816
- // const rows = Math.max(left[0].length, right[0].length);
817
-
818
814
  const left_values = this.RecycleArray(left.value, columns, rows);
819
815
  const right_values = this.RecycleArray(right.value, columns, rows);
820
816
 
@@ -822,8 +818,16 @@ export class ExpressionCalculator {
822
818
 
823
819
  for (let c = 0; c < columns; c++) {
824
820
  const col: UnionValue[] = [];
821
+
825
822
  for (let r = 0; r < rows; r++ ) {
826
- col[r] = fn(left_values[c][r], right_values[c][r]);
823
+
824
+ // handle undefineds. this is unfortunate. shouldn't the recycle
825
+ // function do that? ...CHECK/TODO/FIXME
826
+
827
+ col[r] = fn(
828
+ left_values[c][r] || { type: ValueType.undefined },
829
+ right_values[c][r] || { type: ValueType.undefined });
830
+
827
831
  }
828
832
  value.push(col);
829
833
  }
@@ -88,6 +88,98 @@ const inverse_normal = (q: number): number => {
88
88
 
89
89
  };
90
90
 
91
+ const zlookup_arguments = [
92
+ {
93
+ name: "Lookup value",
94
+ },
95
+ {
96
+ name: "Table",
97
+ },
98
+ {
99
+ name: "Result index",
100
+ },
101
+ {
102
+ name: "Inexact",
103
+ default: true,
104
+ },
105
+ ];
106
+
107
+ /**
108
+ * unified VLOOKUP/HLOOKUP. ordinarily we'd call it XLOOKUP but that's taken.
109
+ * FIXME: can't use use that function for this?
110
+ */
111
+ const ZLookup = (value: any, table: any[][], col: number, inexact = true, transpose = false): UnionValue => {
112
+
113
+ if (transpose) {
114
+ table = Utils.TransposeArray(table);
115
+ }
116
+
117
+ col = Math.max(0, col - 1);
118
+
119
+ // inexact is the default. this assumes that the data is sorted,
120
+ // either numerically or alphabetically. it returns the closest
121
+ // value without going over -- meaning walk the list, and when
122
+ // you're over return the _previous_ item. except if there's an
123
+ // exact match, I guess, in that case return the exact match.
124
+
125
+ // FIXME: there's a hint in the docs for XLOOKUP that this might
126
+ // be using a binary search. not sure why, but that might be
127
+ // correct.
128
+
129
+ if (inexact) {
130
+
131
+ let result: any = table[col][0];
132
+
133
+ if (typeof value === 'number') {
134
+
135
+ let compare = Number(table[0][0]);
136
+ if (isNaN(compare) || compare > value) {
137
+ return NAError();
138
+ }
139
+
140
+ for (let i = 1; i < table[0].length; i++) {
141
+ compare = Number(table[0][i]);
142
+ if (isNaN(compare) || compare > value) {
143
+ break;
144
+ }
145
+ result = table[col][i];
146
+
147
+ }
148
+
149
+ }
150
+ else {
151
+
152
+ value = value.toLowerCase(); // ?
153
+ let compare: string = (table[0][0] || '').toString().toLowerCase();
154
+ if (compare.localeCompare(value) > 0) {
155
+ return NAError();
156
+ }
157
+
158
+ for (let i = 1; i < table[0].length; i++) {
159
+ compare = (table[0][i] || '').toString().toLowerCase();
160
+ if (compare.localeCompare(value) > 0) {
161
+ break;
162
+ }
163
+ result = table[col][i];
164
+
165
+ }
166
+
167
+ }
168
+
169
+ return Box(result);
170
+
171
+ }
172
+ else {
173
+ for (let i = 0; i < table[0].length; i++) {
174
+ if (table[0][i] == value) { // ==
175
+ return Box(table[col][i]);
176
+ }
177
+ }
178
+ return NAError();
179
+ }
180
+
181
+ };
182
+
91
183
  /**
92
184
  * alternate functions. these are used (atm) only for changing complex
93
185
  * behavior.
@@ -637,6 +729,77 @@ export const BaseFunctionLibrary: FunctionMap = {
637
729
  })
638
730
  },
639
731
 
732
+ Large: {
733
+ description: 'Returns the nth numeric value from the data, in descending order',
734
+ arguments: [
735
+ {
736
+ name: 'values',
737
+ },
738
+ {
739
+ name: 'index',
740
+ }
741
+ ],
742
+
743
+ // OK a little tricky here -- we're going to reverse the arguments
744
+ // so we can call apply as array, but applying against the trailing
745
+ // arguments.
746
+
747
+ fn: Utils.ApplyAsArraySwap((data: any, index: number) => {
748
+
749
+ if (index <= 0) {
750
+ return ArgumentError();
751
+ }
752
+
753
+ const flat = Utils.FlattenUnboxed(data);
754
+ const numeric: number[] = flat.filter(test => typeof test === 'number');
755
+ numeric.sort((a, b) => b - a);
756
+
757
+ if (index <= numeric.length) {
758
+ return {
759
+ type: ValueType.number,
760
+ value: numeric[index - 1],
761
+ };
762
+ }
763
+
764
+ return ArgumentError();
765
+ }),
766
+ },
767
+
768
+ Small: {
769
+ description: 'Returns the nth numeric value from the data, in ascending order',
770
+ arguments: [
771
+ {
772
+ name: 'values',
773
+ },
774
+ {
775
+ name: 'index',
776
+ }
777
+ ],
778
+
779
+ // see large, above
780
+
781
+ fn: Utils.ApplyAsArraySwap((data: any, index: number) => {
782
+
783
+ if (index <= 0) {
784
+ return ArgumentError();
785
+ }
786
+
787
+ const flat = Utils.FlattenUnboxed(data);
788
+ const numeric: number[] = flat.filter(test => typeof test === 'number');
789
+ numeric.sort((a, b) => a - b);
790
+
791
+ if (index <= numeric.length) {
792
+ return {
793
+ type: ValueType.number,
794
+ value: numeric[index - 1],
795
+ };
796
+ }
797
+
798
+ return ArgumentError();
799
+
800
+ }),
801
+ },
802
+
640
803
  /**
641
804
  * sort arguments, but ensure we return empty strings to
642
805
  * fill up the result array
@@ -803,90 +966,275 @@ export const BaseFunctionLibrary: FunctionMap = {
803
966
  },
804
967
  */
805
968
 
806
- /**
807
- * FIXME: does not implement inexact matching (what's the algo for
808
- * that, anyway? nearest? price is right style? what about ties?)
969
+ /*
970
+ * unsaid anywhere (that I can locate) aboud XLOOKUP is that lookup
971
+ * array must be one-dimensional. it can be either a row or a column,
972
+ * but one dimension must be one. that simplifies things quite a bit.
973
+ *
974
+ * there's a note in the docs about binary search over the data --
975
+ * that might explain how inexact VLOOKUP works as well. seems an odd
976
+ * choice but maybe back in the day it made sense
809
977
  */
810
- VLookup: {
811
-
978
+ XLOOKUP: {
812
979
  arguments: [
813
- {
814
- name: "Lookup value",
815
- },
816
- {
817
- name: "Table",
818
- },
819
- {
820
- name: "Result index",
821
- },
822
- {
823
- name: "Inexact",
824
- default: true,
825
- },
980
+ { name: 'Lookup value', },
981
+ { name: 'Lookup array', },
982
+ { name: 'Return array', },
983
+ { name: 'Not found', boxed: true },
984
+ { name: 'Match mode', default: 0, },
985
+ { name: 'Search mode', default: 1, },
826
986
  ],
987
+ xlfn: true,
988
+ fn: (
989
+ lookup_value: any,
990
+ lookup_array: any[][],
991
+ return_array: any[][],
992
+ not_found?: UnionValue,
993
+ match_mode = 0,
994
+ search_mode = 1,
995
+ ): UnionValue => {
996
+
997
+ // FIXME: we could I suppose be more graceful about single values
998
+ // if passed instead of arrays
999
+
1000
+ if (!Array.isArray(lookup_array)) {
1001
+ console.info("lookup is not an array");
1002
+ return ValueError();
1003
+ }
827
1004
 
828
- fn: (value: any, table: any[][], col: number, inexact = true): UnionValue => {
1005
+ const first = lookup_array[0];
1006
+ if (!Array.isArray(first)) {
1007
+ console.info("lookip is not a 2d array");
1008
+ return ValueError();
1009
+ }
829
1010
 
830
- col = Math.max(0, col - 1);
1011
+ if (lookup_array.length !== 1 && first.length !== 1) {
1012
+ console.info("lookup array has invalid dimensions");
1013
+ return ValueError();
1014
+ }
831
1015
 
832
- // inexact is the default. this assumes that the data is sorted,
833
- // either numerically or alphabetically. it returns the closest
834
- // value without going over -- meaning walk the list, and when
835
- // you're over return the _previous_ item. except if there's an
836
- // exact match, I guess, in that case return the exact match.
837
-
838
- if (inexact) {
1016
+ // FIXME: is it required that the return array be (at least) the
1017
+ // same size? we can return undefineds, but maybe we should error
839
1018
 
840
- let result: any = table[col][0];
1019
+ if (!Array.isArray(return_array)) {
1020
+ console.info("return array is not an array");
1021
+ return ValueError();
1022
+ }
841
1023
 
842
- if (typeof value === 'number') {
1024
+ let transpose = (lookup_array.length === 1);
1025
+ if (transpose) {
1026
+ lookup_array = Utils.TransposeArray(lookup_array);
1027
+ return_array = Utils.TransposeArray(return_array);
1028
+ }
843
1029
 
844
- let compare = Number(table[0][0]);
845
- if (isNaN(compare) || compare > value) {
846
- return NAError();
847
- }
1030
+ // maybe reverse...
1031
+
1032
+ if (search_mode < 0) {
1033
+ lookup_array.reverse();
1034
+ return_array.reverse();
1035
+ }
848
1036
 
849
- for (let i = 1; i < table[0].length; i++) {
850
- compare = Number(table[0][i]);
851
- if (isNaN(compare) || compare > value) {
852
- break;
1037
+ //
1038
+ // return value at index, transpose if necessary, and return
1039
+ // an array. we might prefer to return a scalar if there's only
1040
+ // one value, not sure what's the intended behavior
1041
+ //
1042
+ const ReturnIndex = (index: number): UnionValue => {
1043
+
1044
+ const values = return_array[index];
1045
+
1046
+ if (!values) {
1047
+ return { type: ValueType.undefined };
1048
+ }
1049
+
1050
+ if (!Array.isArray(values)) {
1051
+ return Box(values);
1052
+ }
1053
+
1054
+ let boxes = [values.map(value => Box(value))];
1055
+
1056
+ if (transpose) {
1057
+ boxes = Utils.TransposeArray(boxes);
1058
+ }
1059
+
1060
+ return {
1061
+ type: ValueType.array,
1062
+ value: boxes,
1063
+ }
1064
+
1065
+ };
1066
+
1067
+ // if value is not a string, then we can ignore wildcards.
1068
+ // in that case convert to exact match.
1069
+
1070
+ if (match_mode === 2 && typeof lookup_value !== 'string') {
1071
+ match_mode = 0;
1072
+ }
1073
+
1074
+ // what does inexact matching mean in this case if the lookup
1075
+ // value is a string or boolean? (...)
1076
+
1077
+ if ((match_mode === 1 || match_mode === -1) && typeof lookup_value === 'number') {
1078
+
1079
+ let min_delta = 0;
1080
+ let index = -1;
1081
+
1082
+ for (let i = 0; i < lookup_array.length; i++) {
1083
+ const value = lookup_array[i][0];
1084
+
1085
+
1086
+ if (typeof value === 'number') {
1087
+
1088
+ // check for exact match first, just in case
1089
+ if (value === lookup_value) {
1090
+ return ReturnIndex(i);
1091
+ }
1092
+
1093
+ const delta = Math.abs(value - lookup_value);
1094
+
1095
+ if ((match_mode === 1 && value > lookup_value) || (match_mode === -1 && value < lookup_value)){
1096
+ if (index < 0 || delta < min_delta) {
1097
+ min_delta = delta;
1098
+ index = i;
1099
+ }
853
1100
  }
854
- result = table[col][i];
855
1101
 
856
1102
  }
1103
+ }
857
1104
 
1105
+ if (index >= 0) {
1106
+ return ReturnIndex(index);
858
1107
  }
859
- else {
860
1108
 
861
- value = value.toLowerCase(); // ?
862
- let compare: string = (table[0][0] || '').toString().toLowerCase();
863
- if (compare.localeCompare(value) > 0) {
864
- return NAError();
865
- }
1109
+ }
1110
+
1111
+ switch (match_mode) {
1112
+
1113
+ case 2:
1114
+ {
1115
+ // wildcard string match. we only handle strings for
1116
+ // this case (see above).
866
1117
 
867
- for (let i = 1; i < table[0].length; i++) {
868
- compare = (table[0][i] || '').toString().toLowerCase();
869
- if (compare.localeCompare(value) > 0) {
870
- break;
1118
+ const pattern = Utils.ParseWildcards(lookup_value);
1119
+ const regex = new RegExp('^' + pattern + '$', 'i'); //.exec(lookup_value);
1120
+
1121
+ for (let i = 0; i < lookup_array.length; i++) {
1122
+ let value = lookup_array[i][0];
1123
+ if (typeof value === 'string' && regex.exec(value)) {
1124
+ return ReturnIndex(i);
1125
+ }
871
1126
  }
872
- result = table[col][i];
873
1127
 
874
1128
  }
1129
+ break;
875
1130
 
876
- }
1131
+ case 0:
1132
+ if (typeof lookup_value === 'string') {
1133
+ lookup_value = lookup_value.toLowerCase();
1134
+ }
1135
+ for (let i = 0; i < lookup_array.length; i++) {
1136
+ let value = lookup_array[i][0];
1137
+
1138
+ if (typeof value === 'string') {
1139
+ value = value.toLowerCase();
1140
+ }
1141
+ if (value === lookup_value) {
1142
+ return ReturnIndex(i);
1143
+ }
1144
+ }
877
1145
 
878
- return Box(result);
1146
+ break;
1147
+ }
1148
+
1149
+ /*
1150
+ const flat_lookup = Utils.FlattenUnboxed(lookup_array);
1151
+ const flat_return = Utils.FlattenUnboxed(return_array);
879
1152
 
1153
+ // maybe reverse...
1154
+
1155
+ if (search_mode < 0) {
1156
+ flat_lookup.reverse();
1157
+ flat_return.reverse();
880
1158
  }
881
- else {
882
- for (let i = 0; i < table[0].length; i++) {
883
- if (table[0][i] == value) { // ==
884
- return Box(table[col][i]);
1159
+
1160
+ // if value is not a string, then we can ignore wildcards.
1161
+ // in that case convert to exact match.
1162
+
1163
+ if (match_mode === 2 && typeof lookup_value !== 'string') {
1164
+ match_mode = 0;
1165
+ }
1166
+
1167
+ switch (match_mode) {
1168
+ case 2:
1169
+
1170
+ {
1171
+
1172
+ // wildcard string match. we only handle strings
1173
+ // for wildcard matching (handled above).
1174
+
1175
+ const pattern = Utils.ParseWildcards(lookup_value);
1176
+ const regex = new RegExp('^' + pattern + '$', 'i'); //.exec(lookup_value);
1177
+
1178
+ for (let i = 0; i < flat_lookup.length; i++) {
1179
+ let value = flat_lookup[i];
1180
+ if (typeof value === 'string' && regex.exec(value)) {
1181
+ return Box(flat_return[i]);
1182
+ }
1183
+ }
1184
+
1185
+
885
1186
  }
886
- }
887
- return NAError();
1187
+
1188
+ break;
1189
+
1190
+ case 0:
1191
+
1192
+ // return exact match or NA/default. in this case
1193
+ // "exact" means icase (but not wildcard)
1194
+
1195
+ if (typeof lookup_value === 'string') {
1196
+ lookup_value = lookup_value.toLowerCase();
1197
+ }
1198
+ for (let i = 0; i < flat_lookup.length; i++) {
1199
+ let value = flat_lookup[i];
1200
+ if (typeof value === 'string') {
1201
+ value = value.toLowerCase();
1202
+ }
1203
+ if (value === lookup_value) {
1204
+ return Box(flat_return[i]);
1205
+ }
1206
+ }
1207
+
1208
+ break;
888
1209
  }
1210
+ */
1211
+
1212
+ // FIXME: if we're expecting to return an array maybe we should
1213
+ // pack it up as an array? if it's not already an array? (...)
1214
+
1215
+ return (not_found && not_found.type !== ValueType.undefined) ? not_found : NAError();
1216
+
1217
+ },
1218
+ },
889
1219
 
1220
+ /**
1221
+ * copied from HLOOKUP, fix that one first
1222
+ */
1223
+ HLookup: {
1224
+ arguments: [...zlookup_arguments],
1225
+ fn: (value: any, table: any[][], col: number, inexact = true): UnionValue => {
1226
+ return ZLookup(value, table, col, inexact, true);
1227
+ },
1228
+ },
1229
+
1230
+ /**
1231
+ * FIXME: does not implement inexact matching (what's the algo for
1232
+ * that, anyway? nearest? price is right style? what about ties?)
1233
+ */
1234
+ VLookup: {
1235
+ arguments: [...zlookup_arguments],
1236
+ fn: (value: any, table: any[][], col: number, inexact = true): UnionValue => {
1237
+ return ZLookup(value, table, col, inexact, false);
890
1238
  },
891
1239
  },
892
1240
 
@@ -193,22 +193,52 @@ export const ApplyArrayFunc = (base: (...args: any[]) => any) => {
193
193
  };
194
194
  };
195
195
 
196
+ export const ApplyAsArraySwap = (base: (...args: any[]) => UnionValue) => {
197
+ return (...args: any[]): UnionValue => {
198
+
199
+ // swap here
200
+ args.reverse();
201
+ const [a, ...rest] = args;
202
+
203
+ if (Array.isArray(a)) {
204
+ return {
205
+ type: ValueType.array,
206
+ value: a.map(row => row.map((element: any) => {
207
+
208
+ // swap back
209
+ const swapped = [...rest, element];
210
+ return base(...swapped);
211
+
212
+ })),
213
+ };
214
+ }
215
+ else if (typeof a === 'object' && !!a && a.type === ValueType.array ) {
216
+ return {
217
+ type: ValueType.array,
218
+ value: (a as ArrayUnion).value.map(row => row.map((element: any) => {
219
+
220
+ const swapped = [...rest, element];
221
+ return base(...swapped);
222
+
223
+ })),
224
+ };
225
+
226
+ }
227
+ else {
228
+ return base(...rest, a);
229
+ }
230
+ }
231
+ };
232
+
196
233
  export const ApplyAsArray = (base: (a: any, ...rest: any[]) => UnionValue) => {
197
234
  return (a: any, ...rest: any[]): UnionValue => {
198
235
  if (Array.isArray(a)) {
199
-
200
236
  return {
201
237
  type: ValueType.array,
202
238
  value: a.map(row => row.map((element: any) => {
203
239
  return base(element, ...rest);
204
240
  })),
205
241
  };
206
-
207
- /*
208
- return a.map(row => row.map((element: any) => {
209
- return base(element, ...rest);
210
- }));
211
- */
212
242
  }
213
243
  else if (typeof a === 'object' && !!a && a.type === ValueType.array ) {
214
244
  return {
@@ -280,23 +310,6 @@ export const ApplyAsArray2 = (base: (a: any, b: any, ...rest: any[]) => UnionVal
280
310
  };
281
311
  }
282
312
 
283
- /*
284
- if (Array.isArray(a)) {
285
- if (Array.isArray(b)) {
286
- return a.map((row, i: number) => row.map((element: any, j: number) => {
287
- return base(element, b[i][j], ...rest);
288
- }));
289
- }
290
- else {
291
- return a.map(row => row.map((element: any) => {
292
- return base(element, b, ...rest);
293
- }));
294
- }
295
- }
296
- else {
297
- return base(a, b, ...rest);
298
- }
299
- */
300
313
  return base(a, b, ...rest);
301
314
 
302
315
  }