@trebco/treb 30.1.8 → 30.2.10

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 (35) hide show
  1. package/dist/languages/treb-i18n-da.mjs +1 -0
  2. package/dist/languages/treb-i18n-de.mjs +1 -0
  3. package/dist/languages/treb-i18n-es.mjs +1 -0
  4. package/dist/languages/treb-i18n-fr.mjs +1 -0
  5. package/dist/languages/treb-i18n-it.mjs +1 -0
  6. package/dist/languages/treb-i18n-nl.mjs +1 -0
  7. package/dist/languages/treb-i18n-no.mjs +1 -0
  8. package/dist/languages/treb-i18n-pl.mjs +1 -0
  9. package/dist/languages/treb-i18n-pt.mjs +1 -0
  10. package/dist/languages/treb-i18n-sv.mjs +1 -0
  11. package/dist/treb-spreadsheet-light.mjs +11 -11
  12. package/dist/treb-spreadsheet.mjs +11 -11
  13. package/dist/treb.d.ts +15 -1
  14. package/esbuild-custom-element.mjs +23 -1
  15. package/esbuild-utils.mjs +1 -0
  16. package/package.json +1 -1
  17. package/treb-calculator/src/calculator.ts +41 -0
  18. package/treb-calculator/src/expression-calculator.ts +40 -32
  19. package/treb-calculator/src/functions/base-functions.ts +164 -1
  20. package/treb-calculator/src/functions/statistics-functions.ts +134 -0
  21. package/treb-calculator/src/functions/text-functions.ts +45 -0
  22. package/treb-calculator/src/utilities.ts +5 -1
  23. package/treb-data-model/src/data_model.ts +227 -2
  24. package/treb-data-model/src/index.ts +1 -0
  25. package/{treb-embed → treb-data-model}/src/language-model.ts +3 -0
  26. package/treb-data-model/src/sheet.ts +0 -13
  27. package/treb-embed/src/embedded-spreadsheet.ts +84 -25
  28. package/treb-embed/src/options.ts +18 -0
  29. package/treb-embed/src/plugin.ts +31 -0
  30. package/treb-grid/src/editors/autocomplete_matcher.ts +8 -1
  31. package/treb-grid/src/render/tile_renderer.ts +14 -0
  32. package/treb-grid/src/types/grid.ts +25 -139
  33. package/treb-parser/src/parser-types.ts +12 -0
  34. package/treb-parser/src/parser.ts +43 -2
  35. package/tsproject.json +1 -1
package/dist/treb.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v30.1. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v30.2. 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
@@ -282,6 +282,17 @@ export interface EmbeddedSpreadsheetOptions {
282
282
  * starting in 30.1.0. set to false to disable.
283
283
  */
284
284
  spill?: boolean;
285
+
286
+ /**
287
+ * language. at the moment this controls spreadsheet function names
288
+ * only; the plan is to expand to the rest of the interface over time.
289
+ * should be an ISO 639-1 language code, like "en", "fr" or "sv" (case
290
+ * insensitive). we only support a limited subset of languages at the
291
+ * moment.
292
+ *
293
+ * leave blank or set to "locale" to use the current locale.
294
+ */
295
+ language?: string;
285
296
  }
286
297
 
287
298
  /**
@@ -393,6 +404,9 @@ export declare class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
393
404
  */
394
405
  ExternalEditor(config?: Partial<ExternalEditorConfig>): void;
395
406
 
407
+ /** dynamically load language module */
408
+ LoadLanguage(language?: string): Promise<void>;
409
+
396
410
  /**
397
411
  * Use this function to batch multiple document changes. Essentially the
398
412
  * grid stops broadcasting events for the duration of the function call,
@@ -5,7 +5,7 @@ import * as esbuild from 'esbuild';
5
5
  import { SassPlugin, WorkerPlugin, NotifyPlugin, HTMLPlugin } from './esbuild-utils.mjs';
6
6
  import { promises as fs } from 'fs';
7
7
 
8
- import pkg from './package.json' assert { type: 'json' };
8
+ import pkg from './package.json' with { type: 'json' };
9
9
 
10
10
 
11
11
  /**
@@ -58,6 +58,26 @@ for (let i = 0; i < process.argv.length; i++) {
58
58
  }
59
59
  }
60
60
 
61
+ /**
62
+ * thanks to
63
+ * https://github.com/evanw/esbuild/issues/3337#issuecomment-2085394950
64
+ */
65
+ function RewriteIgnoredImports() {
66
+ return {
67
+ name: 'RewriteIgnoredImports',
68
+ setup(build) {
69
+ build.onEnd(async (result) => {
70
+ if (result.outputFiles) {
71
+ for (const file of result.outputFiles) {
72
+ const { path, text } = file;
73
+ await fs.writeFile(path, text.replace(/esbuild-ignore-import:/, ''));
74
+ }
75
+ }
76
+ });
77
+ },
78
+ };
79
+ }
80
+
61
81
  /** @type esbuild.BuildOptions */
62
82
  const build_options = {
63
83
  entryPoints: [
@@ -78,7 +98,9 @@ const build_options = {
78
98
  'process.env.BUILD_VERSION': `"${pkg.version}"`,
79
99
  'process.env.BUILD_NAME': `"${pkg.name}"`,
80
100
  },
101
+ write: false,
81
102
  plugins: [
103
+ RewriteIgnoredImports(),
82
104
  NotifyPlugin(),
83
105
  WorkerPlugin(options),
84
106
  HTMLPlugin(options),
package/esbuild-utils.mjs CHANGED
@@ -198,6 +198,7 @@ export const WorkerPlugin = (options) => ({
198
198
  }
199
199
  });
200
200
 
201
+
201
202
  /**
202
203
  * @function
203
204
  * @param {{verbose?: boolean, minify?: boolean}} [options]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "30.1.8",
3
+ "version": "30.2.10",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -33,6 +33,7 @@ import type { CalculationResult } from './dag/spreadsheet_vertex_base';
33
33
 
34
34
  import { ExpressionCalculator, UnionIsMetadata } from './expression-calculator';
35
35
  import * as Utilities from './utilities';
36
+ import { StringUnion } from './utilities';
36
37
 
37
38
  import { FunctionLibrary } from './function-library';
38
39
  import type { FunctionMap } from './descriptors';
@@ -567,6 +568,46 @@ export class Calculator extends Graph {
567
568
  },
568
569
  },
569
570
 
571
+ Address: {
572
+ arguments: [
573
+ { name: 'row' },
574
+ { name: 'column' },
575
+ { name: 'absolute' },
576
+ { name: 'a1' },
577
+ { name: 'sheet name'}
578
+ ],
579
+ fn: (row = 1, column = 1, absolute = 1, a1 = true, sheet_name?: string): UnionValue => {
580
+
581
+ const address: UnitAddress = {
582
+ type: 'address',
583
+ id: 0, position: 0, label: '',
584
+ row: row-1,
585
+ column: column-1,
586
+ };
587
+
588
+ switch (absolute) {
589
+ case 2:
590
+ address.absolute_row = true;
591
+ break;
592
+ case 3:
593
+ address.absolute_column = true;
594
+ break;
595
+ case 4:
596
+ break;
597
+ default:
598
+ address.absolute_column = true;
599
+ address.absolute_row = true;
600
+ }
601
+
602
+ if (sheet_name) {
603
+ address.sheet = sheet_name;
604
+ }
605
+
606
+ return StringUnion(this.parser.Render(address, { r1c1: !a1 }));
607
+
608
+ },
609
+ },
610
+
570
611
  /**
571
612
  * anything I said about COUNTIF applies here, but worse.
572
613
  * COUNTIFS is an AND operation across separate COUNTIFs.
@@ -733,6 +733,41 @@ export class ExpressionCalculator {
733
733
 
734
734
  }
735
735
 
736
+ /**
737
+ * convert expr to a cell address, possibly calculating the contents.
738
+ * returns single address only (ranges with len > 1 will fail)
739
+ */
740
+ protected AsReference(expr: ExpressionUnit): ICellAddress|undefined {
741
+
742
+ switch (expr.type) {
743
+ case 'address':
744
+ return expr;
745
+
746
+ case 'range':
747
+ if (expr.start.row === expr.end.row && expr.start.column === expr.end.column) {
748
+ return expr.start;
749
+ }
750
+ break;
751
+
752
+ default:
753
+ {
754
+ const union = this.CalculateExpression(expr as ExtendedExpressionUnit, true);
755
+ if (UnionIsExpressionUnit(union)) {
756
+ if (union.value.type === 'address') {
757
+ return union.value;
758
+ }
759
+ if (union.value.type === 'range' && union.value.start.row === union.value.end.row && union.value.start.column === union.value.end.column) {
760
+ return union.value.start;
761
+ }
762
+ }
763
+ }
764
+ break;
765
+ }
766
+
767
+ return undefined;
768
+
769
+ }
770
+
736
771
  protected BinaryExpression(x: UnitBinary): (expr: UnitBinary) => UnionValue /*UnionOrArray*/ {
737
772
 
738
773
  // we are constructing and caching functions for binary expressions.
@@ -758,40 +793,13 @@ export class ExpressionCalculator {
758
793
  if (x.operator === ':') {
759
794
  return (expr: UnitBinary) => {
760
795
 
761
- const left = this.CalculateExpression(expr.left as ExtendedExpressionUnit, true);
762
- const right = this.CalculateExpression(expr.right as ExtendedExpressionUnit, true);
763
-
764
- let start: UnitAddress|undefined;
765
- let end: UnitAddress|undefined;
766
-
767
- // console.info({expr, left, right});
768
-
769
- if (UnionIsExpressionUnit(left) && UnionIsExpressionUnit(right)) {
770
-
771
- if (left.value.type === 'range') {
772
- if (left.value.start.row === left.value.end.row && left.value.start.column === left.value.end.column) {
773
- start = left.value.start;
774
- }
775
- }
776
- else if (left.value.type === 'address') {
777
- start = left.value;
778
- }
779
-
780
- if (right.value.type === 'range') {
781
- if (right.value.start.row === right.value.end.row && right.value.start.column === right.value.end.column) {
782
- end = right.value.start;
783
- }
784
- }
785
- else if (right.value.type === 'address') {
786
- end = right.value;
787
- }
788
-
789
- if (start && end) {
790
- return this.CellFunction4(start, end);
791
- }
796
+ const start = this.AsReference(expr.left);
797
+ const end = this.AsReference(expr.right);
792
798
 
799
+ if (start && end) {
800
+ return this.CellFunction4(start, end);
793
801
  }
794
-
802
+
795
803
  return ExpressionError();
796
804
  };
797
805
  }
@@ -21,9 +21,12 @@
21
21
 
22
22
  import type { FunctionMap, IntrinsicValue } from '../descriptors';
23
23
  import * as Utils from '../utilities';
24
+ // import { StringUnion, NumberUnion } from '../utilities';
24
25
  import { ReferenceError, NotImplError, NAError, ArgumentError, DivideByZeroError, ValueError } from '../function-error';
25
26
  import type { UnionValue,
26
- RenderFunctionResult, RenderFunctionOptions, Complex, CellValue } from 'treb-base-types';
27
+ RenderFunctionResult, RenderFunctionOptions, Complex, CellValue,
28
+ // ICellAddress
29
+ } from 'treb-base-types';
27
30
  import { Box, ValueType, GetValueType, ComplexOrReal, IsComplex, Area } from 'treb-base-types';
28
31
  import { Sparkline } from './sparkline';
29
32
  import { LotusDate, UnlotusDate } from 'treb-format';
@@ -318,6 +321,15 @@ export const AltFunctionLibrary: FunctionMap = {
318
321
 
319
322
  export const BaseFunctionLibrary: FunctionMap = {
320
323
 
324
+ // not sure why there are functions for booleans, but there are
325
+ True: {
326
+ fn: () => ({ type: ValueType.boolean, value: true }),
327
+ },
328
+
329
+ False: {
330
+ fn: () => ({ type: ValueType.boolean, value: false }),
331
+ },
332
+
321
333
  Int: {
322
334
  fn: (value: number) => {
323
335
  return {type: ValueType.number, value: Math.floor(value) };
@@ -585,6 +597,37 @@ export const BaseFunctionLibrary: FunctionMap = {
585
597
  },
586
598
  },
587
599
 
600
+ IsErr: {
601
+ description: 'Checks if another cell contains an error',
602
+ arguments: [{ name: 'reference', allow_error: true, boxed: true }],
603
+ fn: (...args: UnionValue[]): UnionValue => {
604
+
605
+ const values = Utils.FlattenBoxed(args);
606
+ for (const value of values) {
607
+ if (value.type === ValueType.error && value.value !== 'N/A') {
608
+ return { type: ValueType.boolean, value: true };
609
+ }
610
+ }
611
+
612
+ /*
613
+ if (Array.isArray(ref)) {
614
+ const values = Utils.Flatten(ref) as UnionValue[];
615
+ for (const value of values) {
616
+ if (value.type === ValueType.error) {
617
+ return { type: ValueType.boolean, value: true };
618
+ }
619
+ }
620
+ }
621
+ else if (ref) {
622
+ return { type: ValueType.boolean, value: ref.type === ValueType.error };
623
+ }
624
+ */
625
+
626
+ return { type: ValueType.boolean, value: false };
627
+
628
+ },
629
+ },
630
+
588
631
  IsError: {
589
632
  description: 'Checks if another cell contains an error',
590
633
  arguments: [{ name: 'reference', allow_error: true, boxed: true }],
@@ -1182,6 +1225,126 @@ export const BaseFunctionLibrary: FunctionMap = {
1182
1225
  */
1183
1226
 
1184
1227
 
1228
+ Row: {
1229
+ arguments: [{ name: 'reference', metadata: true }],
1230
+ fn: (ref: UnionValue): UnionValue => {
1231
+ if (ref.type === ValueType.array) {
1232
+ const arr = ref.value;
1233
+ const first = arr[0][0];
1234
+
1235
+ if (UnionIsMetadata(first)) {
1236
+ return {
1237
+ type: ValueType.array,
1238
+ value: [arr[0].map((row, index) => ({
1239
+ type: ValueType.number,
1240
+ value: index + first.value.address.row + 1
1241
+ }))],
1242
+ };
1243
+ }
1244
+
1245
+ }
1246
+ else if (UnionIsMetadata(ref)) {
1247
+ return {
1248
+ type: ValueType.number, value: ref.value.address.row + 1,
1249
+ }
1250
+ }
1251
+ return ArgumentError();
1252
+ },
1253
+ },
1254
+
1255
+ Column: {
1256
+ arguments: [{ name: 'reference', metadata: true }],
1257
+ fn: (ref: UnionValue): UnionValue => {
1258
+ if (ref.type === ValueType.array) {
1259
+ const arr = ref.value;
1260
+ const first = arr[0][0];
1261
+
1262
+ if (UnionIsMetadata(first)) {
1263
+ return {
1264
+ type: ValueType.array,
1265
+ value: arr.map((row, index) => [{
1266
+ type: ValueType.number,
1267
+ value: index + first.value.address.column + 1
1268
+ }]),
1269
+ };
1270
+ }
1271
+
1272
+ }
1273
+ else if (UnionIsMetadata(ref)) {
1274
+ return {
1275
+ type: ValueType.number, value: ref.value.address.row + 1,
1276
+ }
1277
+ }
1278
+ return ArgumentError();
1279
+ },
1280
+ },
1281
+
1282
+ Choose: {
1283
+ arguments: [
1284
+ { name: 'Selected index', },
1285
+ { name: 'Choice 1...', metadata: true },
1286
+ ],
1287
+ return_type: 'reference',
1288
+ description: 'Returns one of a list of choices',
1289
+ fn: (selected: number, ...choices: UnionValue[]): UnionValue => {
1290
+
1291
+ if (selected < 1 || selected > choices.length) {
1292
+ return ValueError();
1293
+ }
1294
+
1295
+ const value = choices[selected - 1];
1296
+
1297
+ // this should be metadata. is there a different object we
1298
+ // might run into? maybe we should refactor how metadata works
1299
+
1300
+ if (UnionIsMetadata(value)) {
1301
+ return {
1302
+ type: ValueType.object,
1303
+ value: value.value.address,
1304
+ }
1305
+ }
1306
+
1307
+ // check if array is metadata. if it's a literal array
1308
+ // we just want to return it.
1309
+
1310
+ if (value.type === ValueType.array) {
1311
+ const arr = value.value;
1312
+ const rows = arr.length;
1313
+ const cols = arr[0].length;
1314
+ const first = arr[0][0];
1315
+ const last = arr[rows - 1][cols - 1];
1316
+
1317
+ if (rows === 1 && cols === 1) {
1318
+ if (UnionIsMetadata(first)) {
1319
+ return {
1320
+ type: ValueType.object,
1321
+ value: first.value.address,
1322
+ };
1323
+ }
1324
+ }
1325
+ else {
1326
+ if (UnionIsMetadata(first) && UnionIsMetadata(last)) {
1327
+ return {
1328
+ type: ValueType.object,
1329
+ value: {
1330
+ type: 'range',
1331
+ position: 0, id: 0, label: '',
1332
+ start: first.value.address,
1333
+ end: last.value.address,
1334
+ }
1335
+ }
1336
+ }
1337
+
1338
+ }
1339
+ }
1340
+
1341
+ return {
1342
+ ...value, // should we deep-copy in case of an array?
1343
+ };
1344
+
1345
+ },
1346
+ },
1347
+
1185
1348
  /*
1186
1349
  * rewrite of xlookup to return a reference. better compatibility.
1187
1350
  * ---
@@ -260,6 +260,140 @@ export const StatisticsFunctionLibrary: FunctionMap = {
260
260
  },
261
261
  },
262
262
 
263
+ LCM: {
264
+ description: 'Retuns the least common mulitple of the arguments',
265
+ arguments: [{ boxed: true }],
266
+ fn: (...args: UnionValue[]): UnionValue => {
267
+ args = Utils.FlattenBoxed(args);
268
+ const list: number[] = [];
269
+
270
+ for (const ref of args as UnionValue[]) {
271
+ if (ref.type === ValueType.error) {
272
+ return ref;
273
+ }
274
+ if (ref.type === ValueType.number) {
275
+ list.push(ref.value);
276
+ }
277
+ else if (ref.type !== ValueType.undefined ) {
278
+ console.info("RT", ref.type);
279
+ return ArgumentError();
280
+ }
281
+ }
282
+
283
+ const gcd = (x: number, y: number): number => {
284
+ if (x % y == 0) {
285
+ return y; // Base case
286
+ }
287
+ else {
288
+ return gcd(y, x % y); // Recusrsive case
289
+ }
290
+ };
291
+
292
+ if (list.length === 0) {
293
+ return { type: ValueType.number, value: 0 };
294
+ }
295
+
296
+ let lcm = list[0];
297
+
298
+ for (let i = 1; i < list.length; i++) {
299
+ lcm = (lcm * list[i]) / gcd(lcm, list[i]);
300
+ }
301
+
302
+ return {
303
+ type: ValueType.number,
304
+ value: lcm,
305
+ }
306
+
307
+ },
308
+ },
309
+
310
+ GCD: {
311
+ description: 'Finds the greatest common divisor of the arguments',
312
+ arguments: [{ boxed: true }],
313
+ fn: (...args: UnionValue[]): UnionValue => {
314
+ args = Utils.FlattenBoxed(args);
315
+ const list: number[] = [];
316
+
317
+ for (const ref of args as UnionValue[]) {
318
+ if (ref.type === ValueType.error) {
319
+ return ref;
320
+ }
321
+ if (ref.type === ValueType.number) {
322
+ list.push(ref.value);
323
+ }
324
+ else if (ref.type !== ValueType.undefined ) {
325
+ console.info("RT", ref.type);
326
+ return ArgumentError();
327
+ }
328
+ }
329
+
330
+ const gcd = (x: number, y: number): number => {
331
+ if (x % y == 0) {
332
+ return y; // Base case
333
+ }
334
+ else {
335
+ return gcd(y, x % y); // Recusrsive case
336
+ }
337
+ };
338
+
339
+ if (list.length === 0) {
340
+ return ValueError();
341
+ }
342
+
343
+ if (list.length === 1) {
344
+ return {
345
+ type: ValueType.number,
346
+ value: list[0],
347
+ };
348
+ }
349
+
350
+ let value = list[0];
351
+ for (let i = 1; i < list.length; i++) {
352
+ value = gcd(value, list[i]);
353
+ }
354
+
355
+ return {
356
+ type: ValueType.number,
357
+ value,
358
+ };
359
+
360
+
361
+ }
362
+ },
363
+
364
+ HarMean: {
365
+ description: 'Returns the harmonic mean of the arguments',
366
+ arguments: [{ boxed: true }],
367
+ fn: (...args: UnionValue[]): UnionValue => {
368
+ args = Utils.FlattenBoxed(args);
369
+ const result = { real: 0, imaginary: 0 };
370
+ let count = 0;
371
+
372
+ for (const ref of args as UnionValue[]) {
373
+ if (ref.type === ValueType.error) {
374
+ return ref;
375
+ }
376
+ if (ref.type === ValueType.number) {
377
+ result.real += (1/ref.value);
378
+ count++;
379
+ }
380
+ if (ref.type === ValueType.complex) {
381
+ const reciprocal = ComplexMath.Divide({real: 1, imaginary: 0}, ref.value);
382
+ result.real += reciprocal.real;
383
+ result.imaginary += reciprocal.imaginary;
384
+ count++;
385
+ }
386
+ }
387
+
388
+ if (result.imaginary) {
389
+ return { type: ValueType.complex, value: ComplexMath.Divide({real: count, imaginary: 0}, result), };
390
+ }
391
+
392
+ return { type: ValueType.number, value: count/result.real };
393
+
394
+ },
395
+ },
396
+
263
397
  Average: {
264
398
 
265
399
  description: 'Returns the arithmetic mean of all numeric arguments',
@@ -120,6 +120,51 @@ export const TextFunctionLibrary: FunctionMap = {
120
120
  },
121
121
  },
122
122
 
123
+ Len: {
124
+ arguments: [
125
+ { name: 'string', unroll: true },
126
+ ],
127
+ fn: (str: string): UnionValue => {
128
+ return {
129
+ type: ValueType.number,
130
+ value: str.toString().length,
131
+ }
132
+ },
133
+ },
134
+
135
+ Substitute: {
136
+ arguments: [
137
+ { name: 'text' },
138
+ { name: 'search' },
139
+ { name: 'replacement' },
140
+ { name: 'index' },
141
+ ],
142
+ fn: (text: string, search: string, replacement: string, index?: number): UnionValue => {
143
+ if (typeof index === 'number') {
144
+ if (index < 1) {
145
+ return ValueError();
146
+ }
147
+
148
+ let counter = 1;
149
+ return {
150
+ type: ValueType.string,
151
+ value: text.replaceAll(search, (...args) => {
152
+ console.info(args);
153
+ return (counter++) === index ? replacement : search;
154
+ }),
155
+ };
156
+
157
+ }
158
+ else {
159
+ return {
160
+ type: ValueType.string,
161
+ value: text.replaceAll(search, replacement),
162
+ };
163
+ }
164
+
165
+ },
166
+ },
167
+
123
168
  Left: {
124
169
  arguments: [
125
170
  { name: 'string' },
@@ -352,4 +352,8 @@ export const ParseWildcards = (text: string): string => {
352
352
 
353
353
  return result.join('');
354
354
 
355
- };
355
+ };
356
+
357
+ export const StringUnion = (value: string): UnionValue => ({ type: ValueType.string, value });
358
+ export const NumberUnion = (value: number): UnionValue => ({ type: ValueType.number, value });
359
+