@trebco/treb 30.6.3 → 30.9.2
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 +10 -10
- 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 +34 -7
- package/treb-calculator/src/calculator.ts +29 -6
- package/treb-calculator/src/expression-calculator.ts +107 -3
- package/treb-calculator/src/functions/base-functions.ts +78 -1
- package/treb-calculator/src/functions/sparkline.ts +20 -0
- package/treb-calculator/src/functions/statistics-functions.ts +2 -0
- package/treb-data-model/src/conditional_format.ts +4 -1
- package/treb-data-model/src/sheet.ts +15 -4
- package/treb-data-model/src/sheet_types.ts +4 -1
- package/treb-embed/src/embedded-spreadsheet.ts +56 -0
- 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} +126 -22
- package/treb-export/src/index.worker.ts +1 -1
- package/treb-export/src/workbook2.ts +7 -2
- package/treb-grid/src/editors/editor.ts +1 -1
- package/treb-grid/src/types/grid.ts +9 -5
- package/treb-grid/src/types/tab_bar.ts +38 -1
- package/treb-parser/src/parser.ts +14 -4
|
@@ -4840,6 +4840,53 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
4840
4840
|
return reject(event.data.error || 'unknown error');
|
|
4841
4841
|
}
|
|
4842
4842
|
|
|
4843
|
+
if (this.parser.decimal_mark !== DecimalMarkType.Period) {
|
|
4844
|
+
|
|
4845
|
+
// console.info("IMPORT WARNING 2", event.data);
|
|
4846
|
+
|
|
4847
|
+
// FIXME: unify w/ the convert locale method
|
|
4848
|
+
|
|
4849
|
+
const target_decimal_mark = this.parser.decimal_mark;
|
|
4850
|
+
const target_argument_separator = this.parser.argument_separator;
|
|
4851
|
+
this.parser.Save();
|
|
4852
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
4853
|
+
|
|
4854
|
+
const translate = (formula: string): string | undefined => {
|
|
4855
|
+
const parse_result = this.parser.Parse(formula);
|
|
4856
|
+
if (!parse_result.expression) { return undefined; }
|
|
4857
|
+
return '=' + this.parser.Render(
|
|
4858
|
+
parse_result.expression, {
|
|
4859
|
+
missing: '',
|
|
4860
|
+
convert_decimal: target_decimal_mark,
|
|
4861
|
+
convert_argument_separator: target_argument_separator,
|
|
4862
|
+
});
|
|
4863
|
+
};
|
|
4864
|
+
|
|
4865
|
+
for (const named of event.data.results) {
|
|
4866
|
+
named.expression = translate(named.expression);
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
for (const sheet of event.data.results.sheets || []) {
|
|
4870
|
+
|
|
4871
|
+
for (const cell of sheet.cells || []) {
|
|
4872
|
+
if (cell.type === 'formula' && cell.value) {
|
|
4873
|
+
cell.value = translate(cell.value);
|
|
4874
|
+
}
|
|
4875
|
+
}
|
|
4876
|
+
|
|
4877
|
+
if (sheet.annotations){
|
|
4878
|
+
for (const annotation of sheet.annotations) {
|
|
4879
|
+
if (annotation.formula) {
|
|
4880
|
+
annotation.formula = translate(annotation.formula);
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
|
|
4886
|
+
this.parser.Restore();
|
|
4887
|
+
|
|
4888
|
+
}
|
|
4889
|
+
|
|
4843
4890
|
this.grid.FromImportData(event.data.results);
|
|
4844
4891
|
|
|
4845
4892
|
this.ResetInternal();
|
|
@@ -5914,6 +5961,15 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
5914
5961
|
}
|
|
5915
5962
|
}
|
|
5916
5963
|
|
|
5964
|
+
if (data.named) {
|
|
5965
|
+
for (const named of data.named) {
|
|
5966
|
+
const translated = translate(named.expression);
|
|
5967
|
+
if (translated) {
|
|
5968
|
+
named.expression = translated;
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
5971
|
+
}
|
|
5972
|
+
|
|
5917
5973
|
if (data.sheet_data) {
|
|
5918
5974
|
|
|
5919
5975
|
const sheets = Array.isArray(data.sheet_data) ? data.sheet_data : [data.sheet_data];
|
|
@@ -437,6 +437,22 @@ $text-reference-color-5: rgb(254, 47, 1);
|
|
|
437
437
|
&[selected] {
|
|
438
438
|
background: var(--treb-tab-bar-active-tab-background, #fff);
|
|
439
439
|
color: var(--treb-tab-bar-active-tab-color, var(--treb-ui-color, inherit));
|
|
440
|
+
|
|
441
|
+
border-bottom-color: var(--treb-tab-bar-active-tab-border-color, currentColor);
|
|
442
|
+
|
|
443
|
+
/*
|
|
444
|
+
position: relative;
|
|
445
|
+
&::after {
|
|
446
|
+
content: '';
|
|
447
|
+
position: absolute;
|
|
448
|
+
bottom: 0px;
|
|
449
|
+
left: 0px;
|
|
450
|
+
width: 100%;
|
|
451
|
+
height: 1px;
|
|
452
|
+
background: currentColor;
|
|
453
|
+
}
|
|
454
|
+
*/
|
|
455
|
+
|
|
440
456
|
}
|
|
441
457
|
}
|
|
442
458
|
|
|
@@ -439,13 +439,7 @@ export class Exporter {
|
|
|
439
439
|
|
|
440
440
|
const dxf: DOMContent[] = style_cache.dxf_styles.map(style => {
|
|
441
441
|
|
|
442
|
-
const entry: DOMContent = {
|
|
443
|
-
fill: style.fill ? {
|
|
444
|
-
patternFill: {
|
|
445
|
-
bgColor: ColorAttrs(style.fill),
|
|
446
|
-
},
|
|
447
|
-
} : undefined,
|
|
448
|
-
};
|
|
442
|
+
const entry: DOMContent = {};
|
|
449
443
|
|
|
450
444
|
if (style.text || style.bold || style.italic || style.underline) {
|
|
451
445
|
entry.font = {
|
|
@@ -457,6 +451,14 @@ export class Exporter {
|
|
|
457
451
|
};
|
|
458
452
|
}
|
|
459
453
|
|
|
454
|
+
if (style.fill) {
|
|
455
|
+
entry.fill = {
|
|
456
|
+
patternFill: {
|
|
457
|
+
bgColor: ColorAttrs(style.fill),
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
|
|
460
462
|
return entry;
|
|
461
463
|
|
|
462
464
|
});
|
|
@@ -2214,6 +2216,34 @@ export class Exporter {
|
|
|
2214
2216
|
// delete dom.worksheet.drawing;
|
|
2215
2217
|
}
|
|
2216
2218
|
|
|
2219
|
+
// --- tab color ---------------------------------------------------------
|
|
2220
|
+
|
|
2221
|
+
const tab_color_block: DOMContent = {};
|
|
2222
|
+
if (sheet.tab_color) {
|
|
2223
|
+
if (IsThemeColor(sheet.tab_color)) {
|
|
2224
|
+
tab_color_block.sheetPr = {
|
|
2225
|
+
tabColor: {
|
|
2226
|
+
a$: {
|
|
2227
|
+
theme: sheet.tab_color.theme,
|
|
2228
|
+
tint: sheet.tab_color.tint,
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
else if (IsHTMLColor(sheet.tab_color)) {
|
|
2234
|
+
const color = sheet.tab_color.text || '';
|
|
2235
|
+
if (/^#[0-9a-fA-F]*$/.test(color)) {
|
|
2236
|
+
tab_color_block.sheetPr = {
|
|
2237
|
+
tabColor: {
|
|
2238
|
+
a$: {
|
|
2239
|
+
rgb: `FF` + color.substring(1)
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2217
2247
|
// --- move page margins -------------------------------------------------
|
|
2218
2248
|
|
|
2219
2249
|
// const margins = dom.worksheet.pageMargins;
|
|
@@ -2242,6 +2272,9 @@ export class Exporter {
|
|
|
2242
2272
|
|
|
2243
2273
|
worksheet: {
|
|
2244
2274
|
a$: { ...sheet_attributes },
|
|
2275
|
+
|
|
2276
|
+
...tab_color_block,
|
|
2277
|
+
|
|
2245
2278
|
dimension: {
|
|
2246
2279
|
a$: {
|
|
2247
2280
|
ref: new Area(extent.start, extent.end).spreadsheet_label,
|
|
@@ -27,10 +27,10 @@ import Base64JS from 'base64-js';
|
|
|
27
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
|
-
import { Parser } from 'treb-parser';
|
|
30
|
+
import { DecimalMarkType, Parser } from 'treb-parser';
|
|
31
31
|
import type { RangeType, AddressType, HyperlinkType } from './address-type';
|
|
32
32
|
import { is_range, ShiftRange, InRange, is_address } from './address-type';
|
|
33
|
-
import type
|
|
33
|
+
import { type ImportedSheetData, type AnchoredAnnotation, type CellParseResult, type AnnotationLayout, type Corner as LayoutCorner, type IArea, type GradientStop, type Color, type HTMLColor, type ThemeColor, Area } from 'treb-base-types';
|
|
34
34
|
import type { SerializedValueType } from 'treb-base-types';
|
|
35
35
|
import type { Sheet} from './workbook-sheet2';
|
|
36
36
|
import { VisibleState } from './workbook-sheet2';
|
|
@@ -166,20 +166,26 @@ export class Importer {
|
|
|
166
166
|
// doing it like this is sloppy (also does not work properly).
|
|
167
167
|
value = '=' + formula.replace(/^_xll\./g, '');
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
169
|
+
// drop the formula if it's a ref error, we can't handle this
|
|
170
|
+
if (/#REF/.test(formula)) {
|
|
171
|
+
value = formula;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const parse_result = this.parser.Parse(formula); // l10n?
|
|
175
|
+
if (parse_result.expression) {
|
|
176
|
+
this.parser.Walk(parse_result.expression, (unit) => {
|
|
177
|
+
if (unit.type === 'call') {
|
|
178
|
+
if (/^_xll\./.test(unit.name)) {
|
|
179
|
+
unit.name = unit.name.substring(5);
|
|
180
|
+
}
|
|
181
|
+
else if (/^_xlfn\./.test(unit.name)) {
|
|
182
|
+
unit.name = unit.name.substring(6);
|
|
183
|
+
}
|
|
178
184
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
return true;
|
|
186
|
+
});
|
|
187
|
+
value = '=' + this.parser.Render(parse_result.expression, { missing: '' });
|
|
188
|
+
}
|
|
183
189
|
}
|
|
184
190
|
|
|
185
191
|
if (typeof element.f !== 'string') {
|
|
@@ -341,7 +347,7 @@ export class Importer {
|
|
|
341
347
|
|
|
342
348
|
}
|
|
343
349
|
|
|
344
|
-
public ParseConditionalFormat(address: RangeType|AddressType, rule: any): ConditionalFormat|undefined {
|
|
350
|
+
public ParseConditionalFormat(address: RangeType|AddressType, rule: any): ConditionalFormat|ConditionalFormat[]|undefined {
|
|
345
351
|
|
|
346
352
|
const area = this.AddressToArea(address);
|
|
347
353
|
const operators = ConditionalFormatOperators;
|
|
@@ -365,11 +371,13 @@ export class Importer {
|
|
|
365
371
|
area,
|
|
366
372
|
style,
|
|
367
373
|
unique: (rule.a$.type === 'uniqueValues'),
|
|
374
|
+
priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
|
|
375
|
+
|
|
368
376
|
};
|
|
369
377
|
}
|
|
370
378
|
|
|
371
379
|
case 'cellIs':
|
|
372
|
-
if (rule.a$.operator && rule.formula) {
|
|
380
|
+
if (rule.a$.operator && (rule.formula || typeof rule.formula === 'number')) {
|
|
373
381
|
let style = {};
|
|
374
382
|
|
|
375
383
|
if (rule.a$.dxfId) {
|
|
@@ -379,10 +387,26 @@ export class Importer {
|
|
|
379
387
|
}
|
|
380
388
|
}
|
|
381
389
|
|
|
390
|
+
if (rule.a$.operator === 'between') {
|
|
391
|
+
if (Array.isArray(rule.formula) && rule.formula.length === 2
|
|
392
|
+
&& typeof rule.formula[0] === 'number' && typeof rule.formula[1] === 'number') {
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
type: 'cell-match',
|
|
396
|
+
expression: '',
|
|
397
|
+
between: rule.formula, // special case? ugh
|
|
398
|
+
area,
|
|
399
|
+
style,
|
|
400
|
+
priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
382
406
|
const operator = operators[rule.a$.operator || ''];
|
|
383
407
|
|
|
384
408
|
if (!operator) {
|
|
385
|
-
console.info('unhandled cellIs operator:', rule.a$.operator);
|
|
409
|
+
console.info('unhandled cellIs operator:', rule.a$.operator, {rule});
|
|
386
410
|
}
|
|
387
411
|
else {
|
|
388
412
|
return {
|
|
@@ -390,10 +414,14 @@ export class Importer {
|
|
|
390
414
|
expression: operator + ' ' + rule.formula,
|
|
391
415
|
area,
|
|
392
416
|
style,
|
|
417
|
+
priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
|
|
393
418
|
};
|
|
394
419
|
}
|
|
395
420
|
|
|
396
421
|
}
|
|
422
|
+
else {
|
|
423
|
+
console.info("miss?", rule);
|
|
424
|
+
}
|
|
397
425
|
break;
|
|
398
426
|
|
|
399
427
|
case 'containsErrors':
|
|
@@ -427,11 +455,51 @@ export class Importer {
|
|
|
427
455
|
}
|
|
428
456
|
}
|
|
429
457
|
|
|
458
|
+
if (rule.a$.type === 'expression' && (area.start.row !== area.end.row || area.start.column !== area.end.column)) {
|
|
459
|
+
|
|
460
|
+
// (1) this is only required if there are relative references
|
|
461
|
+
// in the formula. so we could check and short-circuit.
|
|
462
|
+
//
|
|
463
|
+
// (2) I'd like to find a way to apply this as a single formula,
|
|
464
|
+
// so there's only one rule required.
|
|
465
|
+
|
|
466
|
+
this.parser.Save();
|
|
467
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
468
|
+
|
|
469
|
+
const list: ConditionalFormat[] = [];
|
|
470
|
+
const a2 = new Area(area.start, area.end);
|
|
471
|
+
|
|
472
|
+
const parse_result = this.parser.Parse(rule.formula);
|
|
473
|
+
if (parse_result.expression) {
|
|
474
|
+
for (const cell of a2) {
|
|
475
|
+
const f = this.parser.Render(parse_result.expression, {
|
|
476
|
+
missing: '',
|
|
477
|
+
offset: { rows: cell.row - area.start.row, columns: cell.column - area.start.column }
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
list.push({
|
|
481
|
+
type: 'expression',
|
|
482
|
+
expression: f,
|
|
483
|
+
style,
|
|
484
|
+
area: { start: cell, end: cell },
|
|
485
|
+
priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
// console.info(f);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
this.parser.Restore();
|
|
493
|
+
return list;
|
|
494
|
+
|
|
495
|
+
}
|
|
496
|
+
|
|
430
497
|
return {
|
|
431
498
|
type: 'expression',
|
|
432
499
|
expression: rule.formula,
|
|
433
500
|
area,
|
|
434
501
|
style,
|
|
502
|
+
priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
|
|
435
503
|
};
|
|
436
504
|
|
|
437
505
|
}
|
|
@@ -479,6 +547,8 @@ export class Importer {
|
|
|
479
547
|
stops,
|
|
480
548
|
color_space: 'RGB',
|
|
481
549
|
area,
|
|
550
|
+
priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
|
|
551
|
+
|
|
482
552
|
};
|
|
483
553
|
|
|
484
554
|
}
|
|
@@ -527,6 +597,33 @@ export class Importer {
|
|
|
527
597
|
|
|
528
598
|
const FindAll: (path: string) => any[] = XMLUtils.FindAll.bind(XMLUtils, sheet.sheet_data);
|
|
529
599
|
|
|
600
|
+
// tab color
|
|
601
|
+
|
|
602
|
+
const tab_color_element = FindAll('worksheet/sheetPr/tabColor');
|
|
603
|
+
|
|
604
|
+
let tab_color: Color|undefined;
|
|
605
|
+
|
|
606
|
+
if (tab_color_element?.[0]) {
|
|
607
|
+
|
|
608
|
+
const element = tab_color_element[0];
|
|
609
|
+
if (element.a$?.theme) {
|
|
610
|
+
tab_color = { theme: Number(element.a$.theme) };
|
|
611
|
+
if (element.a$?.tint) {
|
|
612
|
+
tab_color.tint = Number(element.a$.tint);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (element.a$?.rgb) {
|
|
616
|
+
const argb = element.a$.rgb;
|
|
617
|
+
tab_color = {
|
|
618
|
+
text: '#' + (
|
|
619
|
+
argb.length > 6 ?
|
|
620
|
+
argb.substr(argb.length - 6) :
|
|
621
|
+
argb),
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
}
|
|
626
|
+
|
|
530
627
|
// conditionals
|
|
531
628
|
|
|
532
629
|
const conditional_formatting = FindAll('worksheet/conditionalFormatting');
|
|
@@ -545,7 +642,12 @@ export class Importer {
|
|
|
545
642
|
for (const rule of rules) {
|
|
546
643
|
const format = this.ParseConditionalFormat(area, rule);
|
|
547
644
|
if (format) {
|
|
548
|
-
|
|
645
|
+
if (Array.isArray(format)) {
|
|
646
|
+
conditional_formats.push(...format);
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
conditional_formats.push(format);
|
|
650
|
+
}
|
|
549
651
|
}
|
|
550
652
|
}
|
|
551
653
|
}
|
|
@@ -1051,7 +1153,8 @@ export class Importer {
|
|
|
1051
1153
|
type = 'treb-chart';
|
|
1052
1154
|
func = 'Box.Plot';
|
|
1053
1155
|
if (series?.length) {
|
|
1054
|
-
args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})`
|
|
1156
|
+
args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})`).join(', ')})`;
|
|
1157
|
+
console.info("S?", {series}, args[0])
|
|
1055
1158
|
}
|
|
1056
1159
|
args[1] = descriptor.chart.title;
|
|
1057
1160
|
break;
|
|
@@ -1060,7 +1163,7 @@ export class Importer {
|
|
|
1060
1163
|
type = 'treb-chart';
|
|
1061
1164
|
func = 'Scatter.Line';
|
|
1062
1165
|
if (series && series.length) {
|
|
1063
|
-
args[0] = `Group(${series.map(s => `Series(${s.title || ''},${s.categories||''},${s.values||''})`
|
|
1166
|
+
args[0] = `Group(${series.map(s => `Series(${s.title || ''},${s.categories||''},${s.values||''})`).join(', ')})`;
|
|
1064
1167
|
}
|
|
1065
1168
|
args[1] = descriptor.chart.title;
|
|
1066
1169
|
break;
|
|
@@ -1096,7 +1199,7 @@ export class Importer {
|
|
|
1096
1199
|
|
|
1097
1200
|
if (series) {
|
|
1098
1201
|
if (series.length > 1) {
|
|
1099
|
-
args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})`
|
|
1202
|
+
args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})`).join(', ')})`;
|
|
1100
1203
|
}
|
|
1101
1204
|
else if (series.length === 1) {
|
|
1102
1205
|
if (series[0].title) {
|
|
@@ -1220,6 +1323,7 @@ export class Importer {
|
|
|
1220
1323
|
default_column_width,
|
|
1221
1324
|
column_widths,
|
|
1222
1325
|
row_heights,
|
|
1326
|
+
tab_color,
|
|
1223
1327
|
row_styles,
|
|
1224
1328
|
annotations,
|
|
1225
1329
|
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();
|
|
@@ -39,11 +39,16 @@ import { ZipWrapper } from './zip-wrapper';
|
|
|
39
39
|
import type { CellStyle, ThemeColor } from 'treb-base-types';
|
|
40
40
|
import type { SerializedNamed } from 'treb-data-model';
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @privateRemarks -- FIXME: not sure about the equal/equals thing. need to check.
|
|
44
|
+
*/
|
|
42
45
|
export const ConditionalFormatOperators: Record<string, string> = {
|
|
43
46
|
greaterThan: '>',
|
|
44
|
-
|
|
47
|
+
greaterThanOrEqual: '>=',
|
|
48
|
+
// greaterThanOrEquals: '>=',
|
|
45
49
|
lessThan: '<',
|
|
46
|
-
|
|
50
|
+
lessThanOrEqual: '<=',
|
|
51
|
+
// lessThanOrEquals: '<=',
|
|
47
52
|
equal: '=',
|
|
48
53
|
notEqual: '<>',
|
|
49
54
|
};
|
|
@@ -163,7 +163,7 @@ export interface NodeDescriptor {
|
|
|
163
163
|
|
|
164
164
|
export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorEvent> {
|
|
165
165
|
|
|
166
|
-
protected static readonly FormulaChars = ('$^&*(-+={[
|
|
166
|
+
protected static readonly FormulaChars = ('$^&*(-+={[<>/~%@' + Localization.argument_separator).split(''); // FIXME: i18n
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
169
|
* the current edit cell. in the event we're editing a merged or
|
|
@@ -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';
|
|
@@ -136,9 +136,10 @@ const binary_operators_precendence: PrecedenceList = {
|
|
|
136
136
|
':': 13, // range operator
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
/* *
|
|
140
140
|
* binary ops are sorted by length so we can compare long ops first
|
|
141
|
-
|
|
141
|
+
switching to a composite w/ unary operators
|
|
142
|
+
* /
|
|
142
143
|
const binary_operators = Object.keys(binary_operators_precendence).sort(
|
|
143
144
|
(a, b) => b.length - a.length,
|
|
144
145
|
);
|
|
@@ -147,8 +148,17 @@ const binary_operators = Object.keys(binary_operators_precendence).sort(
|
|
|
147
148
|
* unary operators. atm we have no precedence issues, unary operators
|
|
148
149
|
* always have absolute precedence. (for numbers, these are properly part
|
|
149
150
|
* of the number, but consider `=-SUM(1,2)` -- this is an operator).
|
|
151
|
+
*
|
|
152
|
+
* implicit intersection operator should now have precedence over +/-.
|
|
153
|
+
*/
|
|
154
|
+
const unary_operators: PrecedenceList = { '@': 50, '-': 100, '+': 100 };
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* to avoid the double - and +, we're just adding our one extra unary
|
|
158
|
+
* operator. doing this dynamically would be silly, although this does
|
|
159
|
+
* make this code more fragile.
|
|
150
160
|
*/
|
|
151
|
-
const
|
|
161
|
+
const composite_operators: string[] = [...Object.keys(binary_operators_precendence), '@'].sort((a, b) => b.length - a.length);
|
|
152
162
|
|
|
153
163
|
/**
|
|
154
164
|
* parser for spreadsheet language.
|
|
@@ -2092,7 +2102,7 @@ export class Parser {
|
|
|
2092
2102
|
}
|
|
2093
2103
|
|
|
2094
2104
|
protected ConsumeOperator(): ExpressionUnit | null {
|
|
2095
|
-
for (const operator of
|
|
2105
|
+
for (const operator of composite_operators) {
|
|
2096
2106
|
if (this.expression.substr(this.index, operator.length) === operator) {
|
|
2097
2107
|
const position = this.index;
|
|
2098
2108
|
this.index += operator.length;
|