@trebco/treb 27.12.2 → 28.2.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/README.md +6 -0
  2. package/dist/treb-spreadsheet-light.mjs +16 -0
  3. package/dist/treb-spreadsheet.mjs +13 -11
  4. package/dist/treb.d.ts +33 -5
  5. package/esbuild-custom-element.mjs +3 -1
  6. package/package.json +8 -6
  7. package/treb-base-types/src/dom-utilities.ts +157 -19
  8. package/treb-base-types/src/import.ts +1 -0
  9. package/treb-base-types/src/theme.ts +5 -4
  10. package/treb-charts/src/renderer.ts +4 -58
  11. package/treb-embed/markup/layout.html +4 -0
  12. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +131 -87
  13. package/treb-embed/src/embedded-spreadsheet.ts +153 -140
  14. package/treb-embed/src/options.ts +9 -0
  15. package/treb-embed/src/spinner.ts +5 -3
  16. package/treb-embed/src/toolbar-message.ts +5 -0
  17. package/treb-embed/style/layout.scss +65 -1
  18. package/treb-export/src/export-worker/export-worker.ts +7 -12
  19. package/treb-export/src/export2.ts +57 -33
  20. package/treb-export/src/import2.ts +61 -21
  21. package/treb-export/src/workbook2.ts +69 -24
  22. package/treb-export/src/zip-wrapper.ts +96 -0
  23. package/treb-grid/src/editors/autocomplete.ts +24 -13
  24. package/treb-grid/src/editors/editor.ts +43 -139
  25. package/treb-grid/src/editors/external_editor.ts +1 -1
  26. package/treb-grid/src/editors/formula_bar.ts +24 -24
  27. package/treb-grid/src/editors/overlay_editor.ts +1 -1
  28. package/treb-grid/src/layout/base_layout.ts +34 -25
  29. package/treb-grid/src/layout/grid_layout.ts +20 -20
  30. package/treb-grid/src/render/selection-renderer.ts +3 -3
  31. package/treb-grid/src/render/svg_header_overlay.ts +6 -4
  32. package/treb-grid/src/render/svg_selection_block.ts +10 -7
  33. package/treb-grid/src/types/annotation.ts +2 -2
  34. package/treb-grid/src/types/grid.ts +80 -81
  35. package/treb-grid/src/types/scale-control.ts +69 -81
  36. package/treb-grid/src/types/sheet.ts +3 -52
  37. package/treb-grid/src/types/tab_bar.ts +27 -13
  38. package/treb-grid/src/util/fontmetrics2.ts +24 -21
  39. package/treb-utils/src/event_source.ts +23 -23
  40. package/treb-utils/src/index.ts +2 -2
  41. package/treb-utils/src/measurement.ts +24 -24
  42. package/treb-utils/src/serialize_html.ts +25 -21
  43. package/treb-utils/src/dispatch.ts +0 -57
  44. package/treb-utils/src/resizable.ts +0 -159
@@ -11,55 +11,7 @@ import { ColorFunctions, type Color } from 'treb-base-types';
11
11
  import { Measurement } from 'treb-utils';
12
12
  import type { ToolbarMessage } from '../toolbar-message';
13
13
 
14
- interface ElementOptions {
15
- data: Record<string, string>;
16
- text: string;
17
- style: string;
18
- title: string;
19
- classes: string|string[];
20
- }
21
-
22
- /**
23
- * FIXME: unify this with DOMUtils
24
- */
25
- const Element = <K extends keyof HTMLElementTagNameMap>(tag: K, parent?: HTMLElement|DocumentFragment, options: Partial<ElementOptions> = {}, attrs: Record<string, string> = {}): HTMLElementTagNameMap[K] => {
26
- const element = document.createElement(tag);
27
- if (options.classes) {
28
-
29
- // you can't use an array destructure in a ternary expression? TIL
30
-
31
- if (Array.isArray(options.classes)) {
32
- element.classList.add(...options.classes);
33
- }
34
- else {
35
- element.classList.add(options.classes);
36
- }
37
-
38
- }
39
- if (options.title) {
40
- element.title = options.title;
41
- }
42
- if (options.text) {
43
- element.textContent = options.text;
44
- }
45
- if (options.style) {
46
- element.setAttribute('style', options.style);
47
- }
48
- if (options.data) {
49
- for (const [key, value] of Object.entries(options.data)) {
50
- element.dataset[key] = value;
51
- }
52
- }
53
-
54
- for (const [key, value] of Object.entries(attrs)) {
55
- element.setAttribute(key, value);
56
- }
57
-
58
- if (parent) {
59
- parent.appendChild(element);
60
- }
61
- return element;
62
- }
14
+ import { DOMContext } from 'treb-base-types';
63
15
 
64
16
  /** @internal */
65
17
  export class SpreadsheetConstructor {
@@ -82,6 +34,18 @@ export class SpreadsheetConstructor {
82
34
  /** root layout element */
83
35
  protected layout_element?: HTMLElement;
84
36
 
37
+ /** views container */
38
+ protected views?: HTMLElement;
39
+
40
+ /**
41
+ * handle to the revert button, so we can adjust it. we can use
42
+ * container classes for the most part but we are updating the title.
43
+ * (FIXME: double-up the button, no reference required)
44
+ */
45
+ protected revert_button?: HTMLElement;
46
+
47
+ protected revert_state = false;
48
+
85
49
  /** cached controls */
86
50
  protected toolbar_controls: Record<string, HTMLElement> = {};
87
51
 
@@ -91,6 +55,8 @@ export class SpreadsheetConstructor {
91
55
  other?: HTMLDivElement,
92
56
  } = {};
93
57
 
58
+ protected DOM: DOMContext;
59
+
94
60
  //
95
61
 
96
62
  constructor(root?: HTMLElement|string) {
@@ -99,19 +65,22 @@ export class SpreadsheetConstructor {
99
65
  root = document.querySelector(root) as HTMLElement;
100
66
  }
101
67
 
68
+ this.DOM = DOMContext.GetInstance(root?.ownerDocument);
69
+
102
70
  // there's a possibility this could be running in a node environment.
103
71
  // in that case (wihtout a shim) HTMLElement will not exist, so we can't
104
72
  // check type.
105
73
 
106
- if (typeof HTMLElement !== 'undefined' && root instanceof HTMLElement) {
107
- this.root = root;
74
+ // but in that case what would root be? (...)
108
75
 
109
- const style_node = document.head.querySelector('style[treb-stylesheet]');
76
+ if (this.DOM.view && root instanceof this.DOM.view.HTMLElement) {
77
+
78
+ this.root = root;
79
+
80
+ const style_node = this.DOM.doc?.head.querySelector('style[treb-stylesheet]');
110
81
  if (!style_node) {
111
- const style = Element('style');
112
- style.setAttribute('treb-stylesheet', '');
113
- style.textContent = css;
114
- document.head.prepend(style);
82
+ this.DOM.doc?.head.prepend(
83
+ this.DOM.Create('style', undefined, undefined, { text: css, attrs: { 'treb-stylesheet': '' } }));
115
84
  }
116
85
 
117
86
  }
@@ -273,7 +242,7 @@ export class SpreadsheetConstructor {
273
242
  if (this.root.hasAttribute('inline-document')) {
274
243
  const inline_name = this.root.getAttribute('inline-document') || '';
275
244
  for (const element of Array.from(this.root.children)) {
276
- if (element instanceof HTMLScriptElement) {
245
+ if (this.DOM.view && element instanceof this.DOM.view.HTMLScriptElement) {
277
246
  if (element.type === 'application/json') {
278
247
  const name = element.getAttribute('name') || '';
279
248
 
@@ -367,6 +336,22 @@ export class SpreadsheetConstructor {
367
336
  this.layout_element?.setAttribute('collapsed', '');
368
337
  }
369
338
 
339
+ // --- revert indicator ----------------------------------------------------
340
+
341
+ const revert_indicator = root.querySelector('[data-command=revert-indicator]');
342
+ if (this.DOM.view && revert_indicator instanceof this.DOM.view.HTMLElement) {
343
+ if (sheet.options.revert_indicator) {
344
+ revert_indicator.addEventListener('click', () => {
345
+ sheet.HandleToolbarMessage({
346
+ command: 'revert-indicator',
347
+ });
348
+ });
349
+ }
350
+ else {
351
+ revert_indicator.style.display = 'none';
352
+ }
353
+ }
354
+
370
355
  // --- toolbar/sidebar -----------------------------------------------------
371
356
 
372
357
  const sidebar = root.querySelector('.treb-layout-sidebar');
@@ -439,6 +424,10 @@ export class SpreadsheetConstructor {
439
424
  }
440
425
  }
441
426
 
427
+ if (sheet.options.revert_button) {
428
+ this.revert_button = this.layout_element.querySelector('[data-command=revert]') || undefined;
429
+ }
430
+
442
431
  // --- resize --------------------------------------------------------------
443
432
 
444
433
  if (sheet.options.resizable) {
@@ -448,7 +437,7 @@ export class SpreadsheetConstructor {
448
437
  const delta = { x: 0, y: 0 };
449
438
 
450
439
  // const resize_container = root.querySelector('.treb-layout-resize-container');
451
- const views = root.querySelector('.treb-views');
440
+ this.views = root.querySelector('.treb-views') || undefined;
452
441
 
453
442
  let mask: HTMLElement|undefined;
454
443
  let resizer: HTMLElement|undefined;
@@ -456,10 +445,10 @@ export class SpreadsheetConstructor {
456
445
  const resize_handle = root.querySelector('.treb-layout-resize-handle') as HTMLElement;
457
446
 
458
447
  // mouse up handler added to mask (when created)
459
- const mouse_up = () => finish();
448
+ const mouseup = () => finish();
460
449
 
461
450
  // mouse move handler added to mask (when created)
462
- const mouse_move = ((event: MouseEvent) => {
451
+ const mousemove = ((event: MouseEvent) => {
463
452
  if (event.buttons === 0) {
464
453
  finish();
465
454
  }
@@ -480,13 +469,15 @@ export class SpreadsheetConstructor {
480
469
 
481
470
  if (delta.x || delta.y) {
482
471
  const rect = root.getBoundingClientRect();
483
- root.style.width = (rect.width + delta.x) + 'px';
472
+ if (!sheet.options.constrain_width) {
473
+ root.style.width = (rect.width + delta.x) + 'px';
474
+ }
484
475
  root.style.height = (rect.height + delta.y) + 'px';
485
476
  }
486
477
 
487
478
  if (mask) {
488
- mask.removeEventListener('mouseup', mouse_up);
489
- mask.removeEventListener('mousemove', mouse_move);
479
+ mask.removeEventListener('mouseup', mouseup);
480
+ mask.removeEventListener('mousemove', mousemove);
490
481
  mask.parentElement?.removeChild(mask);
491
482
  mask = undefined;
492
483
  }
@@ -503,15 +494,17 @@ export class SpreadsheetConstructor {
503
494
 
504
495
  const resize_parent = root.querySelector('.treb-main') as HTMLElement; // was document.body
505
496
 
506
- resizer = Element('div', resize_parent, { classes: 'treb-resize-rect' });
497
+ resizer = this.DOM.Div('treb-resize-rect', resize_parent);
507
498
 
508
- mask = Element('div', resize_parent, {
509
- classes: 'treb-resize-mask',
510
- style: 'cursor: nw-resize;',
499
+ mask = this.DOM.Div('treb-resize-mask', resize_parent, {
500
+ attrs: {
501
+ style: 'cursor: nw-resize;'
502
+ },
503
+ events: { mouseup, mousemove },
511
504
  });
512
505
 
513
- mask.addEventListener('mouseup', mouse_up);
514
- mask.addEventListener('mousemove', mouse_move);
506
+ // mask.addEventListener('mouseup', mouse_up);
507
+ // mask.addEventListener('mousemove', mouse_move);
515
508
 
516
509
  // resize_handle.classList.add('retain-opacity'); // we're not using this anymore
517
510
 
@@ -521,7 +514,7 @@ export class SpreadsheetConstructor {
521
514
  delta.x = 0;
522
515
  delta.y = 0;
523
516
 
524
- const layouts = views?.querySelectorAll('.treb-spreadsheet-body');
517
+ const layouts = this.views?.querySelectorAll('.treb-spreadsheet-body');
525
518
  const rects = Array.from(layouts||[]).map(element => element.getBoundingClientRect());
526
519
  if (rects.length) {
527
520
 
@@ -688,7 +681,7 @@ export class SpreadsheetConstructor {
688
681
 
689
682
  {
690
683
 
691
- let fragment = document.createDocumentFragment();
684
+ let fragment = this.DOM.Fragment();
692
685
 
693
686
  const length = sheet.document_styles.theme_colors.length;
694
687
  const themes = ['Background', 'Text', 'Background', 'Text', 'Accent'];
@@ -718,7 +711,12 @@ export class SpreadsheetConstructor {
718
711
  }
719
712
 
720
713
  }
721
- Element('button', fragment, { style, title, data: { command: 'set-color', color: JSON.stringify(entry.color) } });
714
+
715
+ this.DOM.Create('button', undefined, fragment, {
716
+ attrs: { style, title },
717
+ data: { command: 'set-color', color: JSON.stringify(entry.color) },
718
+ });
719
+
722
720
  }
723
721
  }
724
722
 
@@ -726,12 +724,11 @@ export class SpreadsheetConstructor {
726
724
 
727
725
  this.swatch_lists.theme?.replaceChildren(fragment);
728
726
 
729
- fragment = document.createDocumentFragment();
730
- Element('button', fragment, {
731
- classes: 'treb-default-color',
732
- title: 'Default color',
727
+ fragment = this.DOM.Fragment();
728
+ this.DOM.Create('button', 'treb-default-color', fragment, {
729
+ attrs: { title: 'Default color' },
733
730
  data: { command: 'set-color', color: JSON.stringify({}) },
734
- });
731
+ });
735
732
 
736
733
  const colors = ['Black', 'White', 'Gray', 'Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Violet'];
737
734
 
@@ -742,7 +739,12 @@ export class SpreadsheetConstructor {
742
739
 
743
740
  for (const text of [...colors, ...additional_colors]) {
744
741
  const style = `background: ${text.toLowerCase()};`;
745
- Element('button', fragment, { style, title: text, data: { command: 'set-color', color: JSON.stringify({text: text.toLowerCase()})}});
742
+ this.DOM.Create('button', undefined, fragment, {
743
+ attrs: { style, title: text, },
744
+ data: { command: 'set-color', color: JSON.stringify({text: text.toLowerCase()})},
745
+ });
746
+
747
+ // Element('button', fragment, { style, title: text, data: { command: 'set-color', color: JSON.stringify({text: text.toLowerCase()})}});
746
748
  }
747
749
 
748
750
  this.swatch_lists.other?.replaceChildren(fragment);
@@ -770,16 +772,15 @@ export class SpreadsheetConstructor {
770
772
  }
771
773
  }
772
774
 
773
- const Button = (format: string) => {
774
- return Element('button', undefined, {
775
- text: format, data: { format, command: 'number-format' },
775
+ const Button = (format: string) =>
776
+ this.DOM.Create('button', undefined, undefined, {
777
+ text: format,
778
+ data: { format, command: 'number-format' },
776
779
  });
777
- };
778
780
 
779
- const fragment = document.createDocumentFragment();
781
+ const fragment = this.DOM.Fragment();
780
782
  fragment.append(...number_formats.map(format => Button(format)));
781
-
782
- fragment.append(Element('div', undefined, {}, {separator: ''}));
783
+ fragment.append(this.DOM.Div(undefined, undefined, { attrs: { separator: '' }}));
783
784
  fragment.append(...date_formats.map(format => Button(format)));
784
785
 
785
786
  format_menu.textContent = '';
@@ -787,6 +788,48 @@ export class SpreadsheetConstructor {
787
788
 
788
789
  }
789
790
 
791
+ /**
792
+ * setting explicit state on the revert button (if enabled).
793
+ *
794
+ * @param sheet
795
+ */
796
+ public UpdateRevertState(sheet: EmbeddedSpreadsheet) {
797
+
798
+ const state = sheet.can_revert;
799
+
800
+ if (this.revert_state === state) {
801
+ return; // nothing to do
802
+ }
803
+
804
+ this.revert_state = state;
805
+
806
+ if (this.revert_button || sheet.options.revert_indicator) {
807
+
808
+ if (this.revert_state) {
809
+ this.views?.classList.add('treb-can-revert');
810
+ }
811
+ else {
812
+ this.views?.classList.remove('treb-can-revert');
813
+ }
814
+
815
+ if (this.revert_button) {
816
+ this.revert_button.dataset.canRevert = state ? 'true' : 'false'; // FIXME: remove
817
+
818
+ // FIXME: container classes, double up button
819
+
820
+ if (state) {
821
+ this.revert_button.classList.remove('sidebar-disabled');
822
+ this.revert_button.title = 'Revert to original version'; // FIXME: strings
823
+ }
824
+ else {
825
+ this.revert_button.classList.add('sidebar-disabled');
826
+ this.revert_button.title = 'This is the original version of the document'; // FIXME: strings
827
+ }
828
+ }
829
+ }
830
+
831
+ }
832
+
790
833
  /**
791
834
  * replace a given template with its contents.
792
835
  */
@@ -1133,8 +1176,8 @@ export class SpreadsheetConstructor {
1133
1176
 
1134
1177
  if (/firefox/i.test(navigator.userAgent)) {
1135
1178
  scroller.addEventListener('scroll', () => {
1136
- if (document.activeElement instanceof HTMLElement ) {
1137
- document.activeElement.blur();
1179
+ if (this.DOM.view && this.DOM.doc?.activeElement instanceof this.DOM.view.HTMLElement ) {
1180
+ this.DOM.doc.activeElement?.blur();
1138
1181
  }
1139
1182
  });
1140
1183
  }
@@ -1288,6 +1331,7 @@ export class SpreadsheetConstructor {
1288
1331
  case 'reset':
1289
1332
  this.UpdateDocumentStyles(sheet, format_menu);
1290
1333
  this.UpdateSelectionStyle(sheet, toolbar, comment_box);
1334
+ this.UpdateRevertState(sheet);
1291
1335
  break;
1292
1336
 
1293
1337
  case 'selection':