@trebco/treb 28.13.2 → 28.15.1

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.
@@ -38,7 +38,7 @@
38
38
  <div data-bind='message' class='treb-embed-dialog-message'></div>
39
39
  <div data-bind='about' class='treb-embed-dialog-body'></div>
40
40
  </div>
41
- <button type='button' title='Close dialog' data-bind='close' class='treb-close-box'>
41
+ <button type='button' data-title='close_dialog' data-bind='close' class='treb-close-box'>
42
42
  <svg viewBox='0 0 16 16'>
43
43
  <path d='M11.854 4.146a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708-.708l7-7a.5.5 0 0 1 .708 0z'/>
44
44
  <path d='M4.146 4.146a.5.5 0 0 0 0 .708l7 7a.5.5 0 0 0 .708-.708l-7-7a.5.5 0 0 0-.708 0z'/>
@@ -62,6 +62,9 @@
62
62
 
63
63
  <div class="treb-formula-bar notranslate" hidden>
64
64
  <div class="treb-address-label"><div></div></div>
65
+ <button class="treb-insert-function-button"
66
+ data-title="insert_function"
67
+ data-conditional="insert-function">𝑓<small>(x)</small></button>
65
68
  <div class="treb-editor-container">
66
69
  <div contenteditable="true"></div>
67
70
  </div>
@@ -95,7 +98,7 @@
95
98
  -->
96
99
 
97
100
  <!-- converted to button, more appropriate -->
98
- <button class="treb-delete-tab" title="Delete current sheet" data-command="delete-tab" data-conditional="delete-tab">
101
+ <button class="treb-delete-tab" data-title="delete_sheet" data-command="delete-tab" data-conditional="delete-tab">
99
102
  <svg tabindex="-1" viewbox='0 0 16 16'><path d='M4,4 L12,12 M12,4 L4,12'/></svg>
100
103
  </button>
101
104
 
@@ -105,7 +108,8 @@
105
108
  </div>
106
109
 
107
110
  <!-- converted to button, more appropriate -->
108
- <button class="treb-add-tab" data-command="add-tab" data-conditional="add-tab" title="Add sheet">+</button>
111
+ <button class="treb-add-tab" data-command="add-tab" data-conditional="add-tab"
112
+ data-title="add_sheet">+</button>
109
113
 
110
114
  <!--
111
115
  we removed the junk node with "flex grow" to split the layout, in
@@ -125,7 +129,8 @@
125
129
 
126
130
  <div class="treb-revert-indicator"
127
131
  data-command="revert-indicator"
128
- title="This document has been modified from the original version."></div>
132
+ data-title="document_modified"
133
+ ></div>
129
134
 
130
135
  </div> <!-- /treb-view -->
131
136
  </template>
@@ -133,13 +138,13 @@
133
138
  </div>
134
139
 
135
140
  <div class="treb-layout-sidebar treb-animate">
136
- <button data-command="recalculate" title="Recalculate"></button>
137
- <button data-command="toggle-toolbar" data-conditional="toolbar" title="Toggle toolbar"></button>
138
- <button data-command="export-xlsx" data-conditional="export" title="Export as XLSX"></button>
139
- <button data-command="revert" data-conditional="revert" title="Revert to original version"></button>
140
- <button data-command="about" title="What's this?"></button>
141
+ <button data-command="recalculate" data-title="recalculate"></button>
142
+ <button data-command="toggle-toolbar" data-conditional="toolbar" data-title="toggle_toolbar"></button>
143
+ <button data-command="export-xlsx" data-conditional="export" data-title="export"></button>
144
+ <button data-command="revert" data-conditional="revert" data-title="revert"></button>
145
+ <button data-command="about" data-title="about"></button>
141
146
  </div>
142
147
 
143
- <button class="treb-toggle-sidebar-button" title="Toggle sidebar"></button>
148
+ <button class="treb-toggle-sidebar-button" data-title="toggle_sidebar"></button>
144
149
 
145
150
  </div>
@@ -49,12 +49,12 @@
49
49
 
50
50
  <div class="group">
51
51
  <button data-command="wrap-text" title="Wrap text"></button>
52
- <button data-command="merge-cells" data-id="merge" data-title="Merge cells" data-active-title="Unmerge cells"></button>
53
- <button data-command="lock-cells" data-title="Lock cells" data-active-title="Unlock cells"></button>
54
- <button data-command="freeze-panes" data-title="Freeze panes" data-active-title="Unfreeze panes" freeze-button></button>
55
- <button data-command="insert-table" data-icon="table" data-title="Insert table" data-active-title="Remove table" table-button></button>
52
+ <button data-command="merge-cells" data-id="merge" data-inactive-title="Merge cells" data-active-title="Unmerge cells"></button>
53
+ <button data-command="lock-cells" data-inactive-title="Lock cells" data-active-title="Unlock cells"></button>
54
+ <button data-command="freeze-panes" data-inactive-title="Freeze panes" data-active-title="Unfreeze panes" freeze-button></button>
55
+ <button data-command="insert-table" data-icon="table" data-inactive-title="Insert table" data-active-title="Remove table" table-button></button>
56
56
  <div class="treb-menu">
57
- <button data-icon="comment" data-title="Comment" data-active-title="Update comment"></button>
57
+ <button data-icon="comment" data-inactive-title="Comment" data-active-title="Update comment"></button>
58
58
  <div class="treb-comment-box">
59
59
  <textarea></textarea>
60
60
  <div>
@@ -13,6 +13,23 @@ import type { ToolbarMessage } from '../toolbar-message';
13
13
 
14
14
  import { DOMContext } from 'treb-base-types';
15
15
 
16
+ /** with a view towards i18n */
17
+ const default_titles: Record<string, string> = {
18
+
19
+ close_dialog: 'Close dialog',
20
+ insert_function: 'Insert function...',
21
+ delete_sheet: 'Delete current sheet',
22
+ add_sheet: 'Add sheet',
23
+ document_modified: 'This document has been modified from the original version.',
24
+ recalculate: 'Recalculate',
25
+ toggle_toolbar: 'Toggle toolbar',
26
+ export: 'Export as XLSX',
27
+ revert: 'Revert to original version',
28
+ about: `What's this?`,
29
+ toggle_sidebar: 'Toggle sidebar',
30
+
31
+ };
32
+
16
33
  /** @internal */
17
34
  export class SpreadsheetConstructor<USER_DATA_TYPE = unknown> {
18
35
 
@@ -391,6 +408,7 @@ export class SpreadsheetConstructor<USER_DATA_TYPE = unknown> {
391
408
  'revert': !!sheet.options.revert_button,
392
409
  'toolbar': !!sheet.options.toolbar,
393
410
  'export': !!sheet.options.export,
411
+ 'insert-function': !!sheet.options.insert_function_button,
394
412
 
395
413
  // the following won't work as expected in split, because this
396
414
  // code won't be run when the new view is created -- do something
@@ -545,6 +563,24 @@ export class SpreadsheetConstructor<USER_DATA_TYPE = unknown> {
545
563
 
546
564
  }
547
565
 
566
+ // --- titles --------------------------------------------------------------
567
+
568
+ const elements = Array.from(this.layout_element.querySelectorAll('[data-title]'));
569
+ for (const element of elements) {
570
+ if (element instanceof HTMLElement) {
571
+
572
+ // temp workaround
573
+ if (element.dataset.activeTitle) {
574
+ continue;
575
+ }
576
+
577
+ if (element.dataset.title && default_titles[element.dataset.title]) {
578
+ element.title = default_titles[element.dataset.title];
579
+ }
580
+
581
+ }
582
+ }
583
+
548
584
  // --- animated ------------------------------------------------------------
549
585
 
550
586
  // requestAnimationFrame(() => {
@@ -580,8 +616,8 @@ export class SpreadsheetConstructor<USER_DATA_TYPE = unknown> {
580
616
  if (value) {
581
617
  // value.classList.remove('treb-active');
582
618
  value.removeAttribute('active');
583
- if (value.dataset.title) {
584
- value.title = value.dataset.title;
619
+ if (value.dataset.inactiveTitle) {
620
+ value.title = value.dataset.inactiveTitle;
585
621
  }
586
622
  }
587
623
  }
@@ -723,8 +723,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
723
723
 
724
724
  const grid_options: GridOptions = {
725
725
  // expand: false,
726
-
727
- insert_function_button: false, // do we have this?
726
+ // insert_function_button: false, // do we have this?
728
727
  in_cell_editor: true, // if this is always true, why is it an option?
729
728
  repaint_on_cell_change: false,
730
729
  scrollbars: this.options.scrollbars,
@@ -2078,6 +2077,14 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
2078
2077
  for (const format of sheet.conditional_formats) {
2079
2078
  format.internal = undefined;
2080
2079
  }
2080
+ for (const annotation of sheet.annotations) {
2081
+ if (annotation.view && annotation.data.type === 'textbox') {
2082
+ const view: AnnotationViewData = annotation.view[this.grid.view_index] || {};
2083
+ if (view.update_callback) {
2084
+ view.update_callback();
2085
+ }
2086
+ }
2087
+ }
2081
2088
  }
2082
2089
 
2083
2090
  this.calculator.UpdateConditionals();
@@ -5349,6 +5356,63 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
5349
5356
  }
5350
5357
 
5351
5358
  }
5359
+ else if (annotation.data.type === 'textbox') {
5360
+
5361
+ if (annotation.data.data) {
5362
+ const container = document.createElement('div');
5363
+ container.classList.add('treb-annotation-textbox');
5364
+
5365
+ for (const paragraph of annotation.data.data.paragraphs) {
5366
+ const p = document.createElement('p');
5367
+ if (paragraph.style) {
5368
+ if (paragraph.style.horizontal_align === 'right' ||
5369
+ paragraph.style.horizontal_align === 'center') {
5370
+ p.style.textAlign = paragraph.style.horizontal_align;
5371
+ }
5372
+ }
5373
+
5374
+ for (const entry of paragraph.content) {
5375
+ const span = document.createElement('span');
5376
+ span.textContent = entry.text || '';
5377
+ if (entry.style?.bold) {
5378
+ span.style.fontWeight = '600';
5379
+ }
5380
+ if (entry.style?.italic) {
5381
+ span.style.fontStyle = 'italic';
5382
+ }
5383
+ p.appendChild(span);
5384
+ }
5385
+ container.append(p);
5386
+ }
5387
+
5388
+ view.content_node.append(container);
5389
+
5390
+ if (annotation.data.data.style) {
5391
+
5392
+ const style = annotation.data.data.style;
5393
+ const update_textbox = () => {
5394
+
5395
+ if (style.fill) {
5396
+ const color = ThemeColor2(this.grid.theme, style.fill);
5397
+ container.style.background = color;
5398
+ }
5399
+
5400
+ };
5401
+
5402
+ view.update_callback = () => {
5403
+ if (!this.grid.headless) {
5404
+ update_textbox();
5405
+ }
5406
+ };
5407
+
5408
+ update_textbox();
5409
+
5410
+ }
5411
+
5412
+ }
5413
+
5414
+ }
5415
+
5352
5416
  }
5353
5417
  }
5354
5418
 
@@ -311,6 +311,11 @@ export interface EmbeddedSpreadsheetOptions {
311
311
  */
312
312
  recalculate_on_f9?: boolean;
313
313
 
314
+ /**
315
+ * @internal
316
+ */
317
+ insert_function_button?: boolean;
318
+
314
319
  }
315
320
 
316
321
  /**
@@ -25,11 +25,10 @@
25
25
 
26
26
  grid-area: 1/1/2/2;
27
27
 
28
- // min-height: 3em; // FIXME
29
-
30
28
  display: flex;
31
29
  flex-direction: row;
32
30
  text-align: left;
31
+ gap: .5em;
33
32
 
34
33
  &[hidden] {
35
34
  display: none;
@@ -39,7 +38,6 @@
39
38
 
40
39
  max-width: 100%;
41
40
  overflow-x: hidden;
42
- /* last ditch for IE11 */
43
41
 
44
42
  /** label for selection address */
45
43
  .treb-address-label {
@@ -56,7 +54,7 @@
56
54
  justify-content: center;
57
55
  padding-left: 3px;
58
56
 
59
- margin-right: 6px;
57
+ // margin-right: 6px;
60
58
 
61
59
  &>div {
62
60
  outline: none;
@@ -66,6 +64,20 @@
66
64
  }
67
65
  }
68
66
 
67
+ .treb-insert-function-button {
68
+ min-height: 1.5em;
69
+ height: 1.75em;
70
+ border: 1px solid var(--treb-formula-bar-border-color, var(--treb-ui-border-color, #ccc));
71
+ border-radius: 2px;
72
+ background: transparent;
73
+
74
+ &[hidden] {
75
+ display: none;
76
+ }
77
+
78
+ }
79
+
80
+ /** expand formula box -- caret/arrow */
69
81
  .expand-button {
70
82
  background: transparent;
71
83
  border: 0;
@@ -160,7 +172,7 @@
160
172
 
161
173
  }
162
174
 
163
- /** f(x) button, optional */
175
+ /* * f(x) button, optional * /
164
176
  .formula-button {
165
177
  border: 1px solid #ccc;
166
178
  border-radius: 2px;
@@ -188,8 +200,9 @@
188
200
  }
189
201
 
190
202
  }
191
- }
203
+ */
204
+
192
205
 
193
-
206
+ }
194
207
 
195
208
  }
@@ -519,4 +519,24 @@ $text-reference-color-5: rgb(254, 47, 1);
519
519
  font-family: var(--treb-grid-font-family-osx, var(--treb-grid-font-family, inherit));
520
520
  }
521
521
 
522
+ /**
523
+ *
524
+ */
525
+ .treb-annotation-textbox {
526
+ background: Canvas;
527
+ position: relative;
528
+ top: 0px;
529
+ left: 0px;
530
+ overflow: hidden;
531
+ width: 100%;
532
+ height: 100%;
533
+ padding: .5em; // ?
534
+
535
+ p {
536
+ margin-block-start: 0;
537
+ margin-block-end: 0;
538
+ }
539
+
540
+ }
541
+
522
542
  }
@@ -224,9 +224,10 @@ export class Exporter {
224
224
  },
225
225
  };
226
226
 
227
- if (xf.horizontal_alignment || xf.vertical_alignment || xf.wrap_text) {
227
+ if (xf.horizontal_alignment || xf.vertical_alignment || xf.wrap_text || xf.indent) {
228
228
  // block.a$.applyAlignment = 1;
229
229
  block.alignment = { a$: {}};
230
+
230
231
  if (xf.horizontal_alignment) {
231
232
  block.alignment.a$.horizontal = xf.horizontal_alignment;
232
233
  }
@@ -236,6 +237,10 @@ export class Exporter {
236
237
  if (xf.wrap_text) {
237
238
  block.alignment.a$.wrapText = 1;
238
239
  }
240
+ if (xf.indent && xf.horizontal_alignment !== 'center') {
241
+ block.alignment.a$.indent = xf.indent;
242
+ }
243
+
239
244
  }
240
245
 
241
246
  return block;
@@ -24,7 +24,7 @@
24
24
  import UZip from 'uzip';
25
25
  import Base64JS from 'base64-js';
26
26
 
27
- import type { AnchoredChartDescription, AnchoredImageDescription} from './workbook2';
27
+ import type { AnchoredChartDescription, AnchoredImageDescription, AnchoredTextBoxDescription} from './workbook2';
28
28
  import { ChartType, ConditionalFormatOperators, Workbook } from './workbook2';
29
29
  import type { ParseResult } from 'treb-parser';
30
30
  import { Parser } from 'treb-parser';
@@ -93,7 +93,7 @@ export class Importer {
93
93
  ref?: string;
94
94
  },
95
95
  };
96
- }, // ElementTree.Element,
96
+ },
97
97
  shared_formulae: SharedFormulaMap,
98
98
  arrays: RangeType[],
99
99
  merges: RangeType[],
@@ -117,11 +117,9 @@ export class Importer {
117
117
  // console.info(element);
118
118
 
119
119
  let value: undefined | number | boolean | string;
120
- // let type: ValueType = ValueType.undefined;
121
120
  let type: SerializedValueType = 'undefined';
122
121
 
123
122
  let calculated_value: undefined | number | boolean | string;
124
- // let calculated_type: ValueType = ValueType.undefined;
125
123
  let calculated_type: SerializedValueType = 'undefined';
126
124
 
127
125
  // QUESTIONS:
@@ -170,8 +168,13 @@ export class Importer {
170
168
  const parse_result = this.parser.Parse(formula); // l10n?
171
169
  if (parse_result.expression) {
172
170
  this.parser.Walk(parse_result.expression, (unit) => {
173
- if (unit.type === 'call' && /^_xll\./.test(unit.name)) {
174
- unit.name = unit.name.substr(5);
171
+ if (unit.type === 'call') {
172
+ if (/^_xll\./.test(unit.name)) {
173
+ unit.name = unit.name.substring(5);
174
+ }
175
+ else if (/^_xlfn\./.test(unit.name)) {
176
+ unit.name = unit.name.substring(6);
177
+ }
175
178
  }
176
179
  return true;
177
180
  });
@@ -390,6 +393,23 @@ export class Importer {
390
393
 
391
394
  case 'expression':
392
395
  if (rule.formula) {
396
+
397
+ if (typeof rule.formula !== 'string') {
398
+ if (rule.formula.t$) {
399
+
400
+ // the only case (to date) we've seen here is that the attribute
401
+ // is "xml:space=preserve", which we can ignore (are you sure?)
402
+ // (should we check that?)
403
+
404
+ rule.formula = rule.formula.t$;
405
+
406
+ }
407
+ else {
408
+ console.info("unexpected conditional expression", {rule});
409
+
410
+ }
411
+ }
412
+
393
413
  let style = {};
394
414
 
395
415
  if (rule.a$.dxfId) {
@@ -498,16 +518,25 @@ export class Importer {
498
518
  const conditional_formatting = FindAll('worksheet/conditionalFormatting');
499
519
  for (const element of conditional_formatting) {
500
520
  if (element.a$?.sqref ){
501
- const area = sheet.TranslateAddress(element.a$.sqref);
502
- if (element.cfRule) {
503
- const rules = Array.isArray(element.cfRule) ? element.cfRule : [element.cfRule];
504
- for (const rule of rules) {
505
- const format = this.ParseConditionalFormat(area, rule);
506
- if (format) {
507
- conditional_formats.push(format);
521
+
522
+ // FIXME: this attribute might include multiple ranges? e.g.:
523
+ //
524
+ // <conditionalFormatting sqref="B31:I31 B10:E30 G10:I30 F14:F15">
525
+
526
+ const parts = element.a$.sqref.split(/\s+/);
527
+ for (const part of parts) {
528
+ const area = sheet.TranslateAddress(part);
529
+ if (element.cfRule) {
530
+ const rules = Array.isArray(element.cfRule) ? element.cfRule : [element.cfRule];
531
+ for (const rule of rules) {
532
+ const format = this.ParseConditionalFormat(area, rule);
533
+ if (format) {
534
+ conditional_formats.push(format);
535
+ }
508
536
  }
509
537
  }
510
538
  }
539
+
511
540
  }
512
541
  }
513
542
 
@@ -808,6 +837,7 @@ export class Importer {
808
837
  const drawings = FindAll('worksheet/drawing');
809
838
  const chart_descriptors: AnchoredChartDescription[] = [];
810
839
  const image_descriptors: AnchoredImageDescription[] = [];
840
+ const textbox_descriptors: AnchoredTextBoxDescription[] = [];
811
841
 
812
842
  for (const child of drawings) {
813
843
 
@@ -832,6 +862,9 @@ export class Importer {
832
862
  case 'image':
833
863
  image_descriptors.push(entry);
834
864
  break;
865
+ case 'textbox':
866
+ textbox_descriptors.push(entry);
867
+ break;
835
868
  }
836
869
  }
837
870
  }
@@ -873,6 +906,26 @@ export class Importer {
873
906
 
874
907
  };
875
908
 
909
+ for (const descriptor of textbox_descriptors) {
910
+
911
+ const layout: AnnotationLayout = {
912
+ tl: AnchorToCorner(descriptor.anchor.from),
913
+ br: AnchorToCorner(descriptor.anchor.to),
914
+ };
915
+
916
+ // console.info({descriptor});
917
+
918
+ annotations.push({
919
+ layout,
920
+ type: 'textbox',
921
+ data: {
922
+ style: descriptor.style,
923
+ paragraphs: descriptor.paragraphs,
924
+ }
925
+ });
926
+
927
+ }
928
+
876
929
  for (const descriptor of image_descriptors) {
877
930
  if (descriptor && descriptor.image) {
878
931
 
@@ -44,7 +44,7 @@ export class SharedStrings {
44
44
  // <t>text here!</t>
45
45
  // </si>
46
46
 
47
- if (si.t) {
47
+ if (si.t !== undefined) {
48
48
 
49
49
  // seen recently in the wild, text with leading (or trailing) spaces
50
50
  // has an attribute xml:space=preserve (which makes sense, but was not