@trebco/treb 28.13.2 → 28.15.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.
@@ -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';
@@ -170,8 +170,13 @@ export class Importer {
170
170
  const parse_result = this.parser.Parse(formula); // l10n?
171
171
  if (parse_result.expression) {
172
172
  this.parser.Walk(parse_result.expression, (unit) => {
173
- if (unit.type === 'call' && /^_xll\./.test(unit.name)) {
174
- unit.name = unit.name.substr(5);
173
+ if (unit.type === 'call') {
174
+ if (/^_xll\./.test(unit.name)) {
175
+ unit.name = unit.name.substring(5);
176
+ }
177
+ else if (/^_xlfn\./.test(unit.name)) {
178
+ unit.name = unit.name.substring(6);
179
+ }
175
180
  }
176
181
  return true;
177
182
  });
@@ -390,6 +395,20 @@ export class Importer {
390
395
 
391
396
  case 'expression':
392
397
  if (rule.formula) {
398
+
399
+ if (typeof rule.formula !== 'string') {
400
+ console.info("conditional expression", {rule});
401
+ if (rule.formula.t$) {
402
+
403
+ // the only case (to date) we've seen here is that the attribute
404
+ // is "xml:space=preserve", which we can ignore (are you sure?)
405
+ // (should we check that?)
406
+
407
+ rule.formula = rule.formula.t$;
408
+
409
+ }
410
+ }
411
+
393
412
  let style = {};
394
413
 
395
414
  if (rule.a$.dxfId) {
@@ -498,6 +517,26 @@ export class Importer {
498
517
  const conditional_formatting = FindAll('worksheet/conditionalFormatting');
499
518
  for (const element of conditional_formatting) {
500
519
  if (element.a$?.sqref ){
520
+
521
+ // FIXME: this attribute might include multiple ranges? e.g.:
522
+ //
523
+ // <conditionalFormatting sqref="B31:I31 B10:E30 G10:I30 F14:F15">
524
+
525
+ const parts = element.a$.sqref.split(/\s+/);
526
+ for (const part of parts) {
527
+ const area = sheet.TranslateAddress(part);
528
+ if (element.cfRule) {
529
+ const rules = Array.isArray(element.cfRule) ? element.cfRule : [element.cfRule];
530
+ for (const rule of rules) {
531
+ const format = this.ParseConditionalFormat(area, rule);
532
+ if (format) {
533
+ conditional_formats.push(format);
534
+ }
535
+ }
536
+ }
537
+ }
538
+
539
+ /*
501
540
  const area = sheet.TranslateAddress(element.a$.sqref);
502
541
  if (element.cfRule) {
503
542
  const rules = Array.isArray(element.cfRule) ? element.cfRule : [element.cfRule];
@@ -508,6 +547,7 @@ export class Importer {
508
547
  }
509
548
  }
510
549
  }
550
+ */
511
551
  }
512
552
  }
513
553
 
@@ -808,6 +848,7 @@ export class Importer {
808
848
  const drawings = FindAll('worksheet/drawing');
809
849
  const chart_descriptors: AnchoredChartDescription[] = [];
810
850
  const image_descriptors: AnchoredImageDescription[] = [];
851
+ const textbox_descriptors: AnchoredTextBoxDescription[] = [];
811
852
 
812
853
  for (const child of drawings) {
813
854
 
@@ -832,6 +873,9 @@ export class Importer {
832
873
  case 'image':
833
874
  image_descriptors.push(entry);
834
875
  break;
876
+ case 'textbox':
877
+ textbox_descriptors.push(entry);
878
+ break;
835
879
  }
836
880
  }
837
881
  }
@@ -873,6 +917,26 @@ export class Importer {
873
917
 
874
918
  };
875
919
 
920
+ for (const descriptor of textbox_descriptors) {
921
+
922
+ const layout: AnnotationLayout = {
923
+ tl: AnchorToCorner(descriptor.anchor.from),
924
+ br: AnchorToCorner(descriptor.anchor.to),
925
+ };
926
+
927
+ // console.info({descriptor});
928
+
929
+ annotations.push({
930
+ layout,
931
+ type: 'textbox',
932
+ data: {
933
+ style: descriptor.style,
934
+ paragraphs: descriptor.paragraphs,
935
+ }
936
+ });
937
+
938
+ }
939
+
876
940
  for (const descriptor of image_descriptors) {
877
941
  if (descriptor && descriptor.image) {
878
942
 
@@ -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
@@ -76,6 +76,7 @@ export interface CellXf {
76
76
  horizontal_alignment?: string;
77
77
  vertical_alignment?: string;
78
78
  xfid?: number;
79
+ indent?: number;
79
80
 
80
81
  // FIXME // apply_font?: boolean;
81
82
  // FIXME // apply_border?: boolean;
@@ -157,6 +158,7 @@ export interface StyleOptions {
157
158
  vertical_alignment?: string;
158
159
  wrap?: boolean;
159
160
  fill?: Fill;
161
+ indent?: number;
160
162
  }
161
163
 
162
164
 
@@ -434,6 +436,8 @@ export class StyleCache {
434
436
  break;
435
437
  }
436
438
 
439
+ options.indent = composite.indent;
440
+
437
441
  if (composite.fill) {
438
442
  fill.pattern_type = 'solid';
439
443
  if (composite.fill.text) {
@@ -663,6 +667,10 @@ export class StyleCache {
663
667
  break;
664
668
  }
665
669
 
670
+ // indent
671
+
672
+ props.indent = xf.indent;
673
+
666
674
  // wrap
667
675
 
668
676
  if (xf.wrap_text) {
@@ -1091,6 +1099,7 @@ export class StyleCache {
1091
1099
  xf.border === border_index &&
1092
1100
  xf.number_format === number_format_index &&
1093
1101
  !!xf.wrap_text === !!options.wrap &&
1102
+ xf.indent === options.indent &&
1094
1103
  ((!options.horizontal_alignment && !xf.horizontal_alignment) || options.horizontal_alignment === xf.horizontal_alignment) &&
1095
1104
  ((!options.vertical_alignment && !xf.vertical_alignment) || options.vertical_alignment === xf.vertical_alignment)) {
1096
1105
 
@@ -1106,6 +1115,7 @@ export class StyleCache {
1106
1115
  fill: fill_index,
1107
1116
  border: border_index,
1108
1117
  number_format: number_format_index,
1118
+ indent: options.indent,
1109
1119
  };
1110
1120
 
1111
1121
  if (options.horizontal_alignment) {
@@ -1120,44 +1130,6 @@ export class StyleCache {
1120
1130
 
1121
1131
  this.cell_xfs.push(new_xf);
1122
1132
 
1123
- /*
1124
-
1125
- // add the node structure
1126
-
1127
- if (!this.dom) throw new Error('missing dom');
1128
- const xfs = this.dom.find('./cellXfs');
1129
-
1130
- if (!xfs) throw new Error('xfs not found');
1131
- xfs.attrib.count = (Number(xfs.attrib.count || 0) + 1).toString();
1132
-
1133
- const new_element = Element('xf', {
1134
- borderId: new_xf.border.toString(),
1135
- fillId: new_xf.fill.toString(),
1136
- fontId: new_xf.font.toString(),
1137
- numFmtId: new_xf.number_format.toString(),
1138
- });
1139
-
1140
- if (new_xf.horizontal_alignment || new_xf.vertical_alignment) {
1141
- const attrs: {[index: string]: string} = {};
1142
- if (new_xf.horizontal_alignment) {
1143
- attrs.horizontal = new_xf.horizontal_alignment;
1144
- }
1145
- if (new_xf.vertical_alignment) {
1146
- attrs.vertical = new_xf.vertical_alignment;
1147
- }
1148
- if (new_xf.wrap_text) {
1149
- attrs.wrapText = '1';
1150
- }
1151
- new_element.append(Element('alignment', attrs));
1152
- }
1153
-
1154
- if (typeof new_xf.xfid !== 'undefined') {
1155
- new_element.attrib.xfId = new_xf.xfid.toString();
1156
- }
1157
-
1158
- xfs.append(new_element);
1159
- */
1160
-
1161
1133
  return this.cell_xfs.length - 1;
1162
1134
 
1163
1135
  }
@@ -1255,6 +1227,7 @@ export class StyleCache {
1255
1227
  xf.horizontal_alignment = element.alignment.a$.horizontal;
1256
1228
  xf.vertical_alignment = element.alignment.a$.vertical;
1257
1229
  xf.wrap_text = !!element.alignment.a$.wrapText;
1230
+ xf.indent = element.alignment.a$.indent || undefined;
1258
1231
  }
1259
1232
 
1260
1233
  return xf;
@@ -44,6 +44,7 @@ import { Theme } from './workbook-theme2';
44
44
  import { Sheet, VisibleState } from './workbook-sheet2';
45
45
  import type { RelationshipMap } from './relationship';
46
46
  import { ZipWrapper } from './zip-wrapper';
47
+ import type { CellStyle } from 'treb-base-types';
47
48
 
48
49
 
49
50
  /*
@@ -98,7 +99,23 @@ export interface AnchoredChartDescription {
98
99
  anchor: TwoCellAnchor,
99
100
  }
100
101
 
101
- export type AnchoredDrawingPart = AnchoredChartDescription|AnchoredImageDescription;
102
+ export interface AnchoredTextBoxDescription {
103
+ type: 'textbox';
104
+ style?: CellStyle;
105
+ paragraphs: {
106
+ style?: CellStyle,
107
+ content: {
108
+ text: string,
109
+ style?: CellStyle
110
+ }[],
111
+ }[];
112
+ anchor: TwoCellAnchor,
113
+ }
114
+
115
+ export type AnchoredDrawingPart =
116
+ AnchoredChartDescription |
117
+ AnchoredTextBoxDescription |
118
+ AnchoredImageDescription ;
102
119
 
103
120
  export interface TableFooterType {
104
121
  type: 'label'|'formula';
@@ -363,6 +380,107 @@ export class Workbook {
363
380
  }
364
381
 
365
382
  }
383
+ else {
384
+
385
+ let style: CellStyle|undefined;
386
+
387
+ const sppr = XMLUtils.FindAll(anchor_node, 'xdr:sp/xdr:spPr')[0];
388
+ if (sppr) {
389
+ style = {};
390
+ const fill = sppr['a:solidFill'];
391
+ if (fill) {
392
+ if (fill['a:schemeClr']?.a$?.val) {
393
+ let m = (fill['a:schemeClr'].a$.val).match(/accent(\d+)/);
394
+ if (m) {
395
+ style.fill = { theme: Number(m[1]) + 3 }
396
+ if (fill['a:schemeClr']['a:lumOff']?.a$?.val) {
397
+ const num = Number(fill['a:schemeClr']['a:lumOff'].a$.val);
398
+ if (!isNaN(num)) {
399
+ style.fill.tint = num / 1e5;
400
+ }
401
+ }
402
+ }
403
+
404
+ }
405
+ }
406
+ }
407
+
408
+ const tx = XMLUtils.FindAll(anchor_node, 'xdr:sp/xdr:txBody')[0];
409
+ if (tx) {
410
+
411
+ const paragraphs: {
412
+ style?: CellStyle,
413
+ content: {
414
+ text: string,
415
+ style?: CellStyle
416
+ }[],
417
+ }[] = [];
418
+
419
+ /*
420
+ const content: {
421
+ text: string,
422
+ style?: CellStyle,
423
+ }[][] = [];
424
+ */
425
+
426
+ const p_list = XMLUtils.FindAll(tx, 'a:p');
427
+ for (const paragraph of p_list) {
428
+ const para: { text: string, style?: CellStyle }[] = [];
429
+ let style: CellStyle|undefined;
430
+
431
+ let appr = paragraph['a:pPr'];
432
+ if (appr) {
433
+ style = {};
434
+ if (appr.a$?.algn === 'r') {
435
+ style.horizontal_align = 'right';
436
+ }
437
+ else if (appr.a$?.algn === 'ctr') {
438
+ style.horizontal_align = 'center';
439
+ }
440
+ }
441
+
442
+ let ar = paragraph['a:r'];
443
+ if (ar) {
444
+ if (!Array.isArray(ar)) {
445
+ ar = [ar];
446
+ }
447
+ for (const line of ar) {
448
+
449
+ const entry: { text: string, style?: CellStyle } = {
450
+ text: line['a:t'] || '',
451
+ };
452
+
453
+ // format
454
+ const fmt = line['a:rPr'];
455
+ if (fmt) {
456
+ entry.style = {};
457
+ if (fmt.a$?.b === '1') {
458
+ entry.style.bold = true;
459
+ }
460
+ if (fmt.a$?.i === '1') {
461
+ entry.style.italic = true;
462
+ }
463
+ }
464
+
465
+ para.push(entry);
466
+
467
+ }
468
+ }
469
+
470
+ paragraphs.push({ content: para, style });
471
+
472
+ }
473
+
474
+ results.push({
475
+ type: 'textbox',
476
+ style,
477
+ paragraphs,
478
+ anchor,
479
+ });
480
+
481
+ }
482
+
483
+ }
366
484
 
367
485
 
368
486
  }