@trebco/treb 32.4.1 → 32.6.4

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 (31) hide show
  1. package/README.md +0 -6
  2. package/dist/treb-spreadsheet.mjs +12 -12
  3. package/package.json +2 -2
  4. package/treb-base-types/src/import.ts +14 -10
  5. package/treb-calculator/src/calculator.ts +5 -0
  6. package/treb-charts/src/chart-types.ts +2 -0
  7. package/treb-charts/src/chart-utils.ts +45 -4
  8. package/treb-charts/src/default-chart-renderer.ts +84 -24
  9. package/treb-data-model/src/sheet.ts +9 -0
  10. package/treb-embed/src/embedded-spreadsheet.ts +2 -2
  11. package/treb-embed/style/tab-bar.scss +1 -0
  12. package/treb-export/src/{drawing2/drawing2.ts → drawing/drawing.ts} +2 -2
  13. package/treb-export/src/export.ts +11 -11
  14. package/treb-export/src/import.ts +216 -20
  15. package/treb-export/src/metadata.ts +187 -0
  16. package/treb-export/src/{workbook-sheet2.ts → workbook-sheet.ts} +2 -2
  17. package/treb-export/src/{workbook-style2.ts → workbook-style.ts} +1 -1
  18. package/treb-export/src/{workbook-theme2.ts → workbook-theme.ts} +0 -2
  19. package/treb-export/src/{workbook2.ts → workbook.ts} +157 -11
  20. package/treb-grid/src/types/grid.ts +9 -9
  21. package/treb-grid/src/types/grid_base.ts +2 -1
  22. package/treb-grid/src/types/grid_command.ts +3 -0
  23. package/treb-grid/src/types/tab_bar.ts +36 -7
  24. /package/treb-export/src/{drawing2 → drawing}/bubble-chart-template.ts +0 -0
  25. /package/treb-export/src/{drawing2 → drawing}/chart-template-components2.ts +0 -0
  26. /package/treb-export/src/{drawing2/chart2.ts → drawing/chart.ts} +0 -0
  27. /package/treb-export/src/{drawing2 → drawing}/column-chart-template2.ts +0 -0
  28. /package/treb-export/src/{drawing2 → drawing}/donut-chart-template2.ts +0 -0
  29. /package/treb-export/src/{drawing2 → drawing}/embedded-image.ts +0 -0
  30. /package/treb-export/src/{drawing2 → drawing}/scatter-chart-template2.ts +0 -0
  31. /package/treb-export/src/{shared-strings2.ts → shared-strings.ts} +0 -0
@@ -28,16 +28,17 @@ const xmlparser2 = new XMLParser(XMLOptions2);
28
28
 
29
29
  // import * as he from 'he';
30
30
 
31
- import type { TwoCellAnchor, CellAnchor } from './drawing2/drawing2';
31
+ import type { TwoCellAnchor, CellAnchor } from './drawing/drawing';
32
32
 
33
- import { SharedStrings } from './shared-strings2';
34
- import { StyleCache } from './workbook-style2';
35
- import { Theme } from './workbook-theme2';
36
- import { Sheet, VisibleState } from './workbook-sheet2';
33
+ import { SharedStrings } from './shared-strings';
34
+ import { StyleCache } from './workbook-style';
35
+ import { Theme } from './workbook-theme';
36
+ import { Sheet, VisibleState } from './workbook-sheet';
37
37
  import type { RelationshipMap } from './relationship';
38
38
  import { ZipWrapper } from './zip-wrapper';
39
- import type { CellStyle, ICellAddress, ThemeColor } from 'treb-base-types';
39
+ import type { CellStyle, ThemeColor } from 'treb-base-types';
40
40
  import type { SerializedNamed } from 'treb-data-model';
41
+ import { type Metadata, ParseMetadataXML } from './metadata';
41
42
 
42
43
  /**
43
44
  * @privateRemarks -- FIXME: not sure about the equal/equals thing. need to check.
@@ -53,20 +54,42 @@ export const ConditionalFormatOperators: Record<string, string> = {
53
54
  notEqual: '<>',
54
55
  };
55
56
 
57
+ //
58
+ // enums? really? in 2025? FIXME
59
+ //
56
60
  export enum ChartType {
57
- Unknown = 0, Column, Bar, Line, Scatter, Donut, Pie, Bubble, Box
61
+ Null = 0,
62
+ Column,
63
+ Bar,
64
+ Line,
65
+ Scatter,
66
+ Donut,
67
+ Pie,
68
+ Bubble,
69
+ Box,
70
+ Histogram,
71
+ Unknown
58
72
  }
59
73
 
60
74
  export interface ChartSeries {
61
75
  values?: string;
62
76
  categories?: string;
63
77
  bubble_size?: string;
78
+
79
+ /** special for histogram */
80
+ bin_count?: number;
81
+
64
82
  title?: string;
65
83
  }
66
84
 
85
+ type ChartFlags =
86
+ 'stacked'
87
+ ;
88
+
67
89
  export interface ChartDescription {
68
90
  title?: string;
69
91
  type: ChartType;
92
+ flags?: ChartFlags[];
70
93
  series?: ChartSeries[];
71
94
  }
72
95
 
@@ -151,6 +174,9 @@ export class Workbook {
151
174
  /** the workbook "rels" */
152
175
  public rels: RelationshipMap = {};
153
176
 
177
+ /** metadata reference; new and WIP */
178
+ public metadata?: Metadata;
179
+
154
180
  public sheets: Sheet[] = [];
155
181
 
156
182
  public active_tab = 0;
@@ -200,6 +226,13 @@ export class Workbook {
200
226
  let xml = xmlparser2.parse(data || '');
201
227
  this.shared_strings.FromXML(xml);
202
228
 
229
+ // new(ish) metadata
230
+ if (this.zip.Has('xl/metadata.xml')) {
231
+ data = this.zip.Get('xl/metadata.xml');
232
+ xml = xmlparser2.parse(data);
233
+ this.metadata = ParseMetadataXML(xml);
234
+ }
235
+
203
236
  // theme
204
237
  data = this.zip.Get('xl/theme/theme1.xml');
205
238
  xml = xmlparser2.parse(data);
@@ -542,7 +575,7 @@ export class Workbook {
542
575
  const xml = xmlparser2.parse(data);
543
576
 
544
577
  const result: ChartDescription = {
545
- type: ChartType.Unknown
578
+ type: ChartType.Null
546
579
  };
547
580
 
548
581
  // console.info("RC", xml);
@@ -562,8 +595,29 @@ export class Workbook {
562
595
  }
563
596
  }
564
597
  else {
598
+
599
+ // there's a bug in FindAll -- seems to have to do with the nodes
600
+ // here being strings
601
+
602
+ const nodes: (string | { t$: string })[] = [];
603
+ const parents = XMLUtils.FindAll(title_node, 'c:tx/c:rich/a:p/a:r');
604
+ for (const entry of parents) {
605
+ if (entry['a:t']) {
606
+ nodes.push(entry['a:t'])
607
+ }
608
+ }
609
+
610
+ /*
611
+ const xx = XMLUtils.FindAll(title_node, 'c:tx/c:rich/a:p/a:r');
612
+ const yy = XMLUtils.FindAll(title_node, 'c:tx/c:rich/a:p/a:r/a:t');
613
+ console.info({xx, yy});
614
+
565
615
  const nodes = XMLUtils.FindAll(title_node, 'c:tx/c:rich/a:p/a:r/a:t');
566
- result.title = '"' + nodes.join('') + '"';
616
+ */
617
+
618
+ result.title = '"' + nodes.map(node => {
619
+ return typeof node === 'string' ? node : (node.t$ || '');
620
+ }).join('') + '"';
567
621
  }
568
622
 
569
623
  }
@@ -653,6 +707,14 @@ export class Workbook {
653
707
  if (node['c:barDir']) {
654
708
  if (node['c:barDir'].a$?.val === 'col') {
655
709
  result.type = ChartType.Column;
710
+
711
+ if (node['c:grouping']?.a$?.val === 'stacked') {
712
+ if (!result.flags) {
713
+ result.flags = [];
714
+ }
715
+ result.flags.push('stacked');
716
+ }
717
+
656
718
  }
657
719
  }
658
720
 
@@ -705,9 +767,92 @@ export class Workbook {
705
767
  // box plot uses "extended chart" which is totally different... but we
706
768
  // might need it again later? for the time being it's just inlined
707
769
 
770
+ // hmmm also used for histogram... histograms aren't named, they are
771
+ // clustered column type with a binning element, which has some attributes
772
+
708
773
  const ex_series = XMLUtils.FindAll(xml, 'cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series');
709
774
  if (ex_series?.length) {
710
- if (ex_series.every(test => test.a$?.layoutId === 'boxWhisker')) {
775
+
776
+ // testing seems to require looping, so let's try to merge loops
777
+
778
+ let clustered_column = true;
779
+ let histogram = true;
780
+ let box_whisker = true;
781
+
782
+ for (const series of ex_series) {
783
+
784
+ const layout = series.a$?.layoutId;
785
+
786
+ if (clustered_column && layout !== 'clusteredColumn') {
787
+ clustered_column = false;
788
+ }
789
+ if (box_whisker && layout !== 'boxWhisker') {
790
+ box_whisker = false;
791
+ }
792
+ if (clustered_column && histogram) {
793
+ const binning = XMLUtils.FindAll(series, `cx:layoutPr/cx:binning`);
794
+ if (!binning.length) {
795
+ histogram = false;
796
+ }
797
+ }
798
+
799
+ }
800
+
801
+ // ok that's what we know so far...
802
+
803
+ if (histogram) {
804
+ result.type = ChartType.Histogram;
805
+ result.series = [];
806
+
807
+ for (const series_entry of ex_series) {
808
+
809
+ if (series_entry.a$?.hidden === '1') {
810
+ continue;
811
+ }
812
+
813
+ const series: ChartSeries = {};
814
+
815
+ const data = XMLUtils.FindAll(xml, 'cx:chartSpace/cx:chartData/cx:data');
816
+
817
+ // so there are multiple series, and multiple datasets,
818
+ // but they are all merged together? no idea how this design
819
+ // works
820
+
821
+ const values_list: string[] = [];
822
+ for (const data_series of data) {
823
+ values_list.push(data_series['cx:numDim']?.['cx:f'] || '');
824
+ }
825
+ series.values = values_list.join(',');
826
+
827
+ const bin_count = XMLUtils.FindAll(series_entry, `cx:layoutPr/cx:binning/cx:binCount`);
828
+ if (bin_count[0]) {
829
+ const count = Number(bin_count[0].a$?.val || 0);
830
+ if (count) {
831
+ series.bin_count = count;
832
+ }
833
+ }
834
+
835
+ result.series.push(series);
836
+
837
+ }
838
+
839
+ const title = XMLUtils.FindAll(xml, 'cx:chartSpace/cx:chart/cx:title/cx:tx/cx:txData');
840
+ if (title) {
841
+ if (title[0]?.['cx:f']) {
842
+ result.title = title[0]['cx:f'];
843
+ }
844
+ else if (title[0]?.['cx:v']) {
845
+ result.title = '"' + title[0]['cx:v'] + '"';
846
+ }
847
+ }
848
+
849
+ console.info("histogram", result);
850
+ return result;
851
+
852
+ }
853
+
854
+ if (box_whisker) {
855
+
711
856
  result.type = ChartType.Box;
712
857
  result.series = [];
713
858
  const data = XMLUtils.FindAll(xml, 'cx:chartSpace/cx:chartData/cx:data'); // /cx:data/cx:numDim/cx:f');
@@ -758,7 +903,8 @@ export class Workbook {
758
903
  }
759
904
 
760
905
  if (!node) {
761
- console.info("Chart type not handled");
906
+ console.info("Chart type not handled", {xml});
907
+ result.type = ChartType.Unknown;
762
908
  }
763
909
 
764
910
  // console.info("RX?", result);
@@ -1311,7 +1311,7 @@ export class Grid extends GridBase {
1311
1311
  }
1312
1312
 
1313
1313
  if (this.tab_bar) {
1314
- this.tab_bar.Update();
1314
+ this.tab_bar.Update(false); // !user
1315
1315
  }
1316
1316
 
1317
1317
  }
@@ -1320,7 +1320,7 @@ export class Grid extends GridBase {
1320
1320
  * This function is called via Shift+PageUp/PageDown. We need
1321
1321
  * to update to account for hidden sheets, which can't be activated.
1322
1322
  */
1323
- public NextSheet(step = 1): void {
1323
+ public NextSheet(step = 1, user = false): void {
1324
1324
 
1325
1325
  if (this.model.sheets.length === 1) {
1326
1326
  return;
@@ -1344,7 +1344,7 @@ export class Grid extends GridBase {
1344
1344
  if (visible[i].sheet === this.active_sheet) {
1345
1345
  let index = (i + step) % visible.length;
1346
1346
  while (index < 0) { index += visible.length; }
1347
- this.ActivateSheet(visible[index].index);
1347
+ this.ActivateSheet(visible[index].index, user);
1348
1348
  return;
1349
1349
  }
1350
1350
  }
@@ -1430,7 +1430,7 @@ export class Grid extends GridBase {
1430
1430
  }
1431
1431
 
1432
1432
  if (this.tab_bar) {
1433
- this.tab_bar.Update();
1433
+ this.tab_bar.Update(false); // !user
1434
1434
  }
1435
1435
 
1436
1436
  }
@@ -1453,7 +1453,7 @@ export class Grid extends GridBase {
1453
1453
  */
1454
1454
  public UpdateTabBar(): void {
1455
1455
  if (this.tab_bar) {
1456
- this.tab_bar.Update();
1456
+ this.tab_bar.Update(true); // user? ...
1457
1457
  }
1458
1458
  }
1459
1459
 
@@ -1730,7 +1730,7 @@ export class Grid extends GridBase {
1730
1730
  // this.tab_bar.Update();
1731
1731
  //}
1732
1732
 
1733
- this.tab_bar?.Update();
1733
+ this.tab_bar?.Update(false);
1734
1734
 
1735
1735
  this.Repaint(true);
1736
1736
  }
@@ -2673,7 +2673,7 @@ export class Grid extends GridBase {
2673
2673
 
2674
2674
  protected RenameSheetInternal(target: Sheet, name: string) {
2675
2675
  super.RenameSheetInternal(target, name);
2676
- this.tab_bar?.Update();
2676
+ this.tab_bar?.Update(false);
2677
2677
 
2678
2678
  }
2679
2679
 
@@ -2898,7 +2898,7 @@ export class Grid extends GridBase {
2898
2898
  activate: this.active_sheet,
2899
2899
  });
2900
2900
 
2901
- if (this.tab_bar) { this.tab_bar.Update(); }
2901
+ if (this.tab_bar) { this.tab_bar.Update(!!command.user); }
2902
2902
 
2903
2903
  this.layout.scroll_offset = this.active_sheet.scroll_offset;
2904
2904
 
@@ -5237,7 +5237,7 @@ export class Grid extends GridBase {
5237
5237
  case 'PageUp':
5238
5238
  case 'PageDown':
5239
5239
  if (event.shiftKey) {
5240
- this.NextSheet(event.key === 'PageUp' ? -1 : 1);
5240
+ this.NextSheet(event.key === 'PageUp' ? -1 : 1, true);
5241
5241
  break;
5242
5242
  }
5243
5243
 
@@ -254,7 +254,7 @@ export class GridBase {
254
254
  * activate sheet, by name or index number
255
255
  * @param sheet number (index into the array) or string (name)
256
256
  */
257
- public ActivateSheet(sheet: number | string): void {
257
+ public ActivateSheet(sheet: number | string, user?: boolean): void {
258
258
 
259
259
  const index = (typeof sheet === 'number') ? sheet : undefined;
260
260
  const name = (typeof sheet === 'string') ? sheet : undefined;
@@ -263,6 +263,7 @@ export class GridBase {
263
263
  key: CommandKey.ActivateSheet,
264
264
  index,
265
265
  name,
266
+ user,
266
267
  });
267
268
 
268
269
  }
@@ -431,6 +431,9 @@ export interface ActivateSheetCommand extends SheetSelection {
431
431
  tab_bar_event?: boolean;
432
432
 
433
433
  force?: boolean;
434
+
435
+ /** user action; use smooth scrolling */
436
+ user?: boolean;
434
437
  }
435
438
 
436
439
  /**
@@ -235,7 +235,8 @@ export class TabBar extends EventSource<TabEvent> {
235
235
 
236
236
  }
237
237
 
238
- public SetActive(tab: HTMLElement, active: boolean): void {
238
+ public SetActive(tab: HTMLElement, active: boolean, user = false): void {
239
+
239
240
  if (active) {
240
241
  // tab.classList.add('treb-selected');
241
242
  tab.setAttribute('selected', '');
@@ -245,8 +246,33 @@ export class TabBar extends EventSource<TabEvent> {
245
246
  tab.style.color = '';
246
247
  }
247
248
 
249
+ // this is forcing the page to scroll if the sheet is below
250
+ // the fold. this is not useful behavior. at the same time,
251
+ // we do need this to work... we probably have to do it manually
252
+ // instead of using scrollIntoView. would be nice if we could
253
+ // toggle this on manual/auto, so user clicks would still be
254
+ // smooth. call that a TODO
255
+
248
256
  requestAnimationFrame(() => {
249
- tab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest'});
257
+ if (user) {
258
+ tab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest'});
259
+ }
260
+ else {
261
+ if (tab.parentElement) {
262
+ const left = tab.offsetLeft;
263
+ const width = tab.offsetWidth;
264
+ const container_width = tab.parentElement.clientWidth || 0;
265
+ const scroll_left = tab.parentElement.scrollLeft || 0;
266
+
267
+ if (left > container_width) {
268
+ tab.parentElement.scrollLeft = left - width;
269
+ }
270
+ else if (scroll_left > left) {
271
+ tab.parentElement.scrollLeft = left;
272
+ }
273
+
274
+ }
275
+ }
250
276
  });
251
277
 
252
278
  }
@@ -298,7 +324,7 @@ export class TabBar extends EventSource<TabEvent> {
298
324
  case 'Escape':
299
325
  tab.innerText = sheet.name;
300
326
  this.Publish({ type: 'cancel' });
301
- this.Update();
327
+ this.Update(true);
302
328
  break;
303
329
 
304
330
  default:
@@ -314,7 +340,7 @@ export class TabBar extends EventSource<TabEvent> {
314
340
  this.Publish({ type: 'rename-sheet', name, sheet });
315
341
  }
316
342
  else {
317
- this.Update();
343
+ this.Update(true);
318
344
  }
319
345
  });
320
346
 
@@ -350,7 +376,7 @@ export class TabBar extends EventSource<TabEvent> {
350
376
  // then the classes won't change.
351
377
 
352
378
  for (const candidate of tabs) {
353
- this.SetActive(candidate, candidate === tab);
379
+ this.SetActive(candidate, candidate === tab, true);
354
380
  }
355
381
 
356
382
  this.dragging = true;
@@ -445,8 +471,11 @@ export class TabBar extends EventSource<TabEvent> {
445
471
 
446
472
  /**
447
473
  * update tabs from model.
474
+ *
475
+ * @param user - this is a user action, so use smooth scrolling
476
+ * when activating the tab. otherwise it's automatic so jump.
448
477
  */
449
- public Update(): void {
478
+ public Update(user = false): void {
450
479
 
451
480
  this.tab_color_cache.clear(); // we're setting tab color but it's not getting updated otherwise
452
481
 
@@ -512,7 +541,7 @@ export class TabBar extends EventSource<TabEvent> {
512
541
  tab.style.order = (index * 2).toString();
513
542
  tab.role = 'tab';
514
543
 
515
- this.SetActive(tab, sheet === this.view.active_sheet);
544
+ this.SetActive(tab, sheet === this.view.active_sheet, user);
516
545
 
517
546
  const mousedown = (event: MouseEvent) => this.MouseDownTab(event, tab, sheet, index, tabs);
518
547