@trebco/treb 28.13.2 → 28.15.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.
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.0",
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.
@@ -803,90 +895,275 @@ export const BaseFunctionLibrary: FunctionMap = {
803
895
  },
804
896
  */
805
897
 
806
- /**
807
- * FIXME: does not implement inexact matching (what's the algo for
808
- * that, anyway? nearest? price is right style? what about ties?)
898
+ /*
899
+ * unsaid anywhere (that I can locate) aboud XLOOKUP is that lookup
900
+ * array must be one-dimensional. it can be either a row or a column,
901
+ * but one dimension must be one. that simplifies things quite a bit.
902
+ *
903
+ * there's a note in the docs about binary search over the data --
904
+ * that might explain how inexact VLOOKUP works as well. seems an odd
905
+ * choice but maybe back in the day it made sense
809
906
  */
810
- VLookup: {
811
-
907
+ XLOOKUP: {
812
908
  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
- },
909
+ { name: 'Lookup value', },
910
+ { name: 'Lookup array', },
911
+ { name: 'Return array', },
912
+ { name: 'Not found', boxed: true },
913
+ { name: 'Match mode', default: 0, },
914
+ { name: 'Search mode', default: 1, },
826
915
  ],
916
+ xlfn: true,
917
+ fn: (
918
+ lookup_value: any,
919
+ lookup_array: any[][],
920
+ return_array: any[][],
921
+ not_found?: UnionValue,
922
+ match_mode = 0,
923
+ search_mode = 1,
924
+ ): UnionValue => {
925
+
926
+ // FIXME: we could I suppose be more graceful about single values
927
+ // if passed instead of arrays
928
+
929
+ if (!Array.isArray(lookup_array)) {
930
+ console.info("lookup is not an array");
931
+ return ValueError();
932
+ }
827
933
 
828
- fn: (value: any, table: any[][], col: number, inexact = true): UnionValue => {
934
+ const first = lookup_array[0];
935
+ if (!Array.isArray(first)) {
936
+ console.info("lookip is not a 2d array");
937
+ return ValueError();
938
+ }
829
939
 
830
- col = Math.max(0, col - 1);
940
+ if (lookup_array.length !== 1 && first.length !== 1) {
941
+ console.info("lookup array has invalid dimensions");
942
+ return ValueError();
943
+ }
831
944
 
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) {
945
+ // FIXME: is it required that the return array be (at least) the
946
+ // same size? we can return undefineds, but maybe we should error
947
+
948
+ if (!Array.isArray(return_array)) {
949
+ console.info("return array is not an array");
950
+ return ValueError();
951
+ }
839
952
 
840
- let result: any = table[col][0];
953
+ let transpose = (lookup_array.length === 1);
954
+ if (transpose) {
955
+ lookup_array = Utils.TransposeArray(lookup_array);
956
+ return_array = Utils.TransposeArray(return_array);
957
+ }
841
958
 
842
- if (typeof value === 'number') {
959
+ // maybe reverse...
843
960
 
844
- let compare = Number(table[0][0]);
845
- if (isNaN(compare) || compare > value) {
846
- return NAError();
847
- }
961
+ if (search_mode < 0) {
962
+ lookup_array.reverse();
963
+ return_array.reverse();
964
+ }
965
+
966
+ //
967
+ // return value at index, transpose if necessary, and return
968
+ // an array. we might prefer to return a scalar if there's only
969
+ // one value, not sure what's the intended behavior
970
+ //
971
+ const ReturnIndex = (index: number): UnionValue => {
848
972
 
849
- for (let i = 1; i < table[0].length; i++) {
850
- compare = Number(table[0][i]);
851
- if (isNaN(compare) || compare > value) {
852
- break;
973
+ const values = return_array[index];
974
+
975
+ if (!values) {
976
+ return { type: ValueType.undefined };
977
+ }
978
+
979
+ if (!Array.isArray(values)) {
980
+ return Box(values);
981
+ }
982
+
983
+ let boxes = [values.map(value => Box(value))];
984
+
985
+ if (transpose) {
986
+ boxes = Utils.TransposeArray(boxes);
987
+ }
988
+
989
+ return {
990
+ type: ValueType.array,
991
+ value: boxes,
992
+ }
993
+
994
+ };
995
+
996
+ // if value is not a string, then we can ignore wildcards.
997
+ // in that case convert to exact match.
998
+
999
+ if (match_mode === 2 && typeof lookup_value !== 'string') {
1000
+ match_mode = 0;
1001
+ }
1002
+
1003
+ // what does inexact matching mean in this case if the lookup
1004
+ // value is a string or boolean? (...)
1005
+
1006
+ if ((match_mode === 1 || match_mode === -1) && typeof lookup_value === 'number') {
1007
+
1008
+ let min_delta = 0;
1009
+ let index = -1;
1010
+
1011
+ for (let i = 0; i < lookup_array.length; i++) {
1012
+ const value = lookup_array[i][0];
1013
+
1014
+
1015
+ if (typeof value === 'number') {
1016
+
1017
+ // check for exact match first, just in case
1018
+ if (value === lookup_value) {
1019
+ return ReturnIndex(i);
1020
+ }
1021
+
1022
+ const delta = Math.abs(value - lookup_value);
1023
+
1024
+ if ((match_mode === 1 && value > lookup_value) || (match_mode === -1 && value < lookup_value)){
1025
+ if (index < 0 || delta < min_delta) {
1026
+ min_delta = delta;
1027
+ index = i;
1028
+ }
853
1029
  }
854
- result = table[col][i];
855
1030
 
856
1031
  }
1032
+ }
857
1033
 
1034
+ if (index >= 0) {
1035
+ return ReturnIndex(index);
858
1036
  }
859
- else {
860
1037
 
861
- value = value.toLowerCase(); // ?
862
- let compare: string = (table[0][0] || '').toString().toLowerCase();
863
- if (compare.localeCompare(value) > 0) {
864
- return NAError();
865
- }
1038
+ }
1039
+
1040
+ switch (match_mode) {
1041
+
1042
+ case 2:
1043
+ {
1044
+ // wildcard string match. we only handle strings for
1045
+ // this case (see above).
1046
+
1047
+ const pattern = Utils.ParseWildcards(lookup_value);
1048
+ const regex = new RegExp('^' + pattern + '$', 'i'); //.exec(lookup_value);
866
1049
 
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;
1050
+ for (let i = 0; i < lookup_array.length; i++) {
1051
+ let value = lookup_array[i][0];
1052
+ if (typeof value === 'string' && regex.exec(value)) {
1053
+ return ReturnIndex(i);
1054
+ }
871
1055
  }
872
- result = table[col][i];
873
1056
 
874
1057
  }
1058
+ break;
875
1059
 
876
- }
1060
+ case 0:
1061
+ if (typeof lookup_value === 'string') {
1062
+ lookup_value = lookup_value.toLowerCase();
1063
+ }
1064
+ for (let i = 0; i < lookup_array.length; i++) {
1065
+ let value = lookup_array[i][0];
1066
+
1067
+ if (typeof value === 'string') {
1068
+ value = value.toLowerCase();
1069
+ }
1070
+ if (value === lookup_value) {
1071
+ return ReturnIndex(i);
1072
+ }
1073
+ }
877
1074
 
878
- return Box(result);
1075
+ break;
1076
+ }
1077
+
1078
+ /*
1079
+ const flat_lookup = Utils.FlattenUnboxed(lookup_array);
1080
+ const flat_return = Utils.FlattenUnboxed(return_array);
879
1081
 
1082
+ // maybe reverse...
1083
+
1084
+ if (search_mode < 0) {
1085
+ flat_lookup.reverse();
1086
+ flat_return.reverse();
880
1087
  }
881
- else {
882
- for (let i = 0; i < table[0].length; i++) {
883
- if (table[0][i] == value) { // ==
884
- return Box(table[col][i]);
1088
+
1089
+ // if value is not a string, then we can ignore wildcards.
1090
+ // in that case convert to exact match.
1091
+
1092
+ if (match_mode === 2 && typeof lookup_value !== 'string') {
1093
+ match_mode = 0;
1094
+ }
1095
+
1096
+ switch (match_mode) {
1097
+ case 2:
1098
+
1099
+ {
1100
+
1101
+ // wildcard string match. we only handle strings
1102
+ // for wildcard matching (handled above).
1103
+
1104
+ const pattern = Utils.ParseWildcards(lookup_value);
1105
+ const regex = new RegExp('^' + pattern + '$', 'i'); //.exec(lookup_value);
1106
+
1107
+ for (let i = 0; i < flat_lookup.length; i++) {
1108
+ let value = flat_lookup[i];
1109
+ if (typeof value === 'string' && regex.exec(value)) {
1110
+ return Box(flat_return[i]);
1111
+ }
1112
+ }
1113
+
1114
+
885
1115
  }
886
- }
887
- return NAError();
1116
+
1117
+ break;
1118
+
1119
+ case 0:
1120
+
1121
+ // return exact match or NA/default. in this case
1122
+ // "exact" means icase (but not wildcard)
1123
+
1124
+ if (typeof lookup_value === 'string') {
1125
+ lookup_value = lookup_value.toLowerCase();
1126
+ }
1127
+ for (let i = 0; i < flat_lookup.length; i++) {
1128
+ let value = flat_lookup[i];
1129
+ if (typeof value === 'string') {
1130
+ value = value.toLowerCase();
1131
+ }
1132
+ if (value === lookup_value) {
1133
+ return Box(flat_return[i]);
1134
+ }
1135
+ }
1136
+
1137
+ break;
888
1138
  }
1139
+ */
1140
+
1141
+ // FIXME: if we're expecting to return an array maybe we should
1142
+ // pack it up as an array? if it's not already an array? (...)
1143
+
1144
+ return (not_found && not_found.type !== ValueType.undefined) ? not_found : NAError();
1145
+
1146
+ },
1147
+ },
889
1148
 
1149
+ /**
1150
+ * copied from HLOOKUP, fix that one first
1151
+ */
1152
+ HLookup: {
1153
+ arguments: [...zlookup_arguments],
1154
+ fn: (value: any, table: any[][], col: number, inexact = true): UnionValue => {
1155
+ return ZLookup(value, table, col, inexact, true);
1156
+ },
1157
+ },
1158
+
1159
+ /**
1160
+ * FIXME: does not implement inexact matching (what's the algo for
1161
+ * that, anyway? nearest? price is right style? what about ties?)
1162
+ */
1163
+ VLookup: {
1164
+ arguments: [...zlookup_arguments],
1165
+ fn: (value: any, table: any[][], col: number, inexact = true): UnionValue => {
1166
+ return ZLookup(value, table, col, inexact, false);
890
1167
  },
891
1168
  },
892
1169
 
@@ -38,7 +38,7 @@
38
38
  <div data-bind='message' class='treb-embed-dialog-message'></div>
39
39
  <div data-bind='about' class='treb-embed-dialog-body'></div>
40
40
  </div>
41
- <button type='button' title='Close dialog' data-bind='close' class='treb-close-box'>
41
+ <button type='button' data-title='close_dialog' data-bind='close' class='treb-close-box'>
42
42
  <svg viewBox='0 0 16 16'>
43
43
  <path d='M11.854 4.146a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708-.708l7-7a.5.5 0 0 1 .708 0z'/>
44
44
  <path d='M4.146 4.146a.5.5 0 0 0 0 .708l7 7a.5.5 0 0 0 .708-.708l-7-7a.5.5 0 0 0-.708 0z'/>
@@ -62,6 +62,9 @@
62
62
 
63
63
  <div class="treb-formula-bar notranslate" hidden>
64
64
  <div class="treb-address-label"><div></div></div>
65
+ <button class="treb-insert-function-button"
66
+ data-title="insert_function"
67
+ data-conditional="insert-function">𝑓<small>(x)</small></button>
65
68
  <div class="treb-editor-container">
66
69
  <div contenteditable="true"></div>
67
70
  </div>
@@ -95,7 +98,7 @@
95
98
  -->
96
99
 
97
100
  <!-- converted to button, more appropriate -->
98
- <button class="treb-delete-tab" title="Delete current sheet" data-command="delete-tab" data-conditional="delete-tab">
101
+ <button class="treb-delete-tab" data-title="delete_sheet" data-command="delete-tab" data-conditional="delete-tab">
99
102
  <svg tabindex="-1" viewbox='0 0 16 16'><path d='M4,4 L12,12 M12,4 L4,12'/></svg>
100
103
  </button>
101
104
 
@@ -105,7 +108,8 @@
105
108
  </div>
106
109
 
107
110
  <!-- converted to button, more appropriate -->
108
- <button class="treb-add-tab" data-command="add-tab" data-conditional="add-tab" title="Add sheet">+</button>
111
+ <button class="treb-add-tab" data-command="add-tab" data-conditional="add-tab"
112
+ data-title="add_sheet">+</button>
109
113
 
110
114
  <!--
111
115
  we removed the junk node with "flex grow" to split the layout, in
@@ -125,7 +129,8 @@
125
129
 
126
130
  <div class="treb-revert-indicator"
127
131
  data-command="revert-indicator"
128
- title="This document has been modified from the original version."></div>
132
+ data-title="document_modified"
133
+ ></div>
129
134
 
130
135
  </div> <!-- /treb-view -->
131
136
  </template>
@@ -133,13 +138,13 @@
133
138
  </div>
134
139
 
135
140
  <div class="treb-layout-sidebar treb-animate">
136
- <button data-command="recalculate" title="Recalculate"></button>
137
- <button data-command="toggle-toolbar" data-conditional="toolbar" title="Toggle toolbar"></button>
138
- <button data-command="export-xlsx" data-conditional="export" title="Export as XLSX"></button>
139
- <button data-command="revert" data-conditional="revert" title="Revert to original version"></button>
140
- <button data-command="about" title="What's this?"></button>
141
+ <button data-command="recalculate" data-title="recalculate"></button>
142
+ <button data-command="toggle-toolbar" data-conditional="toolbar" data-title="toggle_toolbar"></button>
143
+ <button data-command="export-xlsx" data-conditional="export" data-title="export"></button>
144
+ <button data-command="revert" data-conditional="revert" data-title="revert"></button>
145
+ <button data-command="about" data-title="about"></button>
141
146
  </div>
142
147
 
143
- <button class="treb-toggle-sidebar-button" title="Toggle sidebar"></button>
148
+ <button class="treb-toggle-sidebar-button" data-title="toggle_sidebar"></button>
144
149
 
145
150
  </div>
@@ -49,12 +49,12 @@
49
49
 
50
50
  <div class="group">
51
51
  <button data-command="wrap-text" title="Wrap text"></button>
52
- <button data-command="merge-cells" data-id="merge" data-title="Merge cells" data-active-title="Unmerge cells"></button>
53
- <button data-command="lock-cells" data-title="Lock cells" data-active-title="Unlock cells"></button>
54
- <button data-command="freeze-panes" data-title="Freeze panes" data-active-title="Unfreeze panes" freeze-button></button>
55
- <button data-command="insert-table" data-icon="table" data-title="Insert table" data-active-title="Remove table" table-button></button>
52
+ <button data-command="merge-cells" data-id="merge" data-inactive-title="Merge cells" data-active-title="Unmerge cells"></button>
53
+ <button data-command="lock-cells" data-inactive-title="Lock cells" data-active-title="Unlock cells"></button>
54
+ <button data-command="freeze-panes" data-inactive-title="Freeze panes" data-active-title="Unfreeze panes" freeze-button></button>
55
+ <button data-command="insert-table" data-icon="table" data-inactive-title="Insert table" data-active-title="Remove table" table-button></button>
56
56
  <div class="treb-menu">
57
- <button data-icon="comment" data-title="Comment" data-active-title="Update comment"></button>
57
+ <button data-icon="comment" data-inactive-title="Comment" data-active-title="Update comment"></button>
58
58
  <div class="treb-comment-box">
59
59
  <textarea></textarea>
60
60
  <div>