@trebco/treb 29.3.4 → 29.5.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 (44) hide show
  1. package/dist/treb-spreadsheet-light.mjs +12 -12
  2. package/dist/treb-spreadsheet.mjs +12 -12
  3. package/dist/treb.d.ts +36 -41
  4. package/package.json +1 -1
  5. package/treb-base-types/src/area.ts +7 -0
  6. package/treb-base-types/src/cell.ts +2 -46
  7. package/treb-base-types/src/cells.ts +14 -8
  8. package/treb-base-types/src/gradient.ts +2 -2
  9. package/treb-base-types/src/import.ts +2 -2
  10. package/treb-base-types/src/style.ts +79 -6
  11. package/treb-base-types/src/theme.ts +24 -15
  12. package/treb-calculator/src/calculator.ts +22 -12
  13. package/treb-calculator/src/dag/graph.ts +12 -3
  14. package/treb-calculator/src/expression-calculator.ts +66 -74
  15. package/treb-calculator/src/functions/base-functions.ts +2 -2
  16. package/treb-calculator/src/functions/sparkline.ts +2 -2
  17. package/treb-calculator/src/functions/statistics-functions.ts +31 -1
  18. package/treb-data-model/src/data-validation.ts +44 -0
  19. package/treb-data-model/src/data_model.ts +11 -7
  20. package/treb-data-model/src/index.ts +1 -1
  21. package/treb-data-model/src/named.ts +35 -10
  22. package/treb-data-model/src/sheet.ts +75 -15
  23. package/treb-data-model/src/sheet_types.ts +4 -0
  24. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +7 -3
  25. package/treb-embed/src/embedded-spreadsheet.ts +50 -28
  26. package/treb-embed/src/progress-dialog.ts +4 -1
  27. package/treb-embed/src/types.ts +9 -0
  28. package/treb-export/src/drawing2/chart2.ts +20 -38
  29. package/treb-export/src/drawing2/drawing2.ts +2 -107
  30. package/treb-export/src/export-worker/export-worker.ts +1 -1
  31. package/treb-export/src/{export2.ts → export.ts} +439 -628
  32. package/treb-export/src/import2.ts +63 -26
  33. package/treb-export/src/workbook-style2.ts +16 -14
  34. package/treb-export/src/workbook2.ts +2 -18
  35. package/treb-export/src/xml-utils.ts +50 -2
  36. package/treb-export/src/zip-wrapper.ts +1 -1
  37. package/treb-grid/src/editors/overlay_editor.ts +3 -3
  38. package/treb-grid/src/layout/base_layout.ts +5 -14
  39. package/treb-grid/src/render/tile_renderer.ts +49 -48
  40. package/treb-grid/src/types/grid.ts +164 -26
  41. package/treb-grid/src/types/grid_base.ts +93 -17
  42. package/treb-grid/src/types/grid_command.ts +2 -1
  43. package/treb-parser/src/parser-types.ts +10 -0
  44. package/treb-parser/src/parser.ts +55 -17
@@ -37,11 +37,12 @@
37
37
 
38
38
  import { EventSource } from 'treb-utils';
39
39
  import type { DataModel, ConditionalFormat, // MacroFunction, SerializedModel, SerializedNamedExpression,
40
- ViewModel } from 'treb-data-model';
40
+ ViewModel,
41
+ DataValidation} from 'treb-data-model';
41
42
 
42
43
  import type { Parser, UnitAddress} from 'treb-parser';
43
44
  import { type ExpressionUnit, IllegalSheetNameRegex, ParseCSV, DecimalMarkType } from 'treb-parser';
44
- import { Area, IsCellAddress, ValidationType, ValueType, DefaultTableSortOptions } from 'treb-base-types';
45
+ import { Area, IsCellAddress, ValueType, DefaultTableSortOptions } from 'treb-base-types';
45
46
  import type { ICellAddress, IArea, Cell, CellValue, CellStyle, Table, TableSortOptions, TableTheme, Complex, PatchOptions as PatchAreaOptions } from 'treb-base-types';
46
47
 
47
48
  import { Sheet, type SerializeOptions, type Annotation } from 'treb-data-model';
@@ -592,7 +593,7 @@ export class GridBase {
592
593
  * @param column column, columns, or undefined means all columns
593
594
  * @param width target width, or undefined means auto-size
594
595
  */
595
- public SetColumnWidth(column?: number | number[], width = 0): void {
596
+ public SetColumnWidth(column?: number | number[], width?: number): void {
596
597
  this.ExecCommand({
597
598
  key: CommandKey.ResizeColumns,
598
599
  column,
@@ -922,34 +923,54 @@ export class GridBase {
922
923
 
923
924
  protected SetValidationInternal(command: DataValidationCommand): void {
924
925
 
925
- let cell: Cell|undefined;
926
-
927
926
  const sheet = this.FindSheet(command.area);
928
-
929
- if (sheet) {
930
- cell = sheet.cells.GetCell(command.area, true);
927
+ if (!sheet) {
928
+ throw new Error('invalid sheet in set validation');
931
929
  }
932
930
 
933
- if (!cell) {
934
- throw new Error('invalid cell in set validation');
935
- }
931
+ const target = {start: command.area.start, end: command.area.end };
936
932
 
937
933
  if (command.range) {
934
+
935
+ sheet.AddValidation({
936
+ type: 'range',
937
+ error: !!command.error,
938
+ area: command.range,
939
+ target: [target],
940
+ });
941
+
942
+ /*
938
943
  cell.validation = {
939
944
  type: ValidationType.Range,
940
945
  area: command.range,
941
946
  error: !!command.error,
942
947
  };
948
+ */
949
+
943
950
  }
944
951
  else if (command.list) {
952
+
953
+ sheet.AddValidation({
954
+ type: 'list',
955
+ error: !!command.error,
956
+ list: JSON.parse(JSON.stringify(command.list)),
957
+ target: [target],
958
+ });
959
+
960
+ /*
945
961
  cell.validation = {
946
962
  type: ValidationType.List,
947
963
  list: JSON.parse(JSON.stringify(command.list)),
948
964
  error: !!command.error,
949
965
  }
966
+ */
967
+
950
968
  }
951
969
  else {
952
- cell.validation = undefined;
970
+ // cell.validation = undefined;
971
+
972
+ sheet.RemoveValidations(target);
973
+
953
974
  }
954
975
 
955
976
  }
@@ -2493,11 +2514,20 @@ export class GridBase {
2493
2514
 
2494
2515
  // translate...
2495
2516
  if (unit.offset_column) {
2517
+ unit.absolute_column = false;
2496
2518
  unit.column = unit.column + address.column;
2497
2519
  }
2520
+ else {
2521
+ unit.absolute_column = true;
2522
+ }
2523
+
2498
2524
  if (unit.offset_row) {
2525
+ unit.absolute_row = false;
2499
2526
  unit.row = unit.row + address.row;
2500
2527
  }
2528
+ else {
2529
+ unit.absolute_row = true;
2530
+ }
2501
2531
 
2502
2532
  }
2503
2533
  return true;
@@ -2794,8 +2824,54 @@ export class GridBase {
2794
2824
  /**
2795
2825
  * patch sheet conditionals for insert/delete row/column operations.
2796
2826
  * some of them may be deleted.
2827
+ *
2828
+ * UPDATE: using this routine to also patch data validations
2797
2829
  */
2798
- protected PatchConditionals(options: PatchOptions) {
2830
+ protected PatchConditionalsAndValidations(options: PatchOptions) {
2831
+
2832
+ if (options.sheet.data_validation.length) {
2833
+
2834
+ const delete_list: Set<DataValidation> = new Set();
2835
+ for (const validation of options.sheet.data_validation) {
2836
+
2837
+ const targets: IArea[] = [];
2838
+ for (const area of validation.target) {
2839
+ const updated = Area.PatchArea(area, options);
2840
+ if (updated) {
2841
+ targets.push(updated);
2842
+ }
2843
+ }
2844
+
2845
+ if (targets.length > 0) {
2846
+
2847
+ validation.target = targets;
2848
+
2849
+ // format the range, if necessary
2850
+
2851
+ if (validation.type === 'range') {
2852
+ if (validation.area.start.sheet_id === options.sheet.id) {
2853
+ const updated = Area.PatchArea(validation.area, options);
2854
+ if (updated) {
2855
+ validation.area = updated;
2856
+ }
2857
+ else {
2858
+ delete_list.add(validation);
2859
+ }
2860
+ }
2861
+ }
2862
+
2863
+ }
2864
+ else {
2865
+ delete_list.add(validation);
2866
+ }
2867
+
2868
+ }
2869
+
2870
+ if (delete_list.size) {
2871
+ options.sheet.data_validation = options.sheet.data_validation.filter(test => !delete_list.has(test));
2872
+ }
2873
+
2874
+ }
2799
2875
 
2800
2876
  if (options.sheet.conditional_formats?.length) {
2801
2877
 
@@ -2869,7 +2945,7 @@ export class GridBase {
2869
2945
  }
2870
2946
 
2871
2947
  // conditionals
2872
- this.PatchConditionals({
2948
+ this.PatchConditionalsAndValidations({
2873
2949
  sheet: target_sheet,
2874
2950
  before_column: 0,
2875
2951
  column_count: 0,
@@ -3217,7 +3293,7 @@ export class GridBase {
3217
3293
  }
3218
3294
 
3219
3295
  // conditionals
3220
- this.PatchConditionals({
3296
+ this.PatchConditionalsAndValidations({
3221
3297
  sheet: target_sheet,
3222
3298
  before_column: command.before_column,
3223
3299
  column_count: command.count,
@@ -4029,8 +4105,8 @@ export class GridBase {
4029
4105
  // COEDITING: ok
4030
4106
 
4031
4107
  this.SetValidationInternal(command);
4032
- if (!command.area.sheet_id || command.area.sheet_id === this.active_sheet.id) {
4033
- flags.render_area = Area.Join(new Area(command.area), flags.render_area);
4108
+ if (!command.area.start.sheet_id || command.area.start.sheet_id === this.active_sheet.id) {
4109
+ flags.render_area = Area.Join(new Area(command.area.start, command.area.end), flags.render_area);
4034
4110
  }
4035
4111
  break;
4036
4112
 
@@ -225,7 +225,8 @@ export interface SetNameCommand {
225
225
 
226
226
  export interface DataValidationCommand {
227
227
  key: CommandKey.DataValidation;
228
- area: ICellAddress;
228
+ area: IArea;
229
+
229
230
  range?: IArea;
230
231
  list?: CellValue[];
231
232
  error?: boolean;
@@ -401,6 +401,16 @@ export type ParserFlags = Partial<OptionalParserFlags> & RequiredParserFlags;
401
401
 
402
402
  export interface RenderOptions {
403
403
  offset: { rows: number; columns: number };
404
+
405
+ /**
406
+ * render in R1C1 format. this will be relative if the R1C1 base
407
+ * address is set; otherwise absolute.
408
+ */
409
+ r1c1?: boolean;
410
+
411
+ /** base for offsetting relative R1C1 addresses */
412
+ r1c1_base?: UnitAddress;
413
+
404
414
  missing: string;
405
415
  convert_decimal: DecimalMarkType;
406
416
  convert_argument_separator: ArgumentSeparatorType;
@@ -409,7 +409,7 @@ export class Parser {
409
409
  for (let i = 0; i < n; i++) {
410
410
  transposed[i] = [];
411
411
  for (let j = 0; j < m; j++) {
412
- transposed[i][j] = arr[j][i];
412
+ transposed[i][j] = arr[j] ? arr[j][i] : undefined;
413
413
  }
414
414
  }
415
415
 
@@ -489,14 +489,12 @@ export class Parser {
489
489
 
490
490
  switch (unit.type) {
491
491
  case 'address':
492
- return this.AddressLabel(unit, offset);
492
+ return options.r1c1 ? this.R1C1Label(unit, options.r1c1_base) : this.AddressLabel(unit, offset);
493
493
 
494
494
  case 'range':
495
- return (
496
- this.AddressLabel(unit.start, offset) +
497
- ':' +
498
- this.AddressLabel(unit.end, offset)
499
- );
495
+ return options.r1c1 ?
496
+ this.R1C1Label(unit.start, options.r1c1_base) + ':' + this.R1C1Label(unit.end, options.r1c1_base) :
497
+ this.AddressLabel(unit.start, offset) + ':' + this.AddressLabel(unit.end, offset);
500
498
 
501
499
  case 'missing':
502
500
  return missing;
@@ -823,7 +821,35 @@ export class Parser {
823
821
  return s;
824
822
  }
825
823
 
826
- /** generates address label ("C3") from address (0-based) */
824
+ /**
825
+ * generates absolute or relative R1C1 address
826
+ */
827
+ protected R1C1Label(
828
+ address: UnitAddress,
829
+ base?: UnitAddress,
830
+ ): string {
831
+
832
+ let label = '';
833
+
834
+ if (address.sheet) { // && (!base?.sheet || base?.sheet !== address.sheet)) {
835
+ label = (QuotedSheetNameRegex.test(address.sheet) ?
836
+ '\'' + address.sheet + '\'' : address.sheet) + '!';
837
+ }
838
+
839
+ const row = (address.absolute_row || !base) ? (address.row + 1).toString() : `[${address.row - base.row}]`;
840
+ const column = (address.absolute_column || !base) ? (address.column + 1).toString() : `[${address.column - base.column}]`;
841
+
842
+ label += `R${row}C${column}`;
843
+
844
+ return label;
845
+ }
846
+
847
+ /**
848
+ * generates address label ("C3") from address (0-based).
849
+ *
850
+ * @param offset - offset by some number of rows or columns
851
+ * @param r1c1 - if set, return data in R1C1 format.
852
+ */
827
853
  protected AddressLabel(
828
854
  address: UnitAddress,
829
855
  offset: { rows: number; columns: number },
@@ -839,12 +865,6 @@ export class Parser {
839
865
  let label = '';
840
866
  if (address.sheet) {
841
867
 
842
- /*
843
- if (address.sheet === '__REF') {
844
- return '#REF'; // magic
845
- }
846
- */
847
-
848
868
  label = (QuotedSheetNameRegex.test(address.sheet) ?
849
869
  '\'' + address.sheet + '\'' : address.sheet) + '!';
850
870
  }
@@ -2415,8 +2435,11 @@ export class Parser {
2415
2435
  if (!c) return null;
2416
2436
  position = c.position;
2417
2437
 
2438
+ // things that look like an address but have row 0 are legal
2439
+ // as names. so this should be a token if r === 0.
2440
+
2418
2441
  const r = this.ConsumeAddressRow(position);
2419
- if (!r) return null;
2442
+ if (!r) return null;
2420
2443
  position = r.position;
2421
2444
 
2422
2445
  const label = sheet ?
@@ -2460,7 +2483,11 @@ export class Parser {
2460
2483
 
2461
2484
  /**
2462
2485
  * consumes a row, possibly absolute ($). returns the numeric row
2463
- * (0-based) and metadata
2486
+ * (0-based) and metadata.
2487
+ *
2488
+ * note that something like "X0" is a legal token, because 0 is not
2489
+ * a valid row. but at the same time it can't have a $ in it. although
2490
+ * maybe "X$0" is a token but not a valid name? dunno
2464
2491
  */
2465
2492
  protected ConsumeAddressRow(position: number):
2466
2493
  {
@@ -2484,7 +2511,18 @@ export class Parser {
2484
2511
  else break;
2485
2512
  }
2486
2513
 
2487
- if (start === position) return false;
2514
+ if (start === position) {
2515
+ return false;
2516
+ }
2517
+
2518
+ // handle token X0. should ~maybe~ handle this only if !absolute
2519
+ // temp leaving this separate from the above test just so it's clear
2520
+ // what we are doing
2521
+
2522
+ if (value === 0) {
2523
+ return false;
2524
+ }
2525
+
2488
2526
  return { absolute, row: value - 1, position };
2489
2527
  }
2490
2528