@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.
@@ -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 = (typeof xf.indent === 'string') ? Number(xf.indent) : xf.indent;
673
+
666
674
  // wrap
667
675
 
668
676
  if (xf.wrap_text) {
@@ -714,6 +722,19 @@ export class StyleCache {
714
722
 
715
723
  /** map all cell xfs to styles; retain order */
716
724
  public CellXfToStyles(): CellStyle[] {
725
+
726
+ /*
727
+ const mapped = this.cell_xfs.map((xf, index) => {
728
+ const style = this.CellXfToStyle(xf);
729
+ if (style.font_size?.value && style.font_size.value > 200) {
730
+ console.info({index, fs: JSON.stringify(style.font_size), style, xf});
731
+ console.info(this);
732
+ }
733
+ return style;
734
+ });
735
+ return mapped;
736
+ */
737
+
717
738
  return this.cell_xfs.map((xf) => this.CellXfToStyle(xf));
718
739
  }
719
740
 
@@ -1091,6 +1112,7 @@ export class StyleCache {
1091
1112
  xf.border === border_index &&
1092
1113
  xf.number_format === number_format_index &&
1093
1114
  !!xf.wrap_text === !!options.wrap &&
1115
+ xf.indent === options.indent &&
1094
1116
  ((!options.horizontal_alignment && !xf.horizontal_alignment) || options.horizontal_alignment === xf.horizontal_alignment) &&
1095
1117
  ((!options.vertical_alignment && !xf.vertical_alignment) || options.vertical_alignment === xf.vertical_alignment)) {
1096
1118
 
@@ -1106,6 +1128,7 @@ export class StyleCache {
1106
1128
  fill: fill_index,
1107
1129
  border: border_index,
1108
1130
  number_format: number_format_index,
1131
+ indent: options.indent,
1109
1132
  };
1110
1133
 
1111
1134
  if (options.horizontal_alignment) {
@@ -1120,44 +1143,6 @@ export class StyleCache {
1120
1143
 
1121
1144
  this.cell_xfs.push(new_xf);
1122
1145
 
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
1146
  return this.cell_xfs.length - 1;
1162
1147
 
1163
1148
  }
@@ -1202,40 +1187,14 @@ export class StyleCache {
1202
1187
 
1203
1188
 
1204
1189
  this.borders = composite.map(element => {
1205
-
1206
1190
  const border: BorderStyle = JSON.parse(JSON.stringify(default_border));
1207
1191
 
1208
- /*
1209
- // we're relying on these being empty strings -> falsy, not a good look
1210
-
1211
- if (element.left) {
1212
- // border.left.style = element.left.a$.style;
1213
- // border.left.color = Number(element.left.color?.a$?.indexed);
1214
- }
1215
-
1216
- if (element.right) {
1217
- // border.right.style = element.right.a$.style;
1218
- // border.right.color = Number(element.right.color?.a$?.indexed);
1219
- }
1220
-
1221
- if (element.top) {
1222
- // border.top.style = element.top.a$.style;
1223
- // border.top.color = Number(element.top.color?.a$?.indexed);
1224
- }
1225
-
1226
- if (element.bottom) {
1227
- // border.bottom.style = element.bottom.a$.style;
1228
- // border.bottom.color = Number(element.bottom.color?.a$?.indexed);
1229
- }
1230
- */
1231
-
1232
1192
  ElementToBorderEdge(element.left, border.left);
1233
1193
  ElementToBorderEdge(element.right, border.right);
1234
1194
  ElementToBorderEdge(element.top, border.top);
1235
1195
  ElementToBorderEdge(element.bottom, border.bottom);
1236
1196
 
1237
1197
  return border;
1238
-
1239
1198
  });
1240
1199
 
1241
1200
  // ---
@@ -1255,6 +1214,7 @@ export class StyleCache {
1255
1214
  xf.horizontal_alignment = element.alignment.a$.horizontal;
1256
1215
  xf.vertical_alignment = element.alignment.a$.vertical;
1257
1216
  xf.wrap_text = !!element.alignment.a$.wrapText;
1217
+ xf.indent = element.alignment.a$.indent || undefined;
1258
1218
  }
1259
1219
 
1260
1220
  return xf;
@@ -1340,6 +1300,7 @@ export class StyleCache {
1340
1300
  if (element.color.a$?.rgb) {
1341
1301
  font.color_argb = element.color.a$.rgb;
1342
1302
  }
1303
+
1343
1304
  }
1344
1305
 
1345
1306
  return font;
@@ -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
  }
@@ -29,9 +29,11 @@ import { DOMContext } from 'treb-base-types';
29
29
 
30
30
  // --- from formula_bar ---
31
31
 
32
+ /*
32
33
  export interface FormulaBarResizeEvent {
33
34
  type: 'formula-bar-resize';
34
35
  }
36
+ */
35
37
 
36
38
  export interface FormulaButtonEvent {
37
39
  type: 'formula-button';
@@ -46,7 +48,7 @@ export interface AddressLabelEvent {
46
48
 
47
49
  export type FormulaBar2Event
48
50
  = FormulaButtonEvent
49
- | FormulaBarResizeEvent
51
+ // | FormulaBarResizeEvent
50
52
  | AddressLabelEvent
51
53
  ;
52
54
 
@@ -172,6 +174,7 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
172
174
 
173
175
  this.InitAddressLabel();
174
176
 
177
+ /*
175
178
  if (this.options.insert_function_button) {
176
179
  this.button = DOM.Create('button', 'formula-button', inner_node);
177
180
  this.button.addEventListener('click', () => {
@@ -179,6 +182,7 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
179
182
  this.Publish({ type: 'formula-button', formula });
180
183
  });
181
184
  }
185
+ */
182
186
 
183
187
  this.container_node = container.querySelector('.treb-editor-container') as HTMLDivElement;
184
188
  const target = this.container_node.firstElementChild as HTMLDivElement;
@@ -270,9 +274,18 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
270
274
  this.RegisterListener(descriptor, 'keydown', this.FormulaKeyDown.bind(this));
271
275
  this.RegisterListener(descriptor, 'keyup', this.FormulaKeyUp.bind(this));
272
276
 
277
+ // why is this here, instead of in markup? just an oversight?
278
+
273
279
  if (this.options.expand_formula_button) {
280
+
281
+ let focus_related_target: HTMLElement|undefined;
282
+
274
283
  this.expand_button = DOM.Create('button', 'expand-button', inner_node, {
275
284
  events: {
285
+ focus: (event: FocusEvent) => {
286
+ focus_related_target = event.relatedTarget instanceof HTMLElement ? event.relatedTarget : undefined;
287
+ },
288
+
276
289
  click: (event: MouseEvent) => {
277
290
  event.stopPropagation();
278
291
  event.preventDefault();
@@ -285,6 +298,11 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
285
298
  else {
286
299
  inner_node.setAttribute('expanded', '');
287
300
  }
301
+
302
+ if (focus_related_target) {
303
+ focus_related_target.focus();
304
+ }
305
+
288
306
  },
289
307
  },
290
308
  });
@@ -297,6 +315,10 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
297
315
  return element === this.active_editor?.node;
298
316
  }
299
317
 
318
+ public IsExpandButton(element: HTMLElement): boolean {
319
+ return this.expand_button && (element === this.expand_button);
320
+ }
321
+
300
322
  public InitAddressLabel() {
301
323
 
302
324
  this.address_label.contentEditable = 'true';
@@ -34,6 +34,7 @@ import type { BaseLayout, TileRange } from '../layout/base_layout';
34
34
  import type { DataModel, ViewModel } from '../types/data_model';
35
35
  import type { GridOptions } from '../types/grid_options';
36
36
 
37
+ const DEFAULT_INDENT = ' '; // two spaces in the current font
37
38
  const BASELINE = 'bottom';
38
39
  const WK = /webkit/i.test(typeof navigator === 'undefined' ? '' : navigator?.userAgent || '') ? 1 : 0;
39
40
 
@@ -723,6 +724,15 @@ export class TileRenderer {
723
724
  let override_formatting: string | undefined;
724
725
  let formatted = cell.editing ? '' : cell.formatted; // <-- empty on editing, to remove overflows
725
726
 
727
+ // precalculate indent as string so we can use layout
728
+
729
+ let indent = '';
730
+ if (style.indent) {
731
+ for (let i = 0; i < style.indent; i++) {
732
+ indent += DEFAULT_INDENT;
733
+ }
734
+ }
735
+
726
736
  if (Array.isArray(formatted)) {
727
737
 
728
738
  // type 1 is a multi-part formatted string; used for number formats.
@@ -733,6 +743,15 @@ export class TileRenderer {
733
743
 
734
744
  // this is a single line, with number formatting
735
745
 
746
+ if (indent) {
747
+ if (style.horizontal_align === 'right') {
748
+ formatted.push({ text: indent });
749
+ }
750
+ else if (style.horizontal_align !== 'center') {
751
+ formatted.unshift({ text: indent });
752
+ }
753
+ }
754
+
736
755
  for (const part of formatted) {
737
756
  if (part.flag === TextPartFlag.formatting) {
738
757
  override_formatting = part.text;
@@ -810,11 +829,14 @@ export class TileRenderer {
810
829
 
811
830
  // for wrapping
812
831
 
813
- const bound = cell_width - (2 * this.cell_edge_buffer);
832
+ let bound = cell_width - (2 * this.cell_edge_buffer);
814
833
  const strings: RenderTextPart[][] = [];
815
834
 
816
835
  if (style.wrap) {
817
836
 
837
+ const indent_width = (indent && style.horizontal_align !== 'center') ? context.measureText(indent).width : 0;
838
+ bound -= indent_width;
839
+
818
840
  for (const line of md) {
819
841
 
820
842
  // we should probably normalize whitespace -- because formatting
@@ -921,14 +943,26 @@ export class TileRenderer {
921
943
 
922
944
  max_width = Math.max(max_width, last.width);
923
945
 
924
- strings.push(line2.map((metric) => {
946
+ const line_string = line2.map((metric) => {
925
947
  return {
926
948
  ...metric.part,
927
949
  hidden: false,
928
950
  width: metric.width,
929
951
  text: metric.text,
930
952
  };
931
- }));
953
+ });
954
+
955
+ if (style.indent) {
956
+ if (style.horizontal_align === 'right') {
957
+ line_string.push({ text: indent, hidden: false, width: indent_width });
958
+ }
959
+ else if (style.horizontal_align !== 'center') {
960
+ line_string.unshift({ text: indent, hidden: false, width: indent_width });
961
+ }
962
+
963
+ }
964
+
965
+ strings.push(line_string);
932
966
 
933
967
  }
934
968
 
@@ -939,9 +973,20 @@ export class TileRenderer {
939
973
 
940
974
  // simple case
941
975
 
976
+
942
977
  for (const line of md) {
943
978
  const parts: RenderTextPart[] = [];
944
979
 
980
+ if (style.indent) {
981
+ if (style.horizontal_align === 'right') {
982
+ line.push({ text: indent });
983
+ }
984
+ else if (style.horizontal_align !== 'center') {
985
+ line.unshift({ text: indent });
986
+ }
987
+
988
+ }
989
+
945
990
  let line_width = 0;
946
991
 
947
992
  for (const element of line) {
@@ -19,7 +19,7 @@
19
19
  *
20
20
  */
21
21
 
22
- import type { ICellAddress, AnnotationLayout, IRectangle } from 'treb-base-types';
22
+ import type { ICellAddress, AnnotationLayout, IRectangle, CellStyle } from 'treb-base-types';
23
23
  import { Rectangle } from 'treb-base-types';
24
24
 
25
25
  /**
@@ -97,7 +97,7 @@ export interface ImageAnnotationData {
97
97
 
98
98
  }
99
99
 
100
- export type AnnotationType = 'treb-chart'|'image'|'external';
100
+ export type AnnotationType = 'treb-chart'|'image'|'textbox'|'external';
101
101
 
102
102
  /**
103
103
  * splitting persisted data from the annotation class. that class might
@@ -182,12 +182,26 @@ export interface AnnotationChartData extends AnnotationDataBase {
182
182
  type: 'treb-chart';
183
183
  }
184
184
 
185
+ export interface AnnotationTextBoxData extends AnnotationDataBase {
186
+ type: 'textbox';
187
+ data: {
188
+ style?: CellStyle;
189
+ paragraphs: {
190
+ style?: CellStyle,
191
+ content: {
192
+ text: string,
193
+ style?: CellStyle
194
+ }[],
195
+ }[];
196
+ };
197
+ };
198
+
185
199
  export interface AnnotationExternalData extends AnnotationDataBase {
186
200
  type: 'external';
187
201
  data: Record<string, string>;
188
202
  }
189
203
 
190
- export type AnnotationData = AnnotationChartData | AnnotationImageData | AnnotationExternalData;
204
+ export type AnnotationData = AnnotationChartData | AnnotationImageData | AnnotationExternalData | AnnotationTextBoxData;
191
205
 
192
206
  /**
193
207
  * why is this a class? it doesn't do anything.
@@ -634,6 +634,14 @@ export class Grid extends GridBase {
634
634
  this.editing_annotation = annotation;
635
635
  this.layout.ShowSelections(true);
636
636
  }
637
+ else if (this.formula_bar?.IsExpandButton(event.relatedTarget as HTMLElement)) {
638
+
639
+ // for this particular case, do nothing. basically you are
640
+ // expanding/contracting the formula bar. we want to preserve
641
+ // the selected annotation, if any. after the operation we'll
642
+ // restore focus.
643
+
644
+ }
637
645
  else {
638
646
  if (this.selected_annotation === annotation) {
639
647
  this.selected_annotation = undefined;
@@ -970,7 +978,7 @@ export class Grid extends GridBase {
970
978
 
971
979
  const validated = this.model.named_ranges.ValidateNamed(name);
972
980
  if (!validated) {
973
- console.warn(`invalid name: ${name}`);
981
+ console.warn(`invalid name: ${name}`, import_data.names[name]);
974
982
  continue;
975
983
  }
976
984
 
@@ -68,8 +68,9 @@ export interface GridOptions {
68
68
  /* * show delete tab in the tab bar */
69
69
  // delete_tab?: boolean;
70
70
 
71
- /** show the "insert function" button. requires formula bar. */
71
+ /* * show the "insert function" button. requires formula bar. * /
72
72
  insert_function_button?: boolean;
73
+ */
73
74
 
74
75
  /** button to increase/reduce size of formula editor */
75
76
  expand_formula_button?: boolean;
@@ -97,7 +98,7 @@ export const DefaultGridOptions: GridOptions = {
97
98
  formula_bar: true,
98
99
  add_tab: false,
99
100
  tab_bar: 'auto',
100
- insert_function_button: false,
101
+ // insert_function_button: false,
101
102
  expand_formula_button: false,
102
103
  expand: true,
103
104
  repaint_on_cell_change: true,
@@ -92,10 +92,17 @@ export class NamedRangeCollection {
92
92
  console.warn('invalid name');
93
93
  return false;
94
94
  }
95
+
96
+ // why is this considered invalid here? I've seen it done.
97
+ // maybe something we're doing with these ranges doesn't
98
+ // collapse them? (...)
99
+
95
100
  if (range.entire_column || range.entire_row) {
96
- console.warn('invalid range');
101
+ console.info({range});
102
+ console.warn(`invalid range`);
97
103
  return false;
98
104
  }
105
+
99
106
  this.forward[validated] = range;
100
107
  if (apply) {
101
108
  this.RebuildList();