@trebco/treb 30.8.2 → 30.10.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.
- package/dist/treb-export-worker.mjs +2 -2
- package/dist/treb-spreadsheet.mjs +9 -9
- package/dist/treb.d.ts +4 -1
- package/package.json +1 -1
- package/treb-base-types/src/color.ts +32 -0
- package/treb-base-types/src/import.ts +3 -4
- package/treb-base-types/src/theme.ts +55 -10
- package/treb-charts/style/charts.scss +58 -14
- package/treb-data-model/src/sheet.ts +11 -1
- package/treb-data-model/src/sheet_types.ts +4 -1
- package/treb-embed/style/theme-defaults.scss +16 -0
- package/treb-export/src/export.ts +40 -7
- package/treb-export/src/{import2.ts → import.ts} +121 -49
- package/treb-export/src/index.worker.ts +1 -1
- package/treb-export/src/workbook2.ts +14 -13
- package/treb-export/src/xml-utils.ts +22 -1
- package/treb-grid/src/layout/base_layout.ts +21 -0
- package/treb-grid/src/layout/grid_layout.ts +2 -0
- package/treb-grid/src/types/grid.ts +9 -5
- package/treb-grid/src/types/tab_bar.ts +38 -1
- package/treb-charts/style/old-charts.scss +0 -250
|
@@ -35,7 +35,7 @@ import type { SerializedValueType } from 'treb-base-types';
|
|
|
35
35
|
import type { Sheet} from './workbook-sheet2';
|
|
36
36
|
import { VisibleState } from './workbook-sheet2';
|
|
37
37
|
import type { CellAnchor } from './drawing2/drawing2';
|
|
38
|
-
import { type
|
|
38
|
+
import { type GenericDOMElement, XMLUtils } from './xml-utils';
|
|
39
39
|
|
|
40
40
|
// import { one_hundred_pixels } from './constants';
|
|
41
41
|
import { ColumnWidthToPixels } from './column-width';
|
|
@@ -52,6 +52,55 @@ interface SharedFormula {
|
|
|
52
52
|
|
|
53
53
|
interface SharedFormulaMap { [index: string]: SharedFormula }
|
|
54
54
|
|
|
55
|
+
interface CellElementType {
|
|
56
|
+
a$: {
|
|
57
|
+
r?: string;
|
|
58
|
+
t?: string;
|
|
59
|
+
s?: string;
|
|
60
|
+
};
|
|
61
|
+
v?: string|number|{
|
|
62
|
+
t$: string;
|
|
63
|
+
a$?: Record<string, string>; // DOMContent;
|
|
64
|
+
};
|
|
65
|
+
f?: string|{
|
|
66
|
+
t$: string;
|
|
67
|
+
a$?: {
|
|
68
|
+
si?: string;
|
|
69
|
+
t?: string;
|
|
70
|
+
ref?: string;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
interface ConditionalFormatRule {
|
|
76
|
+
a$: {
|
|
77
|
+
type?: string;
|
|
78
|
+
dxfId?: string;
|
|
79
|
+
priority?: string;
|
|
80
|
+
operator?: string;
|
|
81
|
+
};
|
|
82
|
+
formula?: string|[number,number]|{t$: string};
|
|
83
|
+
colorScale?: {
|
|
84
|
+
cfvo?: {
|
|
85
|
+
a$: {
|
|
86
|
+
type?: string;
|
|
87
|
+
val?: string;
|
|
88
|
+
}
|
|
89
|
+
}[];
|
|
90
|
+
color?: {
|
|
91
|
+
a$: {
|
|
92
|
+
rgb?: string;
|
|
93
|
+
theme?: string;
|
|
94
|
+
tint?: string;
|
|
95
|
+
}
|
|
96
|
+
}[];
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const ElementHasTextNode = (test: unknown): test is {t$: string} => {
|
|
101
|
+
return typeof test === 'object' && typeof (test as {$t: string}).$t !== 'undefined';
|
|
102
|
+
}
|
|
103
|
+
|
|
55
104
|
export class Importer {
|
|
56
105
|
|
|
57
106
|
// FIXME: need a way to share/pass parser flags
|
|
@@ -76,25 +125,7 @@ export class Importer {
|
|
|
76
125
|
|
|
77
126
|
public ParseCell(
|
|
78
127
|
sheet: Sheet,
|
|
79
|
-
element:
|
|
80
|
-
a$: {
|
|
81
|
-
r?: string;
|
|
82
|
-
t?: string;
|
|
83
|
-
s?: string;
|
|
84
|
-
};
|
|
85
|
-
v?: string|number|{
|
|
86
|
-
t$: string;
|
|
87
|
-
a$?: DOMContent;
|
|
88
|
-
};
|
|
89
|
-
f?: string|{
|
|
90
|
-
t$: string;
|
|
91
|
-
a$?: {
|
|
92
|
-
si?: string;
|
|
93
|
-
t?: string;
|
|
94
|
-
ref?: string;
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
},
|
|
128
|
+
element: CellElementType,
|
|
98
129
|
shared_formulae: SharedFormulaMap,
|
|
99
130
|
arrays: RangeType[],
|
|
100
131
|
merges: RangeType[],
|
|
@@ -166,20 +197,31 @@ export class Importer {
|
|
|
166
197
|
// doing it like this is sloppy (also does not work properly).
|
|
167
198
|
value = '=' + formula.replace(/^_xll\./g, '');
|
|
168
199
|
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
200
|
+
// drop the formula if it's a ref error, we can't handle this
|
|
201
|
+
if (/#REF/.test(formula)) {
|
|
202
|
+
value = formula;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const parse_result = this.parser.Parse(formula); // l10n?
|
|
206
|
+
if (parse_result.expression) {
|
|
207
|
+
this.parser.Walk(parse_result.expression, (unit) => {
|
|
208
|
+
if (unit.type === 'call') {
|
|
209
|
+
if (/^_xll\./.test(unit.name)) {
|
|
210
|
+
unit.name = unit.name.substring(5);
|
|
211
|
+
}
|
|
212
|
+
if (/^_xlfn\./.test(unit.name)) {
|
|
213
|
+
console.info("xlfn:", unit.name);
|
|
214
|
+
unit.name = unit.name.substring(6);
|
|
215
|
+
}
|
|
216
|
+
if (/^_xlws\./.test(unit.name)) {
|
|
217
|
+
console.info("xlws:", unit.name);
|
|
218
|
+
unit.name = unit.name.substring(6);
|
|
219
|
+
}
|
|
178
220
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
221
|
+
return true;
|
|
222
|
+
});
|
|
223
|
+
value = '=' + this.parser.Render(parse_result.expression, { missing: '' });
|
|
224
|
+
}
|
|
183
225
|
}
|
|
184
226
|
|
|
185
227
|
if (typeof element.f !== 'string') {
|
|
@@ -341,11 +383,13 @@ export class Importer {
|
|
|
341
383
|
|
|
342
384
|
}
|
|
343
385
|
|
|
344
|
-
public ParseConditionalFormat(address: RangeType|AddressType, rule:
|
|
386
|
+
public ParseConditionalFormat(address: RangeType|AddressType, rule: ConditionalFormatRule): ConditionalFormat|ConditionalFormat[]|undefined {
|
|
345
387
|
|
|
346
388
|
const area = this.AddressToArea(address);
|
|
347
389
|
const operators = ConditionalFormatOperators;
|
|
348
390
|
|
|
391
|
+
// console.info({rule});
|
|
392
|
+
|
|
349
393
|
switch (rule.a$.type) {
|
|
350
394
|
case 'duplicateValues':
|
|
351
395
|
case 'uniqueValues':
|
|
@@ -425,7 +469,7 @@ export class Importer {
|
|
|
425
469
|
if (rule.formula) {
|
|
426
470
|
|
|
427
471
|
if (typeof rule.formula !== 'string') {
|
|
428
|
-
if (rule.formula
|
|
472
|
+
if (ElementHasTextNode(rule.formula)) {
|
|
429
473
|
|
|
430
474
|
// the only case (to date) we've seen here is that the attribute
|
|
431
475
|
// is "xml:space=preserve", which we can ignore (are you sure?)
|
|
@@ -436,7 +480,7 @@ export class Importer {
|
|
|
436
480
|
}
|
|
437
481
|
else {
|
|
438
482
|
console.info("unexpected conditional expression", {rule});
|
|
439
|
-
|
|
483
|
+
rule.formula = '';
|
|
440
484
|
}
|
|
441
485
|
}
|
|
442
486
|
|
|
@@ -514,7 +558,7 @@ export class Importer {
|
|
|
514
558
|
else if (color_element.a$.theme) {
|
|
515
559
|
(color as ThemeColor).theme = Number(color_element.a$.theme) || 0;
|
|
516
560
|
if (color_element.a$.tint) {
|
|
517
|
-
(color as ThemeColor).tint = Math.round(color_element.a$.tint * 1000) / 1000;
|
|
561
|
+
(color as ThemeColor).tint = Math.round(Number(color_element.a$.tint) * 1000) / 1000;
|
|
518
562
|
}
|
|
519
563
|
}
|
|
520
564
|
|
|
@@ -589,7 +633,34 @@ export class Importer {
|
|
|
589
633
|
|
|
590
634
|
const annotations: AnchoredAnnotation[] = [];
|
|
591
635
|
|
|
592
|
-
const FindAll: (path: string) =>
|
|
636
|
+
const FindAll: <T = GenericDOMElement>(path: string) => T[] = XMLUtils.FindAll.bind(XMLUtils, sheet.sheet_data);
|
|
637
|
+
|
|
638
|
+
// tab color
|
|
639
|
+
|
|
640
|
+
const tab_color_element = FindAll('worksheet/sheetPr/tabColor');
|
|
641
|
+
|
|
642
|
+
let tab_color: Color|undefined;
|
|
643
|
+
|
|
644
|
+
if (tab_color_element?.[0]) {
|
|
645
|
+
|
|
646
|
+
const element = tab_color_element[0];
|
|
647
|
+
if (element.a$?.theme) {
|
|
648
|
+
tab_color = { theme: Number(element.a$.theme) };
|
|
649
|
+
if (element.a$?.tint) {
|
|
650
|
+
tab_color.tint = Number(element.a$.tint);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (element.a$?.rgb) {
|
|
654
|
+
const argb = element.a$.rgb;
|
|
655
|
+
tab_color = {
|
|
656
|
+
text: '#' + (
|
|
657
|
+
argb.length > 6 ?
|
|
658
|
+
argb.substr(argb.length - 6) :
|
|
659
|
+
argb),
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
}
|
|
593
664
|
|
|
594
665
|
// conditionals
|
|
595
666
|
|
|
@@ -607,7 +678,7 @@ export class Importer {
|
|
|
607
678
|
if (element.cfRule) {
|
|
608
679
|
const rules = Array.isArray(element.cfRule) ? element.cfRule : [element.cfRule];
|
|
609
680
|
for (const rule of rules) {
|
|
610
|
-
const format = this.ParseConditionalFormat(area, rule);
|
|
681
|
+
const format = this.ParseConditionalFormat(area, rule as unknown as ConditionalFormatRule);
|
|
611
682
|
if (format) {
|
|
612
683
|
if (Array.isArray(format)) {
|
|
613
684
|
conditional_formats.push(...format);
|
|
@@ -628,7 +699,7 @@ export class Importer {
|
|
|
628
699
|
const merge_cells = FindAll('worksheet/mergeCells/mergeCell');
|
|
629
700
|
|
|
630
701
|
for (const element of merge_cells) {
|
|
631
|
-
if (element.a
|
|
702
|
+
if (element.a$?.ref) {
|
|
632
703
|
const merge = sheet.TranslateAddress(element.a$.ref);
|
|
633
704
|
if (is_range(merge)) {
|
|
634
705
|
merges.push(ShiftRange(merge, -1, -1));
|
|
@@ -644,7 +715,7 @@ export class Importer {
|
|
|
644
715
|
const ref = entry.a$?.sqref;
|
|
645
716
|
const formula = entry.formula1;
|
|
646
717
|
|
|
647
|
-
if (ref && formula && type === 'list') {
|
|
718
|
+
if (ref && formula && typeof formula === 'string' && type === 'list') {
|
|
648
719
|
// let address: ICellAddress|undefined;
|
|
649
720
|
let validation: DataValidation|undefined;
|
|
650
721
|
let parse_result = this.parser.Parse(ref);
|
|
@@ -763,8 +834,8 @@ export class Importer {
|
|
|
763
834
|
|
|
764
835
|
}
|
|
765
836
|
else {
|
|
766
|
-
reference = child.__location
|
|
767
|
-
text = child.__display
|
|
837
|
+
reference = typeof child.__location === 'string' ? child.__location : '';
|
|
838
|
+
text = typeof child.__display === 'string' ? child.__display : '';
|
|
768
839
|
}
|
|
769
840
|
|
|
770
841
|
links.push({ address, reference, text });
|
|
@@ -831,11 +902,10 @@ export class Importer {
|
|
|
831
902
|
row_heights[row_index - 1] = height;
|
|
832
903
|
}
|
|
833
904
|
|
|
834
|
-
|
|
835
|
-
if (!Array.isArray(cells)) { cells = [cells]; }
|
|
905
|
+
const cells = row.c ? Array.isArray(row.c) ? row.c : [row.c] : [];
|
|
836
906
|
|
|
837
907
|
for (const element of cells) {
|
|
838
|
-
const cell = this.ParseCell(sheet, element, shared_formulae, arrays, merges, links); // , validations);
|
|
908
|
+
const cell = this.ParseCell(sheet, element as unknown as CellElementType, shared_formulae, arrays, merges, links); // , validations);
|
|
839
909
|
if (cell) {
|
|
840
910
|
data.push(cell);
|
|
841
911
|
}
|
|
@@ -1120,7 +1190,8 @@ export class Importer {
|
|
|
1120
1190
|
type = 'treb-chart';
|
|
1121
1191
|
func = 'Box.Plot';
|
|
1122
1192
|
if (series?.length) {
|
|
1123
|
-
args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})`
|
|
1193
|
+
args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})`).join(', ')})`;
|
|
1194
|
+
console.info("S?", {series}, args[0])
|
|
1124
1195
|
}
|
|
1125
1196
|
args[1] = descriptor.chart.title;
|
|
1126
1197
|
break;
|
|
@@ -1129,7 +1200,7 @@ export class Importer {
|
|
|
1129
1200
|
type = 'treb-chart';
|
|
1130
1201
|
func = 'Scatter.Line';
|
|
1131
1202
|
if (series && series.length) {
|
|
1132
|
-
args[0] = `Group(${series.map(s => `Series(${s.title || ''},${s.categories||''},${s.values||''})`
|
|
1203
|
+
args[0] = `Group(${series.map(s => `Series(${s.title || ''},${s.categories||''},${s.values||''})`).join(', ')})`;
|
|
1133
1204
|
}
|
|
1134
1205
|
args[1] = descriptor.chart.title;
|
|
1135
1206
|
break;
|
|
@@ -1165,7 +1236,7 @@ export class Importer {
|
|
|
1165
1236
|
|
|
1166
1237
|
if (series) {
|
|
1167
1238
|
if (series.length > 1) {
|
|
1168
|
-
args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})`
|
|
1239
|
+
args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})`).join(', ')})`;
|
|
1169
1240
|
}
|
|
1170
1241
|
else if (series.length === 1) {
|
|
1171
1242
|
if (series[0].title) {
|
|
@@ -1289,6 +1360,7 @@ export class Importer {
|
|
|
1289
1360
|
default_column_width,
|
|
1290
1361
|
column_widths,
|
|
1291
1362
|
row_heights,
|
|
1363
|
+
tab_color,
|
|
1292
1364
|
row_styles,
|
|
1293
1365
|
annotations,
|
|
1294
1366
|
conditional_formats,
|
|
@@ -23,7 +23,7 @@ import type { ImportedSheetData } from 'treb-base-types';
|
|
|
23
23
|
import type { SerializedModel } from 'treb-data-model';
|
|
24
24
|
|
|
25
25
|
import { Exporter } from './export';
|
|
26
|
-
import { Importer } from './
|
|
26
|
+
import { Importer } from './import';
|
|
27
27
|
|
|
28
28
|
const ctx: Worker = self as unknown as Worker;
|
|
29
29
|
const exporter = new Exporter();
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { XMLParser } from 'fast-xml-parser';
|
|
23
|
-
import { XMLUtils,
|
|
23
|
+
import { XMLUtils, XMLOptions2 } from './xml-utils';
|
|
24
24
|
|
|
25
25
|
// const xmlparser = new XMLParser();
|
|
26
|
-
const xmlparser1 = new XMLParser(XMLOptions);
|
|
26
|
+
// const xmlparser1 = new XMLParser(XMLOptions);
|
|
27
27
|
const xmlparser2 = new XMLParser(XMLOptions2);
|
|
28
28
|
|
|
29
29
|
// import * as he from 'he';
|
|
@@ -45,10 +45,10 @@ import type { SerializedNamed } from 'treb-data-model';
|
|
|
45
45
|
export const ConditionalFormatOperators: Record<string, string> = {
|
|
46
46
|
greaterThan: '>',
|
|
47
47
|
greaterThanOrEqual: '>=',
|
|
48
|
-
greaterThanOrEquals: '>=',
|
|
48
|
+
// greaterThanOrEquals: '>=',
|
|
49
49
|
lessThan: '<',
|
|
50
50
|
lessThanOrEqual: '<=',
|
|
51
|
-
lessThanOrEquals: '<=',
|
|
51
|
+
// lessThanOrEquals: '<=',
|
|
52
52
|
equal: '=',
|
|
53
53
|
notEqual: '<>',
|
|
54
54
|
};
|
|
@@ -514,7 +514,8 @@ export class Workbook {
|
|
|
514
514
|
const data = this.zip.Get(reference.replace(/^../, 'xl'));
|
|
515
515
|
if (!data) { return undefined; }
|
|
516
516
|
|
|
517
|
-
const xml = xmlparser1.parse(data);
|
|
517
|
+
// const xml = xmlparser1.parse(data);
|
|
518
|
+
const xml = xmlparser2.parse(data);
|
|
518
519
|
|
|
519
520
|
const result: ChartDescription = {
|
|
520
521
|
type: ChartType.Unknown
|
|
@@ -532,8 +533,8 @@ export class Workbook {
|
|
|
532
533
|
if (typeof node === 'string') {
|
|
533
534
|
result.title = node;
|
|
534
535
|
}
|
|
535
|
-
else if (node.
|
|
536
|
-
result.title = node.
|
|
536
|
+
else if (node.t$) {
|
|
537
|
+
result.title = node.t$; // why is this not quoted, if the later one is quoted? is this a reference?
|
|
537
538
|
}
|
|
538
539
|
}
|
|
539
540
|
else {
|
|
@@ -553,14 +554,14 @@ export class Workbook {
|
|
|
553
554
|
series_nodes = [series_nodes];
|
|
554
555
|
}
|
|
555
556
|
|
|
556
|
-
// console.info(
|
|
557
|
+
// console.info({SN: series_nodes});
|
|
557
558
|
|
|
558
559
|
for (const series_node of series_nodes) {
|
|
559
560
|
|
|
560
561
|
let index = series.length;
|
|
561
562
|
const order_node = series_node['c:order'];
|
|
562
563
|
if (order_node) {
|
|
563
|
-
index = Number(order_node.
|
|
564
|
+
index = Number(order_node.a$?.val||0) || 0;
|
|
564
565
|
}
|
|
565
566
|
|
|
566
567
|
const series_data: ChartSeries = {};
|
|
@@ -626,7 +627,7 @@ export class Workbook {
|
|
|
626
627
|
result.type = ChartType.Bar;
|
|
627
628
|
// console.info("BD", node);
|
|
628
629
|
if (node['c:barDir']) {
|
|
629
|
-
if (node['c:barDir'].
|
|
630
|
+
if (node['c:barDir'].a$?.val === 'col') {
|
|
630
631
|
result.type = ChartType.Column;
|
|
631
632
|
}
|
|
632
633
|
}
|
|
@@ -682,7 +683,7 @@ export class Workbook {
|
|
|
682
683
|
|
|
683
684
|
const ex_series = XMLUtils.FindAll(xml, 'cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series');
|
|
684
685
|
if (ex_series?.length) {
|
|
685
|
-
if (ex_series.every(test => test.
|
|
686
|
+
if (ex_series.every(test => test.a$?.layoutId === 'boxWhisker')) {
|
|
686
687
|
result.type = ChartType.Box;
|
|
687
688
|
result.series = [];
|
|
688
689
|
const data = XMLUtils.FindAll(xml, 'cx:chartSpace/cx:chartData/cx:data'); // /cx:data/cx:numDim/cx:f');
|
|
@@ -693,9 +694,9 @@ export class Workbook {
|
|
|
693
694
|
|
|
694
695
|
const series: ChartSeries = {};
|
|
695
696
|
|
|
696
|
-
const id = Number(entry['cx:dataId']?.
|
|
697
|
+
const id = Number(entry['cx:dataId']?.a$?.val);
|
|
697
698
|
for (const data_series of data) {
|
|
698
|
-
if (Number(data_series.
|
|
699
|
+
if (Number(data_series.a$?.id) === id) {
|
|
699
700
|
series.values = data_series['cx:numDim']?.['cx:f'] || '';
|
|
700
701
|
break;
|
|
701
702
|
}
|
|
@@ -28,6 +28,26 @@ export interface DOMContent {
|
|
|
28
28
|
[index: string]: string|DOMContent|string[]|DOMContent[]|number|number[]|undefined;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// --- take 2 ------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
export interface XMLKeys {
|
|
34
|
+
a$?: Record<string, string>;
|
|
35
|
+
t$?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type DOMElementType = number|number[]|string|string[]|(BaseDOM & XMLKeys)|(BaseDOM & XMLKeys)[];
|
|
39
|
+
|
|
40
|
+
export interface BaseDOM {
|
|
41
|
+
[key: string]: DOMElementType;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type GenericDOMElement = BaseDOM & XMLKeys;
|
|
45
|
+
|
|
46
|
+
export const GenericDOMArray = (element: GenericDOMElement|GenericDOMElement[]): GenericDOMElement[] => element ? Array.isArray(element) ? element : [element] : [];
|
|
47
|
+
|
|
48
|
+
// -----------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
|
|
31
51
|
/**
|
|
32
52
|
* not sure why we have to do this, but filter attributes that
|
|
33
53
|
* have value === undefined
|
|
@@ -76,6 +96,7 @@ export const PatchXMLBuilder = (options: Partial<XmlBuilderOptions>) => {
|
|
|
76
96
|
|
|
77
97
|
//////////////////
|
|
78
98
|
|
|
99
|
+
/*
|
|
79
100
|
export const XMLOptions: Partial<X2jOptions> = {
|
|
80
101
|
ignoreAttributes: false,
|
|
81
102
|
attributeNamePrefix: '__',
|
|
@@ -84,6 +105,7 @@ export const XMLOptions: Partial<X2jOptions> = {
|
|
|
84
105
|
tagValueProcessor: XMLTagProcessor,
|
|
85
106
|
ignoreDeclaration: true,
|
|
86
107
|
};
|
|
108
|
+
*/
|
|
87
109
|
|
|
88
110
|
/**
|
|
89
111
|
* group attributes under `a$`, and don't add attribute prefixes (should be
|
|
@@ -91,7 +113,6 @@ export const XMLOptions: Partial<X2jOptions> = {
|
|
|
91
113
|
*/
|
|
92
114
|
export const XMLOptions2: Partial<X2jOptions> = {
|
|
93
115
|
ignoreAttributes: false,
|
|
94
|
-
// attrNodeName: 'a$', // FXP v4
|
|
95
116
|
attributesGroupName: 'a$',
|
|
96
117
|
attributeNamePrefix: '',
|
|
97
118
|
textNodeName: 't$',
|
|
@@ -96,6 +96,8 @@ export abstract class BaseLayout {
|
|
|
96
96
|
|
|
97
97
|
public header_size: Size = { width: 0, height: 0 };
|
|
98
98
|
|
|
99
|
+
public applied_theme_colors: string[] = [];
|
|
100
|
+
|
|
99
101
|
/**
|
|
100
102
|
* last rendered column. this is used to calculate the limits of
|
|
101
103
|
* cell overflows, which may exceed actual data in the sheet.
|
|
@@ -1280,6 +1282,18 @@ export abstract class BaseLayout {
|
|
|
1280
1282
|
|
|
1281
1283
|
}
|
|
1282
1284
|
|
|
1285
|
+
public ApplyThemeColors() {
|
|
1286
|
+
|
|
1287
|
+
// what's the best node for this? (...)
|
|
1288
|
+
|
|
1289
|
+
if (this.container) {
|
|
1290
|
+
for (const [index, entry] of this.applied_theme_colors.entries()) {
|
|
1291
|
+
this.container.style.setProperty(`--treb-applied-theme-color-${index + 1}`, entry);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1283
1297
|
/**
|
|
1284
1298
|
* applies theme to nodes, as necessary
|
|
1285
1299
|
*/
|
|
@@ -1307,6 +1321,13 @@ export abstract class BaseLayout {
|
|
|
1307
1321
|
|
|
1308
1322
|
this.dropdown_list.style.font = Style.Font(theme.grid_cell || {});
|
|
1309
1323
|
|
|
1324
|
+
// testing
|
|
1325
|
+
|
|
1326
|
+
this.applied_theme_colors = theme.theme_colors?.slice(4, 10) || [];
|
|
1327
|
+
if (this.container) {
|
|
1328
|
+
this.ApplyThemeColors();
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1310
1331
|
}
|
|
1311
1332
|
|
|
1312
1333
|
public UpdateTotalSize(): void {
|
|
@@ -967,7 +967,7 @@ export class Grid extends GridBase {
|
|
|
967
967
|
if (add_to_layout) {
|
|
968
968
|
this.layout.AddAnnotation(annotation);
|
|
969
969
|
if (annotation.data.layout) {
|
|
970
|
-
this.EnsureAddress(annotation.data.layout.br.address, 1);
|
|
970
|
+
this.EnsureAddress(annotation.data.layout.br.address, 1, toll_events);
|
|
971
971
|
}
|
|
972
972
|
}
|
|
973
973
|
else {
|
|
@@ -1190,7 +1190,7 @@ export class Grid extends GridBase {
|
|
|
1190
1190
|
this.QueueLayoutUpdate();
|
|
1191
1191
|
|
|
1192
1192
|
this.StyleDefaultFromTheme();
|
|
1193
|
-
|
|
1193
|
+
|
|
1194
1194
|
if (render) {
|
|
1195
1195
|
this.Repaint(false, false); // true, true);
|
|
1196
1196
|
}
|
|
@@ -1403,6 +1403,10 @@ export class Grid extends GridBase {
|
|
|
1403
1403
|
|
|
1404
1404
|
// this.tile_renderer.UpdateTheme(); // has reference
|
|
1405
1405
|
|
|
1406
|
+
if (this.tab_bar) {
|
|
1407
|
+
this.tab_bar.UpdateTheme();
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1406
1410
|
if (!initial) {
|
|
1407
1411
|
|
|
1408
1412
|
this.UpdateLayout(); // in case we have changed font size
|
|
@@ -1454,7 +1458,7 @@ export class Grid extends GridBase {
|
|
|
1454
1458
|
|
|
1455
1459
|
if (this.options.tab_bar) {
|
|
1456
1460
|
|
|
1457
|
-
this.tab_bar = new TabBar(this.layout, this.model, this.view, this.options, view_node);
|
|
1461
|
+
this.tab_bar = new TabBar(this.layout, this.model, this.view, this.options, this.theme, view_node);
|
|
1458
1462
|
this.tab_bar.Subscribe((event) => {
|
|
1459
1463
|
switch (event.type) {
|
|
1460
1464
|
case 'cancel':
|
|
@@ -5936,7 +5940,7 @@ export class Grid extends GridBase {
|
|
|
5936
5940
|
/**
|
|
5937
5941
|
* if the address is outside of current extent, expand
|
|
5938
5942
|
*/
|
|
5939
|
-
private EnsureAddress(address: ICellAddress, step = 8): boolean {
|
|
5943
|
+
private EnsureAddress(address: ICellAddress, step = 8, toll_layout = false): boolean {
|
|
5940
5944
|
|
|
5941
5945
|
let expanded = false;
|
|
5942
5946
|
|
|
@@ -5959,7 +5963,7 @@ export class Grid extends GridBase {
|
|
|
5959
5963
|
expanded = true;
|
|
5960
5964
|
}
|
|
5961
5965
|
|
|
5962
|
-
if (expanded) {
|
|
5966
|
+
if (expanded && !toll_layout) {
|
|
5963
5967
|
this.layout.UpdateTiles();
|
|
5964
5968
|
this.layout.UpdateContentsSize();
|
|
5965
5969
|
this.Repaint(true, true);
|
|
@@ -25,7 +25,7 @@ import type { BaseLayout } from '../layout/base_layout';
|
|
|
25
25
|
import { MouseDrag } from './drag_mask';
|
|
26
26
|
import type { GridOptions } from './grid_options';
|
|
27
27
|
import { type ScaleEvent, ScaleControl } from './scale-control';
|
|
28
|
-
import { DOMContext } from 'treb-base-types';
|
|
28
|
+
import { DOMContext, ResolveThemeColor, type Theme } from 'treb-base-types';
|
|
29
29
|
|
|
30
30
|
export interface ActivateSheetEvent {
|
|
31
31
|
type: 'activate-sheet';
|
|
@@ -100,6 +100,8 @@ export class TabBar extends EventSource<TabEvent> {
|
|
|
100
100
|
timeout?: number;
|
|
101
101
|
} = {};
|
|
102
102
|
|
|
103
|
+
private tab_color_cache: Map<number, { background: string, foreground: string }> = new Map();
|
|
104
|
+
|
|
103
105
|
// tslint:disable-next-line: variable-name
|
|
104
106
|
private _visible = false;
|
|
105
107
|
|
|
@@ -133,6 +135,7 @@ export class TabBar extends EventSource<TabEvent> {
|
|
|
133
135
|
private model: DataModel,
|
|
134
136
|
private view: ViewModel,
|
|
135
137
|
private options: GridOptions,
|
|
138
|
+
private theme: Theme,
|
|
136
139
|
// private container: HTMLElement,
|
|
137
140
|
view_node: HTMLElement,
|
|
138
141
|
) {
|
|
@@ -236,10 +239,21 @@ export class TabBar extends EventSource<TabEvent> {
|
|
|
236
239
|
if (active) {
|
|
237
240
|
// tab.classList.add('treb-selected');
|
|
238
241
|
tab.setAttribute('selected', '');
|
|
242
|
+
|
|
243
|
+
if (tab.dataset.background_color) {
|
|
244
|
+
tab.style.backgroundColor = `color-mix(in srgb, ${tab.dataset.background_color} 20%, var(--treb-tab-bar-active-tab-background, #fff))`;
|
|
245
|
+
tab.style.color = '';
|
|
246
|
+
}
|
|
239
247
|
}
|
|
240
248
|
else {
|
|
241
249
|
// tab.classList.remove('treb-selected');
|
|
242
250
|
tab.removeAttribute('selected');
|
|
251
|
+
if (tab.dataset.background_color) {
|
|
252
|
+
tab.style.backgroundColor = tab.dataset.background_color;
|
|
253
|
+
}
|
|
254
|
+
if (tab.dataset.foreground_color) {
|
|
255
|
+
tab.style.color = tab.dataset.foreground_color;
|
|
256
|
+
}
|
|
243
257
|
}
|
|
244
258
|
}
|
|
245
259
|
|
|
@@ -419,6 +433,11 @@ export class TabBar extends EventSource<TabEvent> {
|
|
|
419
433
|
|
|
420
434
|
}
|
|
421
435
|
|
|
436
|
+
public UpdateTheme() {
|
|
437
|
+
this.tab_color_cache.clear();
|
|
438
|
+
this.Update();
|
|
439
|
+
}
|
|
440
|
+
|
|
422
441
|
/**
|
|
423
442
|
* update tabs from model.
|
|
424
443
|
*/
|
|
@@ -464,6 +483,24 @@ export class TabBar extends EventSource<TabEvent> {
|
|
|
464
483
|
const tab = this.DOM.Create('li');
|
|
465
484
|
tab.setAttribute('tabindex', '0');
|
|
466
485
|
|
|
486
|
+
if (sheet.tab_color) {
|
|
487
|
+
const id = sheet.id;
|
|
488
|
+
if (!this.tab_color_cache.has(id)) {
|
|
489
|
+
const background = ResolveThemeColor(this.theme, sheet.tab_color);
|
|
490
|
+
const foreground = ResolveThemeColor(this.theme, { offset: sheet.tab_color });
|
|
491
|
+
if (background && foreground) {
|
|
492
|
+
this.tab_color_cache.set(id, { background, foreground });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
const color = this.tab_color_cache.get(id);
|
|
496
|
+
if (color) {
|
|
497
|
+
tab.style.backgroundColor = color.background;
|
|
498
|
+
tab.style.color = color.foreground;
|
|
499
|
+
tab.dataset.background_color = color.background;
|
|
500
|
+
tab.dataset.foreground_color = color.foreground;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
467
504
|
// tab.classList.add('tab');
|
|
468
505
|
tab.style.order = (index * 2).toString();
|
|
469
506
|
tab.role = 'tab';
|