@trebco/treb 28.7.0 → 28.10.5

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 +14 -14
  2. package/dist/treb-spreadsheet.mjs +12 -12
  3. package/dist/treb.d.ts +39 -6
  4. package/notes/connected-elements.md +37 -0
  5. package/package.json +1 -1
  6. package/treb-base-types/src/gradient.ts +1 -1
  7. package/treb-base-types/src/localization.ts +6 -0
  8. package/treb-calculator/src/calculator.ts +72 -30
  9. package/treb-calculator/src/dag/calculation_leaf_vertex.ts +7 -0
  10. package/treb-calculator/src/dag/graph.ts +8 -0
  11. package/treb-calculator/src/functions/base-functions.ts +30 -1
  12. package/treb-calculator/src/index.ts +1 -1
  13. package/treb-charts/src/chart-functions.ts +14 -0
  14. package/treb-charts/src/chart-types.ts +25 -1
  15. package/treb-charts/src/chart-utils.ts +195 -9
  16. package/treb-charts/src/chart.ts +4 -0
  17. package/treb-charts/src/default-chart-renderer.ts +17 -1
  18. package/treb-charts/src/renderer.ts +182 -9
  19. package/treb-charts/style/charts.scss +39 -0
  20. package/treb-embed/markup/toolbar.html +35 -34
  21. package/treb-embed/src/custom-element/treb-global.ts +10 -2
  22. package/treb-embed/src/embedded-spreadsheet.ts +209 -106
  23. package/treb-embed/src/options.ts +7 -0
  24. package/treb-embed/style/layout.scss +4 -0
  25. package/treb-embed/style/toolbar.scss +37 -0
  26. package/treb-grid/src/index.ts +1 -1
  27. package/treb-grid/src/types/conditional_format.ts +1 -1
  28. package/treb-grid/src/types/data_model.ts +32 -0
  29. package/treb-grid/src/types/grid.ts +11 -1
  30. package/treb-grid/src/types/grid_base.ts +161 -5
  31. package/treb-grid/src/types/grid_command.ts +32 -0
  32. package/treb-grid/src/types/grid_events.ts +7 -0
  33. package/treb-grid/src/types/grid_options.ts +8 -0
  34. package/treb-grid/src/types/sheet.ts +0 -56
  35. package/treb-grid/src/types/update_flags.ts +1 -0
  36. package/treb-parser/src/parser-types.ts +6 -0
  37. package/treb-parser/src/parser.ts +48 -1
@@ -67,6 +67,10 @@ interface PatchOptions extends PatchAreaOptions {
67
67
  sheet: Sheet;
68
68
  }
69
69
 
70
+ const AssertNever = (value: never) => {
71
+ console.error('invalid case');
72
+ };
73
+
70
74
  export class GridBase {
71
75
 
72
76
  // --- public members --------------------------------------------------------
@@ -160,6 +164,24 @@ export class GridBase {
160
164
 
161
165
  // --- API methods -----------------------------------------------------------
162
166
 
167
+ public RemoveConditionalFormat(options: {
168
+ format?: ConditionalFormat;
169
+ area?: IArea;
170
+ }) {
171
+
172
+ this.ExecCommand({
173
+ key: CommandKey.RemoveConditionalFormat,
174
+ ...options,
175
+ });
176
+ }
177
+
178
+ public AddConditionalFormat(format: ConditionalFormat) {
179
+ this.ExecCommand({
180
+ key: CommandKey.AddConditionalFormat,
181
+ format,
182
+ });
183
+ }
184
+
163
185
  /** remove a table. doesn't remove any data, just removes the overlay. */
164
186
  public RemoveTable(table: Table) {
165
187
  this.ExecCommand({
@@ -1887,7 +1909,7 @@ export class GridBase {
1887
1909
  break;
1888
1910
  */
1889
1911
 
1890
- // special case
1912
+ // special cases
1891
1913
  case CommandKey.SortTable:
1892
1914
  case CommandKey.RemoveTable:
1893
1915
  if (!command.table.area.start.sheet_id) {
@@ -1895,6 +1917,12 @@ export class GridBase {
1895
1917
  }
1896
1918
  break;
1897
1919
 
1920
+ case CommandKey.AddConditionalFormat:
1921
+ if (!command.format.area.start.sheet_id) {
1922
+ command.format.area.start.sheet_id = id;
1923
+ }
1924
+ break;
1925
+
1898
1926
  // field
1899
1927
  case CommandKey.ResizeRows:
1900
1928
  case CommandKey.ResizeColumns:
@@ -1920,6 +1948,14 @@ export class GridBase {
1920
1948
  case CommandKey.Select:
1921
1949
  case CommandKey.InsertTable:
1922
1950
 
1951
+ // note that remove conditional format could have a format
1952
+ // object (with an area) instead of an area. but in that case,
1953
+ // the format object must match an existing format, so it would
1954
+ // have to have a proper area. there's no case where we would
1955
+ // want to add it. so we only handle the area case.
1956
+
1957
+ case CommandKey.RemoveConditionalFormat:
1958
+
1923
1959
  if (command.area) {
1924
1960
  if (IsCellAddress(command.area)) {
1925
1961
  if (!command.area.sheet_id) {
@@ -1934,10 +1970,13 @@ export class GridBase {
1934
1970
  }
1935
1971
  break;
1936
1972
 
1937
- // default:
1938
- // // command key here should be `never` if we've covered all the
1939
- // // cases (ts will complain)
1940
- // // console.warn('unhandled command key', command.key);
1973
+ default:
1974
+
1975
+ // this is intended to check that we've handled all cases. if you
1976
+ // add additional commands and they're not handled, this should
1977
+ // raise a ts error.
1978
+
1979
+ AssertNever(command);
1941
1980
 
1942
1981
  }
1943
1982
  }
@@ -2231,6 +2270,16 @@ export class GridBase {
2231
2270
  }
2232
2271
  }
2233
2272
 
2273
+ for (const element of this.model.connected_elements.values()) {
2274
+ if (element.formula) {
2275
+ const updated = this.PatchExpressionSheetName(element.formula, old_name, name);
2276
+ if (updated) {
2277
+ element.formula = updated;
2278
+ changes++;
2279
+ }
2280
+ }
2281
+ }
2282
+
2234
2283
  return changes;
2235
2284
 
2236
2285
  }
@@ -2811,6 +2860,18 @@ export class GridBase {
2811
2860
  row_count: command.count
2812
2861
  });
2813
2862
 
2863
+ // connected elements
2864
+ for (const external of this.model.connected_elements.values()) {
2865
+ if (external.formula) {
2866
+ const modified = this.PatchFormulasInternal(external.formula,
2867
+ command.before_row, command.count, 0, 0,
2868
+ target_sheet.name.toLowerCase(), false);
2869
+ if (modified) {
2870
+ external.formula = modified;
2871
+ }
2872
+ }
2873
+ }
2874
+
2814
2875
  // see InsertColumnsInternal re: tables. rows are less complicated,
2815
2876
  // except that if you delete the header row we want to remove the
2816
2877
  // table entirely.
@@ -3133,6 +3194,18 @@ export class GridBase {
3133
3194
  before_row: 0,
3134
3195
  row_count: 0 });
3135
3196
 
3197
+ // connected elements
3198
+ for (const element of this.model.connected_elements.values()) {
3199
+ if (element.formula) {
3200
+ const modified = this.PatchFormulasInternal(element.formula,
3201
+ 0, 0, command.before_column, command.count,
3202
+ target_sheet.name.toLowerCase(), false);
3203
+ if (modified) {
3204
+ element.formula = modified;
3205
+ }
3206
+ }
3207
+ }
3208
+
3136
3209
  // patch tables. we removed this from the sheet routine entirely,
3137
3210
  // we need to rebuild any affected tables now.
3138
3211
 
@@ -3534,6 +3607,88 @@ export class GridBase {
3534
3607
 
3535
3608
  break;
3536
3609
 
3610
+ case CommandKey.AddConditionalFormat:
3611
+ {
3612
+
3613
+ const sheet = this.FindSheet(command.format.area);
3614
+ sheet.conditional_formats.push(command.format);
3615
+
3616
+ sheet.Invalidate(new Area(command.format.area.start, command.format.area.end));
3617
+
3618
+ if (sheet === this.active_sheet) {
3619
+ // flags.style_area = Area.Join(command.format.area, flags.style_area);
3620
+ flags.render_area = Area.Join(command.format.area, flags.render_area);
3621
+ }
3622
+ else {
3623
+ // flags.style_event = true;
3624
+ }
3625
+
3626
+ flags.structure_event = true;
3627
+ flags.conditional_formatting_event = true;
3628
+
3629
+ }
3630
+ break;
3631
+
3632
+ case CommandKey.RemoveConditionalFormat:
3633
+
3634
+ {
3635
+ let sheet: Sheet|undefined;
3636
+ let count = 0;
3637
+
3638
+ if (command.format) {
3639
+
3640
+ // we're removing by object equivalence, not strict equality.
3641
+ // this is in case we're switching contexts and the objects
3642
+ // are not strictly equal. may be unecessary. do we need to
3643
+ // normalize in some way? (...)
3644
+
3645
+ const format = JSON.stringify(command.format);
3646
+
3647
+ sheet = this.FindSheet(command.format.area);
3648
+ sheet.conditional_formats = sheet.conditional_formats.filter(test => {
3649
+ // if (test === command.format) {
3650
+ if (JSON.stringify(test) === format) {
3651
+ count++;
3652
+ flags.render_area = Area.Join(test.area, flags.render_area);
3653
+ return false;
3654
+ }
3655
+ return true;
3656
+ });
3657
+
3658
+ }
3659
+ else if (command.area) {
3660
+ const area = new Area(command.area.start, command.area.end);
3661
+ sheet = this.FindSheet(command.area);
3662
+
3663
+ sheet.conditional_formats = sheet.conditional_formats.filter(test => {
3664
+ const compare = new Area(test.area.start, test.area.end);
3665
+ if (compare.Intersects(area)) {
3666
+ count++;
3667
+ flags.render_area = Area.Join(compare, flags.render_area);
3668
+ return false;
3669
+ }
3670
+ return true;
3671
+ });
3672
+
3673
+ }
3674
+
3675
+ if (sheet && count) {
3676
+ sheet.FlushConditionalFormats();
3677
+ flags.structure_event = true;
3678
+
3679
+ // this will flush leaf vertices. but it's expensive because
3680
+ // it's rebuilding the whole graph. we could maybe reduce a
3681
+ // bit... the question is what's worse: rebuilding the graph
3682
+ // or leaving orphans for a while?
3683
+
3684
+ // flags.structure_rebuild_required = true;
3685
+
3686
+ flags.conditional_formatting_event = true;
3687
+ }
3688
+
3689
+ }
3690
+ break;
3691
+
3537
3692
  case CommandKey.InsertTable:
3538
3693
 
3539
3694
  // the most important thing here is validating that we can
@@ -4170,6 +4325,7 @@ export class GridBase {
4170
4325
  events.push({
4171
4326
  type: 'structure',
4172
4327
  rebuild_required: flags.structure_rebuild_required,
4328
+ conditional_format: flags.conditional_formatting_event,
4173
4329
  });
4174
4330
  }
4175
4331
 
@@ -22,6 +22,7 @@
22
22
  import type { ICellAddress, IArea, Style, CellStyle, Color, CellValue, Table, TableSortType, TableTheme } from 'treb-base-types';
23
23
  import type { ExpressionUnit } from 'treb-parser';
24
24
  import type { BorderConstants } from './border_constants';
25
+ import type { ConditionalFormat } from './conditional_format';
25
26
 
26
27
  /**
27
28
  * switching to an exec-command based model, so we can serialize
@@ -73,6 +74,9 @@ export enum CommandKey {
73
74
  InsertTable,
74
75
  RemoveTable,
75
76
 
77
+ AddConditionalFormat,
78
+ RemoveConditionalFormat,
79
+
76
80
  }
77
81
 
78
82
  /** base type for sheet commands -- can select sheet by name, id or index */
@@ -418,6 +422,32 @@ export interface ReorderSheetCommand {
418
422
  move_before: number;
419
423
  }
420
424
 
425
+ /**
426
+ * add conditional format
427
+ */
428
+ export interface AddConditionalFormatCommand {
429
+ key: CommandKey.AddConditionalFormat;
430
+ format: ConditionalFormat;
431
+ }
432
+
433
+ /**
434
+ * remove conditional format, either as an object or from a target
435
+ * area. as an object, we'll match using object equivalence and not
436
+ * identity.
437
+ */
438
+ export interface RemoveConditionalFormatCommand {
439
+
440
+ key: CommandKey.RemoveConditionalFormat;
441
+
442
+ /** if format is omitted, we will remove all formats from the target range */
443
+ format?: ConditionalFormat;
444
+
445
+ /** one of area or format should be supplied */
446
+ area?: IArea;
447
+
448
+ }
449
+
450
+
421
451
  /**
422
452
  * ephemeral flag added to commands.
423
453
  * /
@@ -459,6 +489,8 @@ export type Command =
459
489
  | ActivateSheetCommand
460
490
  | DataValidationCommand
461
491
  | DuplicateSheetCommand
492
+ | AddConditionalFormatCommand
493
+ | RemoveConditionalFormatCommand
462
494
  ) ; // & Ephemeral;
463
495
 
464
496
  /**
@@ -23,6 +23,7 @@ import type { GridSelection } from './grid_selection';
23
23
  import type { Annotation } from './annotation';
24
24
  import type { Sheet } from './sheet';
25
25
  import type { Area } from 'treb-base-types';
26
+ import type { ConditionalFormat } from './conditional_format';
26
27
 
27
28
  export enum ErrorCode {
28
29
 
@@ -80,6 +81,12 @@ export interface StructureEvent {
80
81
  * FIXME: merge/unmerge? (...) I think yes
81
82
  */
82
83
  rebuild_required?: boolean;
84
+
85
+ /**
86
+ * we can use this when conditional formats are modified/added/removed
87
+ */
88
+ conditional_format?: boolean;
89
+
83
90
  }
84
91
 
85
92
  export interface AnnotationEvent {
@@ -24,6 +24,14 @@ import type { StatsEntry } from './tab_bar';
24
24
 
25
25
  export type StatsFunction = (data: CellValue|CellValue[][]|undefined) => StatsEntry[];
26
26
 
27
+
28
+ /**
29
+ * @internalRemarks
30
+ *
31
+ * why are there two levels of options? can we consolidate this with
32
+ * the embedded spreadsheet options? they are never (AFAICT) used
33
+ * independently. maybe that's recent.
34
+ */
27
35
  export interface GridOptions {
28
36
 
29
37
  /** can expand rows/columns */
@@ -3232,62 +3232,6 @@ export class Sheet {
3232
3232
 
3233
3233
  }
3234
3234
 
3235
- /*
3236
- public ApplyConditionalFormats() {
3237
-
3238
- this.FlushConditionalFormatCache();
3239
-
3240
- for (const entry of this.conditional_formats) {
3241
-
3242
- console.info({entry});
3243
-
3244
- if (entry.applied) {
3245
- this.ApplyConditionalFormatCache(entry);
3246
- }
3247
- }
3248
-
3249
- }
3250
-
3251
- public FlushConditionalFormatCache() {
3252
-
3253
- // FIXME: need to flush any styles that are set, unless they match;
3254
- // perhaps we should use an alternate cache so we can compare? TODO/FIXME
3255
-
3256
- for (const [row, row_data] of this.conditional_format_cache.entries()) {
3257
- if (row_data) {
3258
- for (const [column, column_data] of row_data.entries()) {
3259
- if (column_data) {
3260
-
3261
- this.CellData({row, column}).FlushStyle();
3262
-
3263
- }
3264
- }
3265
- }
3266
- }
3267
-
3268
- this.conditional_format_cache = [];
3269
-
3270
- }
3271
-
3272
- public ApplyConditionalFormatCache(format: ConditionalFormat) {
3273
-
3274
- for (let row = format.area.start.row; row <= format.area.end.row; row++ ) {
3275
- for (let column = format.area.start.column; column <= format.area.end.column; column++ ) {
3276
- if (!this.conditional_format_cache[row]) {
3277
- this.conditional_format_cache[row] = [];
3278
- }
3279
- if (!this.conditional_format_cache[row][column]) {
3280
- this.conditional_format_cache[row][column] = [];
3281
- }
3282
- this.conditional_format_cache[row][column].push(format.style);
3283
- this.CellData({row, column}).FlushStyle();
3284
-
3285
- }
3286
- }
3287
-
3288
- }
3289
- */
3290
-
3291
3235
  private ConditionalFormatForCell(address: ICellAddress): CellStyle[] {
3292
3236
  if (this.conditional_format_cache[address.row]) {
3293
3237
  return this.conditional_format_cache[address.row][address.column] || [];
@@ -64,6 +64,7 @@ export interface UpdateFlags {
64
64
 
65
65
  data_event?: boolean;
66
66
  style_event?: boolean;
67
+ conditional_formatting_event?: boolean;
67
68
  structure_event?: boolean;
68
69
  structure_rebuild_required?: boolean;
69
70
 
@@ -395,3 +395,9 @@ export interface RenderOptions {
395
395
  long_structured_references: boolean;
396
396
  table_name: string;
397
397
  }
398
+
399
+ export interface PersistedParserConfig {
400
+ flags: Partial<ParserFlags>;
401
+ argument_separator: ArgumentSeparatorType;
402
+ decimal_mark: DecimalMarkType;
403
+ }
@@ -33,7 +33,8 @@ import type {
33
33
  UnitLiteralNumber,
34
34
  ParserFlags,
35
35
  UnitStructuredReference,
36
- RenderOptions} from './parser-types';
36
+ RenderOptions,
37
+ PersistedParserConfig} from './parser-types';
37
38
  import {
38
39
  ArgumentSeparatorType,
39
40
  DecimalMarkType
@@ -248,6 +249,52 @@ export class Parser {
248
249
  */
249
250
  protected full_reference_list: Array<UnitAddress | UnitRange | UnitIdentifier | UnitStructuredReference> = [];
250
251
 
252
+ protected parser_state: string[] = [];
253
+
254
+ /**
255
+ * save local configuration to a buffer, so it can be restored. we're doing
256
+ * this because in a lot of places we're caching parser flagss, changing
257
+ * them, and then restoring them. that's become repetitive, fragile to
258
+ * changes or new flags, and annoying.
259
+ *
260
+ * config is managed in a list with push/pop semantics. we store it as
261
+ * JSON so there's no possibility we'll accidentally mutate.
262
+ *
263
+ * FIXME: while we're at it why not migrate the separators -> flags, so
264
+ * there's a single location for this kind of state? (...TODO)
265
+ *
266
+ */
267
+ public Save() {
268
+ const config: PersistedParserConfig = {
269
+ flags: this.flags,
270
+ argument_separator: this.argument_separator,
271
+ decimal_mark: this.decimal_mark,
272
+ }
273
+ this.parser_state.push(JSON.stringify(config));
274
+ }
275
+
276
+ /**
277
+ * restore persisted config
278
+ * @see Save
279
+ */
280
+ public Restore() {
281
+ const json = this.parser_state.shift();
282
+ if (json) {
283
+ try {
284
+ const config = JSON.parse(json) as PersistedParserConfig;
285
+ this.flags = config.flags;
286
+ this.argument_separator = config.argument_separator;
287
+ this.decimal_mark = config.decimal_mark;
288
+ }
289
+ catch (err) {
290
+ console.error(err);
291
+ }
292
+ }
293
+ else {
294
+ console.warn("No parser state to restore");
295
+ }
296
+ }
297
+
251
298
  /**
252
299
  * recursive tree walk.
253
300
  *