@trebco/treb 28.11.1 → 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.
Files changed (37) hide show
  1. package/dist/treb-spreadsheet-light.mjs +11 -11
  2. package/dist/treb-spreadsheet.mjs +11 -11
  3. package/dist/treb.d.ts +27 -3
  4. package/package.json +1 -1
  5. package/treb-base-types/src/style.ts +3 -0
  6. package/treb-calculator/src/calculator.ts +235 -68
  7. package/treb-calculator/src/descriptors.ts +5 -0
  8. package/treb-calculator/src/expression-calculator.ts +9 -5
  9. package/treb-calculator/src/functions/base-functions.ts +410 -21
  10. package/treb-calculator/src/functions/text-functions.ts +45 -55
  11. package/treb-calculator/src/primitives.ts +11 -0
  12. package/treb-calculator/src/utilities.ts +55 -0
  13. package/treb-embed/markup/layout.html +15 -10
  14. package/treb-embed/markup/toolbar.html +5 -5
  15. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +38 -2
  16. package/treb-embed/src/embedded-spreadsheet.ts +227 -29
  17. package/treb-embed/src/options.ts +5 -0
  18. package/treb-embed/style/dark-theme.scss +1 -0
  19. package/treb-embed/style/formula-bar.scss +20 -7
  20. package/treb-embed/style/theme-defaults.scss +20 -0
  21. package/treb-export/src/export-worker/export-worker.ts +1 -0
  22. package/treb-export/src/export2.ts +6 -1
  23. package/treb-export/src/import2.ts +76 -6
  24. package/treb-export/src/shared-strings2.ts +1 -1
  25. package/treb-export/src/workbook-style2.ts +89 -52
  26. package/treb-export/src/workbook2.ts +119 -1
  27. package/treb-grid/src/editors/editor.ts +7 -0
  28. package/treb-grid/src/editors/formula_bar.ts +23 -1
  29. package/treb-grid/src/render/tile_renderer.ts +46 -3
  30. package/treb-grid/src/types/annotation.ts +17 -3
  31. package/treb-grid/src/types/grid.ts +28 -9
  32. package/treb-grid/src/types/grid_base.ts +10 -105
  33. package/treb-grid/src/types/grid_options.ts +3 -2
  34. package/treb-grid/src/types/named_range.ts +8 -1
  35. package/treb-grid/src/types/serialize_options.ts +5 -0
  36. package/treb-parser/src/parser-types.ts +27 -4
  37. package/treb-parser/src/parser.ts +74 -36
package/dist/treb.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v28.11. 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
@@ -756,6 +756,9 @@ export declare class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
756
756
  /**
757
757
  * Create a macro function.
758
758
  *
759
+ * FIXME: this needs a control for argument separator, like other
760
+ * functions that use formulas (@see SetRange)
761
+ *
759
762
  * @public
760
763
  */
761
764
  DefineFunction(name: string, argument_names?: string | string[], function_def?: string): void;
@@ -1074,7 +1077,7 @@ export interface FreezePane {
1074
1077
  rows: number;
1075
1078
  columns: number;
1076
1079
  }
1077
- export type AnnotationType = 'treb-chart' | 'image' | 'external';
1080
+ export type AnnotationType = 'treb-chart' | 'image' | 'textbox' | 'external';
1078
1081
  export declare type BorderConstants = "none" | "all" | "outside" | "top" | "bottom" | "left" | "right";
1079
1082
 
1080
1083
  /**
@@ -1105,6 +1108,11 @@ export interface SerializeOptions {
1105
1108
 
1106
1109
  /** share resources (images, for now) to prevent writing data URIs more than once */
1107
1110
  share_resources?: boolean;
1111
+
1112
+ /**
1113
+ * if a function has an export() handler, call that
1114
+ */
1115
+ export_functions?: boolean;
1108
1116
  }
1109
1117
 
1110
1118
  /**
@@ -1266,6 +1274,9 @@ export interface CellStyle {
1266
1274
  /** border color */
1267
1275
  border_bottom_fill?: Color;
1268
1276
 
1277
+ /** text indent */
1278
+ indent?: number;
1279
+
1269
1280
  /**
1270
1281
  * cell is locked for editing
1271
1282
  */
@@ -1797,7 +1808,7 @@ export interface SerializedGridSelection {
1797
1808
  /** for cacheing addtional selections. optimally don't serialize */
1798
1809
  rendered?: boolean;
1799
1810
  }
1800
- export type AnnotationData = AnnotationChartData | AnnotationImageData | AnnotationExternalData;
1811
+ export type AnnotationData = AnnotationChartData | AnnotationImageData | AnnotationExternalData | AnnotationTextBoxData;
1801
1812
  export interface ImageSize {
1802
1813
  width: number;
1803
1814
  height: number;
@@ -1880,6 +1891,19 @@ export interface AnnotationImageData extends AnnotationDataBase {
1880
1891
  export interface AnnotationChartData extends AnnotationDataBase {
1881
1892
  type: 'treb-chart';
1882
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
+ }
1883
1907
  export interface AnnotationExternalData extends AnnotationDataBase {
1884
1908
  type: 'external';
1885
1909
  data: Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "28.11.1",
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
  *
@@ -23,7 +23,7 @@ import type { Cell, ICellAddress, ICellAddress2, UnionValue, EvaluateOptions,
23
23
  ArrayUnion, IArea, CellDataWithAddress, CellValue} from 'treb-base-types';
24
24
  import { Localization, Area, ValueType, IsCellAddress} from 'treb-base-types';
25
25
 
26
- import type { ExpressionUnit, DependencyList, UnitRange, UnitAddress, UnitIdentifier } from 'treb-parser';
26
+ import type { ExpressionUnit, DependencyList, UnitRange, UnitAddress, UnitIdentifier, ParseResult } from 'treb-parser';
27
27
  import { Parser,
28
28
  DecimalMarkType, ArgumentSeparatorType, QuotedSheetNameRegex } from 'treb-parser';
29
29
 
@@ -57,6 +57,8 @@ import { StateLeafVertex } from './dag/state_leaf_vertex';
57
57
  import { CalculationLeafVertex } from './dag/calculation_leaf_vertex';
58
58
  import type { ConnectedElementType } from 'treb-grid';
59
59
 
60
+ import { ValueParser } from 'treb-format';
61
+
60
62
  /**
61
63
  * breaking this out so we can use it for export (TODO)
62
64
  *
@@ -241,6 +243,160 @@ export class Calculator extends Graph {
241
243
  }
242
244
 
243
245
  // special functions... need reference to the graph (this)
246
+ // moving countif here so we can reference it in COUNTIFS...
247
+
248
+ const FlattenBooleans = (value: ArrayUnion) => {
249
+ const result: boolean[] = [];
250
+ for (const col of value.value) {
251
+ for (const entry of col) {
252
+ result.push(entry.type === ValueType.boolean && entry.value);
253
+ }
254
+ }
255
+ return result;
256
+ };
257
+
258
+ const CountIfInternal = (range: any, criteria: any): UnionValue => {
259
+
260
+ // do we really need parser/calculator for this? I think
261
+ // we've maybe gone overboard here, could we just use valueparser
262
+ // on the criteria and then calculate normally? I think we might...
263
+ // in any event there are no dynamic dependencies with this
264
+ // function.
265
+
266
+ const data = Utilities.FlattenUnboxed(range);
267
+
268
+ let parse_result: ParseResult|undefined;
269
+ let expression: ExpressionUnit|undefined;
270
+
271
+ // we'll handle operator and operand separately
272
+
273
+ let operator = '=';
274
+
275
+ // handle wildcards first. if we have a wildcard we use a
276
+ // matching function so we can centralize.
277
+
278
+ if (typeof criteria === 'string') {
279
+
280
+ // normalize first, pull out operator
281
+
282
+ criteria = criteria.trim();
283
+ const match = criteria.match(/^([=<>]+)/);
284
+ if (match) {
285
+ operator = match[1];
286
+ criteria = criteria.substring(operator.length);
287
+ }
288
+
289
+ const value_parser_result = ValueParser.TryParse(criteria);
290
+ if (value_parser_result?.type === ValueType.string) {
291
+ criteria = `"${value_parser_result.value}"`;
292
+ }
293
+ else {
294
+ criteria = value_parser_result?.value?.toString() || '';
295
+ }
296
+
297
+ // console.info({operator, criteria});
298
+
299
+ // check for wildcards (this will false-positive on escaped
300
+ // wildcards, which will not break but will waste cycles. we
301
+ // could check. TOOD/FIXME)
302
+
303
+ if (/[?*]/.test(criteria)) {
304
+
305
+ // NOTE: we're not specifying an argument separator when writing
306
+ // functions, because that might break numbers passed as strings.
307
+ // so we write the function based on the current separator.
308
+
309
+ const separator = this.parser.argument_separator;
310
+
311
+ if (operator === '=' || operator === '<>') {
312
+
313
+ parse_result = this.parser.Parse(`=WildcardMatch({}${separator} ${criteria}${separator} ${operator === '<>'})`);
314
+ expression = parse_result.expression;
315
+
316
+ if (parse_result.error || !expression) {
317
+ return ExpressionError();
318
+ }
319
+
320
+ if (expression?.type === 'call' && expression.args[0]?.type === 'array') {
321
+ expression.args[0].values = [data];
322
+ }
323
+
324
+ }
325
+
326
+ }
327
+
328
+ }
329
+ else {
330
+
331
+ // if it's not a string, by definition it doesn't have an
332
+ // operator so use equality (default). it does not need
333
+ // escaping.
334
+
335
+ criteria = (criteria || 0).toString();
336
+
337
+ }
338
+
339
+ if (!parse_result) {
340
+
341
+ parse_result = this.parser.Parse('{}' + operator + criteria);
342
+ expression = parse_result.expression;
343
+
344
+ if (parse_result.error || !expression) {
345
+ return ExpressionError();
346
+ }
347
+ if (expression.type !== 'binary') {
348
+ console.warn('invalid expression [1]', expression);
349
+ return ExpressionError();
350
+ }
351
+ if (expression.left.type !== 'array') {
352
+ console.warn('invalid expression [1]', expression);
353
+ return ExpressionError();
354
+ }
355
+
356
+ // this is only going to work for binary left/right. it won't
357
+ // work if we change this to a function (wildcard match)
358
+
359
+ // this will not happen anymore, we can remove
360
+
361
+ if (expression.right.type === 'identifier') {
362
+
363
+ console.warn('will never happen');
364
+
365
+ expression.right = {
366
+ ...expression.right,
367
+ type: 'literal',
368
+ value: expression.right.name,
369
+ }
370
+ }
371
+
372
+ expression.left.values = [data];
373
+
374
+ }
375
+
376
+ if (!expression) {
377
+ return ValueError();
378
+ }
379
+
380
+ const result = this.CalculateExpression(expression);
381
+ return result;
382
+
383
+ /*
384
+ // console.info({expression, result});
385
+
386
+ if (result.type === ValueType.array) {
387
+ let count = 0;
388
+ for (const column of (result as ArrayUnion).value) {
389
+ for (const cell of column) {
390
+ if (cell.value) { count++; }
391
+ }
392
+ }
393
+ return { type: ValueType.number, value: count };
394
+ }
395
+
396
+ return result; // error?
397
+ */
398
+
399
+ };
244
400
 
245
401
  this.library.Register({
246
402
 
@@ -403,6 +559,57 @@ export class Calculator extends Graph {
403
559
  },
404
560
  },
405
561
 
562
+ /**
563
+ * anything I said about COUNTIF applies here, but worse.
564
+ * COUNTIFS is an AND operation across separate COUNTIFs.
565
+ * presumably they have to be the same shape.
566
+ */
567
+ CountIfs: {
568
+ arguments: [
569
+ { name: 'range1', },
570
+ { name: 'criteria1', },
571
+ { name: 'range2', },
572
+ { name: 'criteria2', }
573
+ ],
574
+ fn: (...args): UnionValue => {
575
+
576
+ let count = 0;
577
+
578
+ let result = CountIfInternal(args[0], args[1]);
579
+ if (result.type !== ValueType.array) {
580
+ return result; // error
581
+ }
582
+
583
+ const base = FlattenBooleans(result);
584
+
585
+ for (let i = 2; i < args.length; i += 2) {
586
+ if (args[i] && args[i + 1]) {
587
+
588
+ const result = CountIfInternal(args[i], args[i+1]);
589
+ if (result.type !== ValueType.array) {
590
+ return result;
591
+ }
592
+
593
+ const step = FlattenBooleans(result);
594
+ for (const [index, value] of base.entries()) {
595
+ base[index] = value && step[index];
596
+ }
597
+
598
+ }
599
+ }
600
+
601
+ for (const element of base) {
602
+ if (element) { count++; }
603
+ }
604
+
605
+ return {
606
+ type: ValueType.number,
607
+ value: count,
608
+ }
609
+
610
+ },
611
+ },
612
+
406
613
  /**
407
614
  * this function is here so it has access to the parser.
408
615
  * this is crazy expensive. is there a way to reduce cost?
@@ -425,59 +632,7 @@ export class Calculator extends Graph {
425
632
  ],
426
633
  fn: (range, criteria): UnionValue => {
427
634
 
428
- const data = Utilities.FlattenUnboxed(range);
429
-
430
- // console.info({range, data});
431
-
432
- // console.info({range});
433
-
434
- if (typeof criteria !== 'string') {
435
- criteria = '=' + (criteria || 0).toString();
436
- }
437
- else {
438
- criteria = criteria.trim();
439
- if (!/^[=<>]/.test(criteria)) {
440
- criteria = '=' + criteria;
441
- }
442
- }
443
-
444
- // switching to an array. doesn't actually seem to be any
445
- // faster... more appropriate, though.
446
-
447
- const parse_result = this.parser.Parse('{}' + criteria);
448
- const expression = parse_result.expression;
449
-
450
- if (parse_result.error || !expression) {
451
- return ExpressionError();
452
- }
453
- if (expression.type !== 'binary') {
454
- // console.warn('invalid expression [1]', expression);
455
- return ExpressionError();
456
- }
457
- if (expression.left.type !== 'array') {
458
- // console.warn('invalid expression [1]', expression);
459
- return ExpressionError();
460
- }
461
-
462
- expression.left.values = [data];
463
- const result = this.CalculateExpression(expression);
464
-
465
- // console.info({expression, result});
466
-
467
- // this is no longer the case because we're getting
468
- // a boxed result (union)
469
-
470
- /*
471
- if (Array.isArray(result)) {
472
- let count = 0;
473
- for (const column of result) {
474
- for (const cell of column) {
475
- if (cell.value) { count++; }
476
- }
477
- }
478
- return { type: ValueType.number, value: count };
479
- }
480
- */
635
+ const result = CountIfInternal(range, criteria);
481
636
 
482
637
  if (result.type === ValueType.array) {
483
638
  let count = 0;
@@ -489,7 +644,7 @@ export class Calculator extends Graph {
489
644
  return { type: ValueType.number, value: count };
490
645
  }
491
646
 
492
- return result; // error?
647
+ return result; // error
493
648
 
494
649
  },
495
650
  },
@@ -1150,12 +1305,16 @@ export class Calculator extends Graph {
1150
1305
  // don't assume default, always set
1151
1306
 
1152
1307
  if (Localization.decimal_separator === ',') {
1153
- this.parser.decimal_mark = DecimalMarkType.Comma;
1154
- this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
1308
+ this.parser.SetLocaleSettings(DecimalMarkType.Comma);
1309
+
1310
+ // this.parser.decimal_mark = DecimalMarkType.Comma;
1311
+ // this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
1155
1312
  }
1156
1313
  else {
1157
- this.parser.decimal_mark = DecimalMarkType.Period;
1158
- this.parser.argument_separator = ArgumentSeparatorType.Comma;
1314
+ this.parser.SetLocaleSettings(DecimalMarkType.Period);
1315
+
1316
+ // this.parser.decimal_mark = DecimalMarkType.Period;
1317
+ // this.parser.argument_separator = ArgumentSeparatorType.Comma;
1159
1318
  }
1160
1319
 
1161
1320
  // this.expression_calculator.UpdateLocale();
@@ -1381,17 +1540,23 @@ export class Calculator extends Graph {
1381
1540
  /** moved from embedded sheet */
1382
1541
  public Evaluate(expression: string, active_sheet?: Sheet, options: EvaluateOptions = {}, raw_result = false) {
1383
1542
 
1384
- const current = this.parser.argument_separator;
1385
- const r1c1_state = this.parser.flags.r1c1;
1543
+ // const current = this.parser.argument_separator;
1544
+ // const r1c1_state = this.parser.flags.r1c1;
1545
+
1546
+ this.parser.Save();
1386
1547
 
1387
1548
  if (options.argument_separator) {
1388
1549
  if (options.argument_separator === ',') {
1389
- this.parser.argument_separator = ArgumentSeparatorType.Comma;
1390
- this.parser.decimal_mark = DecimalMarkType.Period;
1550
+ this.parser.SetLocaleSettings(DecimalMarkType.Period);
1551
+
1552
+ // this.parser.argument_separator = ArgumentSeparatorType.Comma;
1553
+ // this.parser.decimal_mark = DecimalMarkType.Period;
1391
1554
  }
1392
1555
  else {
1393
- this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
1394
- this.parser.decimal_mark = DecimalMarkType.Comma;
1556
+ this.parser.SetLocaleSettings(DecimalMarkType.Comma);
1557
+
1558
+ // this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
1559
+ // this.parser.decimal_mark = DecimalMarkType.Comma;
1395
1560
  }
1396
1561
  }
1397
1562
 
@@ -1403,9 +1568,11 @@ export class Calculator extends Graph {
1403
1568
 
1404
1569
  // reset
1405
1570
 
1406
- this.parser.argument_separator = current;
1407
- this.parser.decimal_mark = (current === ArgumentSeparatorType.Comma) ? DecimalMarkType.Period : DecimalMarkType.Comma;
1408
- this.parser.flags.r1c1 = r1c1_state;
1571
+ // this.parser.argument_separator = current;
1572
+ // this.parser.decimal_mark = (current === ArgumentSeparatorType.Comma) ? DecimalMarkType.Period : DecimalMarkType.Comma;
1573
+ // this.parser.flags.r1c1 = r1c1_state;
1574
+
1575
+ this.parser.Restore();
1409
1576
 
1410
1577
  // OK
1411
1578
 
@@ -149,6 +149,11 @@ export interface CompositeFunctionDescriptor {
149
149
  */
150
150
  return_type?: ReturnType;
151
151
 
152
+ /**
153
+ * @internal
154
+ */
155
+ export?: (...args: any[]) => string;
156
+
152
157
  }
153
158
 
154
159
  export interface FunctionMap {
@@ -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
  }