@trebco/treb 28.11.1 → 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.
Files changed (37) hide show
  1. package/dist/treb-spreadsheet-light.mjs +11 -11
  2. package/dist/treb-spreadsheet.mjs +11 -11
  3. package/dist/treb.d.ts +27 -3
  4. package/package.json +1 -1
  5. package/treb-base-types/src/style.ts +3 -0
  6. package/treb-calculator/src/calculator.ts +235 -68
  7. package/treb-calculator/src/descriptors.ts +5 -0
  8. package/treb-calculator/src/expression-calculator.ts +9 -5
  9. package/treb-calculator/src/functions/base-functions.ts +410 -21
  10. package/treb-calculator/src/functions/text-functions.ts +45 -55
  11. package/treb-calculator/src/primitives.ts +11 -0
  12. package/treb-calculator/src/utilities.ts +55 -0
  13. package/treb-embed/markup/layout.html +15 -10
  14. package/treb-embed/markup/toolbar.html +5 -5
  15. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +38 -2
  16. package/treb-embed/src/embedded-spreadsheet.ts +227 -29
  17. package/treb-embed/src/options.ts +5 -0
  18. package/treb-embed/style/dark-theme.scss +1 -0
  19. package/treb-embed/style/formula-bar.scss +20 -7
  20. package/treb-embed/style/theme-defaults.scss +20 -0
  21. package/treb-export/src/export-worker/export-worker.ts +1 -0
  22. package/treb-export/src/export2.ts +6 -1
  23. package/treb-export/src/import2.ts +76 -6
  24. package/treb-export/src/shared-strings2.ts +1 -1
  25. package/treb-export/src/workbook-style2.ts +89 -52
  26. package/treb-export/src/workbook2.ts +119 -1
  27. package/treb-grid/src/editors/editor.ts +7 -0
  28. package/treb-grid/src/editors/formula_bar.ts +23 -1
  29. package/treb-grid/src/render/tile_renderer.ts +46 -3
  30. package/treb-grid/src/types/annotation.ts +17 -3
  31. package/treb-grid/src/types/grid.ts +28 -9
  32. package/treb-grid/src/types/grid_base.ts +10 -105
  33. package/treb-grid/src/types/grid_options.ts +3 -2
  34. package/treb-grid/src/types/named_range.ts +8 -1
  35. package/treb-grid/src/types/serialize_options.ts +5 -0
  36. package/treb-parser/src/parser-types.ts +27 -4
  37. package/treb-parser/src/parser.ts +74 -36
@@ -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';
@@ -81,7 +81,10 @@ export class Importer {
81
81
  t?: string;
82
82
  s?: string;
83
83
  };
84
- v?: string|number; // is this never an object? (note: booleans are numbers in Excel)
84
+ v?: string|number|{
85
+ t$: string;
86
+ a$?: any;
87
+ };
85
88
  f?: string|{
86
89
  t$: string;
87
90
  a$?: {
@@ -167,8 +170,13 @@ export class Importer {
167
170
  const parse_result = this.parser.Parse(formula); // l10n?
168
171
  if (parse_result.expression) {
169
172
  this.parser.Walk(parse_result.expression, (unit) => {
170
- if (unit.type === 'call' && /^_xll\./.test(unit.name)) {
171
- 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
+ }
172
180
  }
173
181
  return true;
174
182
  });
@@ -214,14 +222,17 @@ export class Importer {
214
222
  }
215
223
 
216
224
  if (typeof element.v !== 'undefined') {
217
- const num = Number(element.v.toString());
225
+
226
+ const V = (typeof element.v === 'object') ? element.v?.t$ : element.v;
227
+
228
+ const num = Number(V.toString());
218
229
  if (!isNaN(num)) {
219
230
  calculated_type = 'number'; // ValueType.number;
220
231
  calculated_value = num;
221
232
  }
222
233
  else {
223
234
  calculated_type = 'string'; // ValueType.string;
224
- calculated_value = element.v.toString();
235
+ calculated_value = V.toString();
225
236
  }
226
237
  }
227
238
 
@@ -384,6 +395,20 @@ export class Importer {
384
395
 
385
396
  case 'expression':
386
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
+
387
412
  let style = {};
388
413
 
389
414
  if (rule.a$.dxfId) {
@@ -492,6 +517,26 @@ export class Importer {
492
517
  const conditional_formatting = FindAll('worksheet/conditionalFormatting');
493
518
  for (const element of conditional_formatting) {
494
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
+ /*
495
540
  const area = sheet.TranslateAddress(element.a$.sqref);
496
541
  if (element.cfRule) {
497
542
  const rules = Array.isArray(element.cfRule) ? element.cfRule : [element.cfRule];
@@ -502,6 +547,7 @@ export class Importer {
502
547
  }
503
548
  }
504
549
  }
550
+ */
505
551
  }
506
552
  }
507
553
 
@@ -802,6 +848,7 @@ export class Importer {
802
848
  const drawings = FindAll('worksheet/drawing');
803
849
  const chart_descriptors: AnchoredChartDescription[] = [];
804
850
  const image_descriptors: AnchoredImageDescription[] = [];
851
+ const textbox_descriptors: AnchoredTextBoxDescription[] = [];
805
852
 
806
853
  for (const child of drawings) {
807
854
 
@@ -826,6 +873,9 @@ export class Importer {
826
873
  case 'image':
827
874
  image_descriptors.push(entry);
828
875
  break;
876
+ case 'textbox':
877
+ textbox_descriptors.push(entry);
878
+ break;
829
879
  }
830
880
  }
831
881
  }
@@ -867,6 +917,26 @@ export class Importer {
867
917
 
868
918
  };
869
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
+
870
940
  for (const descriptor of image_descriptors) {
871
941
  if (descriptor && descriptor.image) {
872
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
@@ -29,6 +29,9 @@ import { XMLUtils } from './xml-utils';
29
29
 
30
30
  import { Unescape } from './unescape_xml';
31
31
 
32
+ // what's the default font size? ... 11pt?
33
+ const DEFAULT_FONT_SIZE = 11;
34
+
32
35
  export interface Font {
33
36
  size?: number;
34
37
  name?: string;
@@ -73,6 +76,7 @@ export interface CellXf {
73
76
  horizontal_alignment?: string;
74
77
  vertical_alignment?: string;
75
78
  xfid?: number;
79
+ indent?: number;
76
80
 
77
81
  // FIXME // apply_font?: boolean;
78
82
  // FIXME // apply_border?: boolean;
@@ -154,6 +158,7 @@ export interface StyleOptions {
154
158
  vertical_alignment?: string;
155
159
  wrap?: boolean;
156
160
  fill?: Fill;
161
+ indent?: number;
157
162
  }
158
163
 
159
164
 
@@ -269,13 +274,24 @@ export class StyleCache {
269
274
 
270
275
  }
271
276
 
277
+
272
278
  if (composite.font_size?.unit && composite.font_size.value) {
273
- if (composite.font_size.unit !== 'pt') {
274
- console.warn(`can't handle non-point font (FIXME)`);
279
+ if (composite.font_size.unit === 'em') {
280
+ font.size = composite.font_size.value * DEFAULT_FONT_SIZE;
275
281
  }
276
- else {
282
+ else if (composite.font_size.unit === '%') {
283
+ font.size = composite.font_size.value * DEFAULT_FONT_SIZE / 100;
284
+ }
285
+ else if (composite.font_size.unit === 'pt' ){
277
286
  font.size = composite.font_size.value;
278
287
  }
288
+ else if (composite.font_size.unit === 'px' ){
289
+ font.size = composite.font_size.value * .75; // ?
290
+ }
291
+ else {
292
+ console.warn(`Unhandled font size unit`, composite.font_size);
293
+ }
294
+
279
295
  }
280
296
 
281
297
  if (composite.bold) font.bold = true;
@@ -420,6 +436,8 @@ export class StyleCache {
420
436
  break;
421
437
  }
422
438
 
439
+ options.indent = composite.indent;
440
+
423
441
  if (composite.fill) {
424
442
  fill.pattern_type = 'solid';
425
443
  if (composite.fill.text) {
@@ -649,6 +667,10 @@ export class StyleCache {
649
667
  break;
650
668
  }
651
669
 
670
+ // indent
671
+
672
+ props.indent = xf.indent;
673
+
652
674
  // wrap
653
675
 
654
676
  if (xf.wrap_text) {
@@ -657,6 +679,19 @@ export class StyleCache {
657
679
 
658
680
  // borders
659
681
 
682
+ const BorderEdgeToColor = (edge: BorderEdge): Color|undefined => {
683
+
684
+ // TODO: indexed
685
+
686
+ if (typeof edge.theme !== 'undefined') {
687
+ return {
688
+ theme: edge.theme,
689
+ tint: edge.tint,
690
+ };
691
+ }
692
+
693
+ };
694
+
660
695
  const border = this.borders[xf.border || 0];
661
696
  if (border) {
662
697
  if (border.bottom.style) {
@@ -666,10 +701,20 @@ export class StyleCache {
666
701
  else {
667
702
  props.border_bottom = 1;
668
703
  }
704
+ props.border_bottom_fill = BorderEdgeToColor(border.bottom);
705
+ }
706
+ if (border.left.style) {
707
+ props.border_left = 1;
708
+ props.border_left_fill = BorderEdgeToColor(border.left);
709
+ }
710
+ if (border.top.style) {
711
+ props.border_top = 1;
712
+ props.border_top_fill = BorderEdgeToColor(border.top);
713
+ }
714
+ if (border.right.style) {
715
+ props.border_right = 1;
716
+ props.border_right_fill = BorderEdgeToColor(border.right);
669
717
  }
670
- if (border.left.style) props.border_left = 1;
671
- if (border.top.style) props.border_top = 1;
672
- if (border.right.style) props.border_right = 1;
673
718
  }
674
719
 
675
720
  return props;
@@ -1054,6 +1099,7 @@ export class StyleCache {
1054
1099
  xf.border === border_index &&
1055
1100
  xf.number_format === number_format_index &&
1056
1101
  !!xf.wrap_text === !!options.wrap &&
1102
+ xf.indent === options.indent &&
1057
1103
  ((!options.horizontal_alignment && !xf.horizontal_alignment) || options.horizontal_alignment === xf.horizontal_alignment) &&
1058
1104
  ((!options.vertical_alignment && !xf.vertical_alignment) || options.vertical_alignment === xf.vertical_alignment)) {
1059
1105
 
@@ -1069,6 +1115,7 @@ export class StyleCache {
1069
1115
  fill: fill_index,
1070
1116
  border: border_index,
1071
1117
  number_format: number_format_index,
1118
+ indent: options.indent,
1072
1119
  };
1073
1120
 
1074
1121
  if (options.horizontal_alignment) {
@@ -1083,44 +1130,6 @@ export class StyleCache {
1083
1130
 
1084
1131
  this.cell_xfs.push(new_xf);
1085
1132
 
1086
- /*
1087
-
1088
- // add the node structure
1089
-
1090
- if (!this.dom) throw new Error('missing dom');
1091
- const xfs = this.dom.find('./cellXfs');
1092
-
1093
- if (!xfs) throw new Error('xfs not found');
1094
- xfs.attrib.count = (Number(xfs.attrib.count || 0) + 1).toString();
1095
-
1096
- const new_element = Element('xf', {
1097
- borderId: new_xf.border.toString(),
1098
- fillId: new_xf.fill.toString(),
1099
- fontId: new_xf.font.toString(),
1100
- numFmtId: new_xf.number_format.toString(),
1101
- });
1102
-
1103
- if (new_xf.horizontal_alignment || new_xf.vertical_alignment) {
1104
- const attrs: {[index: string]: string} = {};
1105
- if (new_xf.horizontal_alignment) {
1106
- attrs.horizontal = new_xf.horizontal_alignment;
1107
- }
1108
- if (new_xf.vertical_alignment) {
1109
- attrs.vertical = new_xf.vertical_alignment;
1110
- }
1111
- if (new_xf.wrap_text) {
1112
- attrs.wrapText = '1';
1113
- }
1114
- new_element.append(Element('alignment', attrs));
1115
- }
1116
-
1117
- if (typeof new_xf.xfid !== 'undefined') {
1118
- new_element.attrib.xfId = new_xf.xfid.toString();
1119
- }
1120
-
1121
- xfs.append(new_element);
1122
- */
1123
-
1124
1133
  return this.cell_xfs.length - 1;
1125
1134
 
1126
1135
  }
@@ -1144,31 +1153,58 @@ export class StyleCache {
1144
1153
 
1145
1154
  composite = FindAll('styleSheet/borders/border');
1146
1155
 
1156
+ const ElementToBorderEdge = (element: any, edge: BorderEdge) => {
1157
+
1158
+ if (element?.a$) {
1159
+ edge.style = element.a$.style;
1160
+ if (typeof element.color === 'object') {
1161
+ if (typeof element.color.a$?.indexed !== 'undefined') {
1162
+ edge.color = Number(element.color.a$.indexed);
1163
+ }
1164
+ if (typeof element.color.a$?.theme !== 'undefined') {
1165
+ edge.theme = Number(element.color.a$.theme);
1166
+ }
1167
+ if (typeof element.color.a$?.tint !== 'undefined') {
1168
+ edge.tint = Number(element.color.a$.tint);
1169
+ }
1170
+ }
1171
+ }
1172
+
1173
+ };
1174
+
1175
+
1147
1176
  this.borders = composite.map(element => {
1148
1177
 
1149
1178
  const border: BorderStyle = JSON.parse(JSON.stringify(default_border));
1150
1179
 
1180
+ /*
1151
1181
  // we're relying on these being empty strings -> falsy, not a good look
1152
1182
 
1153
1183
  if (element.left) {
1154
- border.left.style = element.left.a$.style;
1155
- border.left.color = Number(element.left.color?.a$?.indexed);
1184
+ // border.left.style = element.left.a$.style;
1185
+ // border.left.color = Number(element.left.color?.a$?.indexed);
1156
1186
  }
1157
1187
 
1158
1188
  if (element.right) {
1159
- border.right.style = element.right.a$.style;
1160
- border.right.color = Number(element.right.color?.a$?.indexed);
1189
+ // border.right.style = element.right.a$.style;
1190
+ // border.right.color = Number(element.right.color?.a$?.indexed);
1161
1191
  }
1162
1192
 
1163
1193
  if (element.top) {
1164
- border.top.style = element.top.a$.style;
1165
- border.top.color = Number(element.top.color?.a$?.indexed);
1194
+ // border.top.style = element.top.a$.style;
1195
+ // border.top.color = Number(element.top.color?.a$?.indexed);
1166
1196
  }
1167
1197
 
1168
1198
  if (element.bottom) {
1169
- border.bottom.style = element.bottom.a$.style;
1170
- border.bottom.color = Number(element.bottom.color?.a$?.indexed);
1199
+ // border.bottom.style = element.bottom.a$.style;
1200
+ // border.bottom.color = Number(element.bottom.color?.a$?.indexed);
1171
1201
  }
1202
+ */
1203
+
1204
+ ElementToBorderEdge(element.left, border.left);
1205
+ ElementToBorderEdge(element.right, border.right);
1206
+ ElementToBorderEdge(element.top, border.top);
1207
+ ElementToBorderEdge(element.bottom, border.bottom);
1172
1208
 
1173
1209
  return border;
1174
1210
 
@@ -1191,6 +1227,7 @@ export class StyleCache {
1191
1227
  xf.horizontal_alignment = element.alignment.a$.horizontal;
1192
1228
  xf.vertical_alignment = element.alignment.a$.vertical;
1193
1229
  xf.wrap_text = !!element.alignment.a$.wrapText;
1230
+ xf.indent = element.alignment.a$.indent || undefined;
1194
1231
  }
1195
1232
 
1196
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
  }
@@ -789,6 +789,13 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
789
789
 
790
790
  if (this.active_editor && !this.assume_formula) {
791
791
  this.active_editor.node.spellcheck = !(this.text_formula);
792
+
793
+ // if not assuming formula, and it's not a formula, then exit.
794
+
795
+ if (!this.text_formula) {
796
+ return;
797
+ }
798
+
792
799
  }
793
800
 
794
801
  // this is a short-circuit so we don't format the same text twice.
@@ -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';