@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
package/dist/treb.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! API v30.
|
|
1
|
+
/*! API v30.9. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* add our tag to the map
|
|
@@ -1779,6 +1779,9 @@ export interface SerializedSheet {
|
|
|
1779
1779
|
/** sheet name */
|
|
1780
1780
|
name?: string;
|
|
1781
1781
|
|
|
1782
|
+
/** tab color */
|
|
1783
|
+
tab_color?: Color;
|
|
1784
|
+
|
|
1782
1785
|
/** current active selection */
|
|
1783
1786
|
selection: SerializedGridSelection;
|
|
1784
1787
|
|
package/package.json
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
*
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
|
|
23
|
+
|
|
22
24
|
/**
|
|
23
25
|
* utility functions, primarily for adjusting lightness. since we generally
|
|
24
26
|
* traffic in RGB (or symbolic colors) that requires translating to/from HSL.
|
|
@@ -120,5 +122,35 @@ export const ColorFunctions = {
|
|
|
120
122
|
return p;
|
|
121
123
|
},
|
|
122
124
|
|
|
125
|
+
////////////////
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
GetLuminance: (r: number, g: number, b: number): number => {
|
|
129
|
+
const a = [r, g, b].map(v => {
|
|
130
|
+
v /= 255;
|
|
131
|
+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
132
|
+
});
|
|
133
|
+
return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
GetContrastRatio: (data: [number, number]): number => {
|
|
137
|
+
data.sort((a, b) => b - a);
|
|
138
|
+
return (data[0] + 0.05) / (data[1] + 0.05);
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
GetTextColor: (background: [number, number, number], a: [number, number, number], b: [number, number, number]) => {
|
|
142
|
+
|
|
143
|
+
const luminance = ColorFunctions.GetLuminance(...background);
|
|
144
|
+
const luminance_a = ColorFunctions.GetLuminance(...a);
|
|
145
|
+
const luminance_b = ColorFunctions.GetLuminance(...b);
|
|
146
|
+
|
|
147
|
+
const contrast_a = ColorFunctions.GetContrastRatio([luminance_a, luminance]);
|
|
148
|
+
const contrast_b = ColorFunctions.GetContrastRatio([luminance_b, luminance]);
|
|
149
|
+
|
|
150
|
+
return contrast_a > contrast_b ? a : b;
|
|
151
|
+
},
|
|
152
|
+
|
|
123
153
|
};
|
|
124
154
|
|
|
155
|
+
|
|
156
|
+
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import type { CellStyle } from './style';
|
|
22
|
+
import type { CellStyle, Color } from './style';
|
|
23
23
|
import type { SerializedValueType } from './value-type';
|
|
24
24
|
import type { IArea } from './area';
|
|
25
25
|
import type { AnnotationLayout } from './layout';
|
|
@@ -64,14 +64,13 @@ export interface ImportedSheetData {
|
|
|
64
64
|
// optional, for backcompat
|
|
65
65
|
sheet_style?: number;
|
|
66
66
|
column_styles?: number[];
|
|
67
|
-
|
|
68
67
|
row_styles?: number[];
|
|
69
68
|
|
|
70
|
-
// new
|
|
71
69
|
annotations?: AnchoredAnnotation[];
|
|
70
|
+
outline?: number[];
|
|
72
71
|
|
|
73
72
|
// new
|
|
74
|
-
|
|
73
|
+
tab_color?: Color;
|
|
75
74
|
|
|
76
75
|
hidden?: boolean;
|
|
77
76
|
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { type Color, type CellStyle, IsHTMLColor, IsThemeColor, ThemeColorIndex, type ThemeColor } from './style';
|
|
23
23
|
import { ColorFunctions } from './color';
|
|
24
24
|
import { DOMContext } from './dom-utilities';
|
|
25
|
+
import { Measurement } from 'treb-utils';
|
|
25
26
|
|
|
26
27
|
/*
|
|
27
28
|
* so this is a little strange. we use CSS to populate a theme object,
|
|
@@ -242,7 +243,15 @@ export const ResolveThemeColor = (theme: Theme, color?: Color, default_index?: n
|
|
|
242
243
|
return '';
|
|
243
244
|
}
|
|
244
245
|
|
|
245
|
-
|
|
246
|
+
let resolved = '';
|
|
247
|
+
|
|
248
|
+
if (IsHTMLColor(color.offset)) {
|
|
249
|
+
const clamped = Measurement.MeasureColor(color.offset.text);
|
|
250
|
+
resolved = `rgb(${clamped[0]}, ${clamped[1]}, ${clamped[2]})`;
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
resolved = ResolveThemeColor(theme, color.offset, undefined);
|
|
254
|
+
}
|
|
246
255
|
|
|
247
256
|
// check cache
|
|
248
257
|
if (theme.offset_cache && theme.offset_cache[resolved]) {
|
|
@@ -253,17 +262,35 @@ export const ResolveThemeColor = (theme: Theme, color?: Color, default_index?: n
|
|
|
253
262
|
|
|
254
263
|
if (resolved) {
|
|
255
264
|
// ok figure it out?
|
|
256
|
-
const match = resolved.match(/rgb\((\d+)
|
|
265
|
+
const match = resolved.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
257
266
|
if (match) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
267
|
+
|
|
268
|
+
type ColorTuple = [number, number, number];
|
|
269
|
+
|
|
270
|
+
const background: ColorTuple = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
// const hsl = ColorFunctions.RGBToHSL(r, g, b);
|
|
274
|
+
// const check = ColorFunctions.GetLuminance(r, g, b);
|
|
275
|
+
|
|
276
|
+
const a = Array.from(Measurement.MeasureColor(theme.offset_dark)) as ColorTuple;
|
|
277
|
+
const b = Array.from(Measurement.MeasureColor(theme.offset_light)) as ColorTuple;
|
|
278
|
+
|
|
279
|
+
const tc = ColorFunctions.GetTextColor(background, a, b);
|
|
280
|
+
|
|
281
|
+
offset = `rgb(${tc[0]}, ${tc[1]}, ${tc[2]})`;
|
|
282
|
+
|
|
283
|
+
/*
|
|
284
|
+
if (hsl.l >.65)) {
|
|
261
285
|
offset = theme.offset_dark;
|
|
262
286
|
}
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
|
|
263
290
|
}
|
|
264
291
|
else {
|
|
265
292
|
// ...
|
|
266
|
-
console.warn(`can't offset against color`, resolved);
|
|
293
|
+
console.warn(`can't offset against color`, resolved, '(1)');
|
|
267
294
|
}
|
|
268
295
|
|
|
269
296
|
if (!theme.offset_cache) {
|
|
@@ -272,7 +299,7 @@ export const ResolveThemeColor = (theme: Theme, color?: Color, default_index?: n
|
|
|
272
299
|
theme.offset_cache[resolved] = offset;
|
|
273
300
|
}
|
|
274
301
|
else {
|
|
275
|
-
console.warn(`can't resolve offset color`, color.offset);
|
|
302
|
+
console.warn(`can't resolve offset color`, color.offset, '(2)');
|
|
276
303
|
}
|
|
277
304
|
|
|
278
305
|
return offset;
|
|
@@ -2383,6 +2383,9 @@ export class Calculator extends Graph {
|
|
|
2383
2383
|
// is it also (3) adding unecessary calculations (building the expression,
|
|
2384
2384
|
// below)?
|
|
2385
2385
|
|
|
2386
|
+
// NOTE: moving all conditional formats into EN-US (i.e. dot-separated).
|
|
2387
|
+
// make sure to evaluate them in this format.
|
|
2388
|
+
|
|
2386
2389
|
if (!list) {
|
|
2387
2390
|
|
|
2388
2391
|
// we could in theory remove all of the leaves (the ones we know to
|
|
@@ -2416,8 +2419,15 @@ export class Calculator extends Graph {
|
|
|
2416
2419
|
let expression = '';
|
|
2417
2420
|
|
|
2418
2421
|
switch (entry.type) {
|
|
2422
|
+
|
|
2419
2423
|
case 'cell-match':
|
|
2420
|
-
|
|
2424
|
+
if (entry.between) {
|
|
2425
|
+
const addr = this.Unresolve(entry.area, context, true, false);
|
|
2426
|
+
expression = `BETWEEN(${[addr, ...entry.between].join(', ')})`;
|
|
2427
|
+
}
|
|
2428
|
+
else {
|
|
2429
|
+
expression = this.Unresolve(entry.area, context, true, false) + ' ' + entry.expression;
|
|
2430
|
+
}
|
|
2421
2431
|
break;
|
|
2422
2432
|
|
|
2423
2433
|
case 'expression':
|
|
@@ -2440,7 +2450,7 @@ export class Calculator extends Graph {
|
|
|
2440
2450
|
entry.min ?? '',
|
|
2441
2451
|
entry.max ?? '',
|
|
2442
2452
|
|
|
2443
|
-
].join(
|
|
2453
|
+
].join(',')
|
|
2444
2454
|
})`;
|
|
2445
2455
|
break;
|
|
2446
2456
|
|
|
@@ -2464,13 +2474,17 @@ export class Calculator extends Graph {
|
|
|
2464
2474
|
|
|
2465
2475
|
entry.internal.vertex = vertex;
|
|
2466
2476
|
|
|
2467
|
-
let options: EvaluateOptions
|
|
2477
|
+
let options: EvaluateOptions = {
|
|
2478
|
+
argument_separator: ',',
|
|
2479
|
+
};
|
|
2480
|
+
|
|
2468
2481
|
if (entry.type !== 'gradient' && entry.type !== 'duplicate-values') {
|
|
2469
|
-
options = entry.options;
|
|
2482
|
+
options = {...entry.options, ...options};
|
|
2470
2483
|
}
|
|
2471
2484
|
|
|
2472
2485
|
// first pass, run the calculation
|
|
2473
2486
|
const check = this.Evaluate(expression, context, options, true);
|
|
2487
|
+
|
|
2474
2488
|
entry.internal.vertex.result = check;
|
|
2475
2489
|
entry.internal.vertex.updated = true;
|
|
2476
2490
|
|
|
@@ -2478,7 +2492,7 @@ export class Calculator extends Graph {
|
|
|
2478
2492
|
|
|
2479
2493
|
const vertex = entry.internal.vertex as LeafVertex;
|
|
2480
2494
|
this.AddLeafVertex(vertex);
|
|
2481
|
-
this.UpdateLeafVertex(vertex, expression, context);
|
|
2495
|
+
this.UpdateLeafVertex(vertex, expression, context, DecimalMarkType.Period); // force en-us
|
|
2482
2496
|
|
|
2483
2497
|
}
|
|
2484
2498
|
|
|
@@ -2846,10 +2860,15 @@ export class Calculator extends Graph {
|
|
|
2846
2860
|
return dependencies;
|
|
2847
2861
|
}
|
|
2848
2862
|
|
|
2849
|
-
protected UpdateLeafVertex(vertex: LeafVertex, formula: string, context: Sheet): void {
|
|
2863
|
+
protected UpdateLeafVertex(vertex: LeafVertex, formula: string, context: Sheet, decimal_mark?: DecimalMarkType): void {
|
|
2850
2864
|
|
|
2851
2865
|
vertex.Reset();
|
|
2852
2866
|
|
|
2867
|
+
if (decimal_mark) {
|
|
2868
|
+
this.parser.Save();
|
|
2869
|
+
this.parser.SetLocaleSettings(decimal_mark);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2853
2872
|
const parse_result = this.parser.Parse(formula);
|
|
2854
2873
|
if (parse_result.expression) {
|
|
2855
2874
|
const dependencies =
|
|
@@ -2895,6 +2914,10 @@ export class Calculator extends Graph {
|
|
|
2895
2914
|
|
|
2896
2915
|
// vertex.UpdateState();
|
|
2897
2916
|
|
|
2917
|
+
if (decimal_mark) {
|
|
2918
|
+
this.parser.Restore();
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2898
2921
|
}
|
|
2899
2922
|
|
|
2900
2923
|
/**
|
|
@@ -31,7 +31,7 @@ import { ValueType, GetValueType, Area } from 'treb-base-types';
|
|
|
31
31
|
import type { Parser, ExpressionUnit, UnitBinary, UnitIdentifier,
|
|
32
32
|
UnitGroup, UnitUnary, UnitAddress, UnitRange, UnitCall, UnitDimensionedQuantity, UnitStructuredReference } from 'treb-parser';
|
|
33
33
|
import type { DataModel, MacroFunction, Sheet } from 'treb-data-model';
|
|
34
|
-
import { NameError, ReferenceError, ExpressionError, UnknownError, SpillError } from './function-error';
|
|
34
|
+
import { NameError, ReferenceError, ExpressionError, UnknownError, SpillError, ValueError } from './function-error';
|
|
35
35
|
|
|
36
36
|
import * as Primitives from './primitives';
|
|
37
37
|
|
|
@@ -633,7 +633,7 @@ export class ExpressionCalculator {
|
|
|
633
633
|
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
-
protected UnaryExpression(x: UnitUnary): (expr: UnitUnary) => UnionValue /*UnionOrArray*/ { // operator: string, operand: any){
|
|
636
|
+
protected UnaryExpression(x: UnitUnary, return_reference = false): (expr: UnitUnary) => UnionValue /*UnionOrArray*/ { // operator: string, operand: any){
|
|
637
637
|
|
|
638
638
|
// there are basically three code paths here: negate, identity, and error.
|
|
639
639
|
// they have very different semantics so we're going to do them completely
|
|
@@ -663,6 +663,110 @@ export class ExpressionCalculator {
|
|
|
663
663
|
|
|
664
664
|
}
|
|
665
665
|
|
|
666
|
+
case '@':
|
|
667
|
+
return (expr: UnitUnary) => {
|
|
668
|
+
|
|
669
|
+
// if the operand is a range, then we need to do implicit intersection.
|
|
670
|
+
// otherwise, calculate the expression and return the first value
|
|
671
|
+
// if it's an array.
|
|
672
|
+
|
|
673
|
+
let address: UnitAddress|undefined;
|
|
674
|
+
|
|
675
|
+
switch (expr.operand.type) {
|
|
676
|
+
case 'address':
|
|
677
|
+
if (expr.operand.spill) {
|
|
678
|
+
|
|
679
|
+
// we need to calculate the result so we know how large the
|
|
680
|
+
// range is... perhaps there's a way to look this up without
|
|
681
|
+
// calculating? (TODO/FIXME)
|
|
682
|
+
|
|
683
|
+
const calculated = this.CellFunction2(expr.operand)();
|
|
684
|
+
if (calculated.type === ValueType.array) {
|
|
685
|
+
|
|
686
|
+
const row = this.context.address.row ?? -1;
|
|
687
|
+
const column = this.context.address.column ?? -1;
|
|
688
|
+
|
|
689
|
+
// for this verison we already have the result, so unless
|
|
690
|
+
// we're trying to preserve the address, we could just
|
|
691
|
+
// return it
|
|
692
|
+
|
|
693
|
+
if (row >= expr.operand.row && row < expr.operand.row + calculated.value[0]?.length && calculated.value.length === 1) {
|
|
694
|
+
|
|
695
|
+
if (!return_reference) {
|
|
696
|
+
return calculated.value[0][row - expr.operand.row];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
address = {
|
|
700
|
+
...expr.operand,
|
|
701
|
+
row,
|
|
702
|
+
spill: false,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
else if (column >= expr.operand.column && column < expr.operand.column + calculated.value.length && calculated.value[0]?.length === 1) {
|
|
706
|
+
|
|
707
|
+
if (!return_reference) {
|
|
708
|
+
return calculated.value[column - expr.operand.column][0];
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
address = {
|
|
712
|
+
...expr.operand,
|
|
713
|
+
column,
|
|
714
|
+
spill: false,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
return ValueError(); // out of range
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// return { type: ValueType.string, value: 'implicit (spill)' };
|
|
724
|
+
}
|
|
725
|
+
break;
|
|
726
|
+
|
|
727
|
+
case 'range':
|
|
728
|
+
{
|
|
729
|
+
// how do we intersect, if at all?
|
|
730
|
+
|
|
731
|
+
const row = this.context.address.row ?? -1;
|
|
732
|
+
const column = this.context.address.column ?? -1;
|
|
733
|
+
if (row >= expr.operand.start.row && row <= expr.operand.end.row && expr.operand.start.column === expr.operand.end.column) {
|
|
734
|
+
address = {
|
|
735
|
+
...expr.operand.start,
|
|
736
|
+
row,
|
|
737
|
+
spill: false,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
else if (column >= expr.operand.start.column && column <= expr.operand.end.column && expr.operand.start.row === expr.operand.end.row) {
|
|
741
|
+
address = {
|
|
742
|
+
...expr.operand.start,
|
|
743
|
+
column,
|
|
744
|
+
spill: false,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
return ValueError(); // out of range
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (address) {
|
|
754
|
+
if (return_reference) {
|
|
755
|
+
return { type: ValueType.object, value: address,
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return this.CellFunction2(address)();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const operand = this.CalculateExpression(expr.operand as ExtendedExpressionUnit);
|
|
762
|
+
|
|
763
|
+
if (operand.type === ValueType.array) {
|
|
764
|
+
return operand.value[0][0];
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return operand;
|
|
768
|
+
};
|
|
769
|
+
|
|
666
770
|
default:
|
|
667
771
|
return () => {
|
|
668
772
|
console.warn('unexpected unary operator:', x.operator);
|
|
@@ -977,7 +1081,7 @@ export class ExpressionCalculator {
|
|
|
977
1081
|
return (expr.fn = this.BinaryExpression(expr))(expr); // check
|
|
978
1082
|
|
|
979
1083
|
case 'unary':
|
|
980
|
-
return (expr.fn = this.UnaryExpression(expr))(expr); // check
|
|
1084
|
+
return (expr.fn = this.UnaryExpression(expr, return_reference))(expr); // check
|
|
981
1085
|
|
|
982
1086
|
case 'identifier':
|
|
983
1087
|
return (expr.fn = this.Identifier(expr))(); // check
|
|
@@ -244,6 +244,23 @@ const ZLookup = (value: number|string|boolean|undefined, table: (number|string|b
|
|
|
244
244
|
|
|
245
245
|
};
|
|
246
246
|
|
|
247
|
+
const NumberArgument = (argument?: UnionValue, default_value: number|false = false) => {
|
|
248
|
+
|
|
249
|
+
if (!argument) {
|
|
250
|
+
return default_value;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
switch (argument.type) {
|
|
254
|
+
case ValueType.number:
|
|
255
|
+
return argument.value;
|
|
256
|
+
case ValueType.undefined:
|
|
257
|
+
return default_value;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return false;
|
|
261
|
+
|
|
262
|
+
};
|
|
263
|
+
|
|
247
264
|
/**
|
|
248
265
|
* alternate functions. these are used (atm) only for changing complex
|
|
249
266
|
* behavior.
|
|
@@ -1923,6 +1940,17 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
1923
1940
|
},
|
|
1924
1941
|
},
|
|
1925
1942
|
|
|
1943
|
+
Ceiling: {
|
|
1944
|
+
arguments: [ { unroll: true }, { unroll: true } ], // FIXME: lazy
|
|
1945
|
+
|
|
1946
|
+
fn: (a: number) => {
|
|
1947
|
+
return {
|
|
1948
|
+
type: ValueType.number,
|
|
1949
|
+
value: Math.ceil(a),
|
|
1950
|
+
};
|
|
1951
|
+
},
|
|
1952
|
+
},
|
|
1953
|
+
|
|
1926
1954
|
Round: {
|
|
1927
1955
|
arguments: [ { unroll: true }, { unroll: true } ], // FIXME: lazy
|
|
1928
1956
|
|
|
@@ -2358,6 +2386,21 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
2358
2386
|
},
|
|
2359
2387
|
},
|
|
2360
2388
|
|
|
2389
|
+
Between: {
|
|
2390
|
+
arguments: [
|
|
2391
|
+
{ name: 'target', boxed: true, unroll: true },
|
|
2392
|
+
{ name: 'min' },
|
|
2393
|
+
{ name: 'max' },
|
|
2394
|
+
],
|
|
2395
|
+
visibility: 'internal',
|
|
2396
|
+
fn: (target: UnionValue, min = 0, max = 1) => {
|
|
2397
|
+
return {
|
|
2398
|
+
type: ValueType.boolean,
|
|
2399
|
+
value: (target.type === ValueType.number) && (target.value >= min && target.value <= max),
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
},
|
|
2403
|
+
|
|
2361
2404
|
Gradient: {
|
|
2362
2405
|
arguments: [
|
|
2363
2406
|
{ name: 'range', boxed: true },
|
|
@@ -2509,7 +2552,41 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
2509
2552
|
return ArgumentError();
|
|
2510
2553
|
|
|
2511
2554
|
},
|
|
2512
|
-
}
|
|
2555
|
+
},
|
|
2556
|
+
|
|
2557
|
+
Sequence: {
|
|
2558
|
+
arguments:[
|
|
2559
|
+
{ name: 'rows', boxed: true },
|
|
2560
|
+
{ name: 'columns', default: 1, boxed: true },
|
|
2561
|
+
{ name: 'start', default: 1, boxed: true },
|
|
2562
|
+
{ name: 'step', default: 1, boxed: true }
|
|
2563
|
+
],
|
|
2564
|
+
fn: (rows: UnionValue, columns: UnionValue, start: UnionValue, step: UnionValue) => {
|
|
2565
|
+
|
|
2566
|
+
const rx = NumberArgument(rows, 1);
|
|
2567
|
+
const cx = NumberArgument(columns, 1);
|
|
2568
|
+
const step_ = NumberArgument(step, 1);
|
|
2569
|
+
const start_ = NumberArgument(start, 1);
|
|
2570
|
+
|
|
2571
|
+
if (rx === false || cx === false || step_ === false || start_ === false) {
|
|
2572
|
+
return ArgumentError();
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
const value: UnionValue[][] = [];
|
|
2576
|
+
for (let c = 0; c < cx; c++) {
|
|
2577
|
+
const col: UnionValue[] = [];
|
|
2578
|
+
for (let r = 0; r < rx; r++) {
|
|
2579
|
+
col.push({ type: ValueType.number, value: start_ + r * step_ * cx + c * step_ });
|
|
2580
|
+
}
|
|
2581
|
+
value.push(col);
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
return { type: ValueType.array, value };
|
|
2585
|
+
|
|
2586
|
+
},
|
|
2587
|
+
|
|
2588
|
+
},
|
|
2589
|
+
|
|
2513
2590
|
|
|
2514
2591
|
};
|
|
2515
2592
|
|
|
@@ -173,6 +173,16 @@ export class Sparkline {
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
if (min === max) {
|
|
177
|
+
if (min) {
|
|
178
|
+
min -= 1;
|
|
179
|
+
max += 1;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
max += 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
176
186
|
if (min !== max) {
|
|
177
187
|
|
|
178
188
|
const step = (width * (1 - 2 * x_margin)) / (values.length - 1);
|
|
@@ -251,6 +261,16 @@ export class Sparkline {
|
|
|
251
261
|
}
|
|
252
262
|
}
|
|
253
263
|
|
|
264
|
+
if (min === max) {
|
|
265
|
+
if (min) {
|
|
266
|
+
min -= 1;
|
|
267
|
+
max += 1;
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
max += 1;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
254
274
|
if (values.length) {
|
|
255
275
|
|
|
256
276
|
// const step = (width - 2 * x_margin - 2) / (values.length-1);
|
|
@@ -685,6 +685,8 @@ export const StatisticsFunctionLibrary: FunctionMap = {
|
|
|
685
685
|
export const StatisticsFunctionAliases: {[index: string]: string} = {
|
|
686
686
|
Mean: 'Average',
|
|
687
687
|
'StDev': 'StDev.S',
|
|
688
|
+
'StDevA': 'StDev.S',
|
|
689
|
+
'StDevPA': 'StDev.P',
|
|
688
690
|
'Var': 'Var.S',
|
|
689
691
|
'Quartile': 'Quartile.Inc',
|
|
690
692
|
};
|
|
@@ -139,6 +139,7 @@ export interface ConditionalFormatCellMatch extends ConditionalFormatCellMatchOp
|
|
|
139
139
|
export interface ConditionalFormatCellMatchOptions {
|
|
140
140
|
style: CellStyle;
|
|
141
141
|
expression: string;
|
|
142
|
+
between?: [number, number];
|
|
142
143
|
options?: EvaluateOptions;
|
|
143
144
|
}
|
|
144
145
|
|
|
@@ -178,8 +179,10 @@ export interface ConditionalFormatDuplicateValues extends ConditionalFormatDupli
|
|
|
178
179
|
*
|
|
179
180
|
* ...everybody has a vertex now, we could standardize it
|
|
180
181
|
*
|
|
182
|
+
* update: adding a priority field, optional
|
|
183
|
+
*
|
|
181
184
|
*/
|
|
182
|
-
export type ConditionalFormat = { internal?: unknown } & (
|
|
185
|
+
export type ConditionalFormat = { internal?: unknown, priority?: number } & (
|
|
183
186
|
ConditionalFormatDuplicateValues |
|
|
184
187
|
ConditionalFormatExpression |
|
|
185
188
|
ConditionalFormatCellMatch |
|
|
@@ -145,6 +145,8 @@ export class Sheet {
|
|
|
145
145
|
|
|
146
146
|
public name = Sheet.default_sheet_name;
|
|
147
147
|
|
|
148
|
+
public tab_color?: Color;
|
|
149
|
+
|
|
148
150
|
public background_image?: string;
|
|
149
151
|
|
|
150
152
|
protected _image: HTMLImageElement|undefined = undefined;
|
|
@@ -403,6 +405,9 @@ export class Sheet {
|
|
|
403
405
|
if (source.name) {
|
|
404
406
|
sheet.name = source.name;
|
|
405
407
|
}
|
|
408
|
+
if (source.tab_color) {
|
|
409
|
+
sheet.tab_color = source.tab_color;
|
|
410
|
+
}
|
|
406
411
|
|
|
407
412
|
if (source.background_image) {
|
|
408
413
|
sheet.background_image = source.background_image;
|
|
@@ -2744,6 +2749,7 @@ export class Sheet {
|
|
|
2744
2749
|
|
|
2745
2750
|
id: this.id,
|
|
2746
2751
|
name: this.name,
|
|
2752
|
+
tab_color: this.tab_color,
|
|
2747
2753
|
|
|
2748
2754
|
data,
|
|
2749
2755
|
sheet_style,
|
|
@@ -2915,7 +2921,11 @@ export class Sheet {
|
|
|
2915
2921
|
// this.cells.FromJSON(cell_data);
|
|
2916
2922
|
this.cells.FromJSON(data.cells);
|
|
2917
2923
|
if (data.name) {
|
|
2918
|
-
this.name = data.name || '';
|
|
2924
|
+
this.name = data.name || ''; // wtf is this?
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
if (data.tab_color) {
|
|
2928
|
+
this.tab_color = data.tab_color;
|
|
2919
2929
|
}
|
|
2920
2930
|
|
|
2921
2931
|
// 0 is implicitly just a general style
|
|
@@ -3338,9 +3348,6 @@ export class Sheet {
|
|
|
3338
3348
|
for (const format of this.conditional_formats) {
|
|
3339
3349
|
|
|
3340
3350
|
if (format.internal?.vertex?.updated) {
|
|
3341
|
-
|
|
3342
|
-
// console.info('updated');
|
|
3343
|
-
|
|
3344
3351
|
format.internal.vertex.updated = false;
|
|
3345
3352
|
}
|
|
3346
3353
|
|
|
@@ -3354,6 +3361,10 @@ export class Sheet {
|
|
|
3354
3361
|
// stop rule. if you go forwards, you need some sort of indicator
|
|
3355
3362
|
// or flag).
|
|
3356
3363
|
|
|
3364
|
+
// there's more to this, because there are rules that apply to areas,
|
|
3365
|
+
// which might stop, and there's priority. so we probably need those
|
|
3366
|
+
// flags eventually.
|
|
3367
|
+
|
|
3357
3368
|
const area = JSON.parse(JSON.stringify(format.area));
|
|
3358
3369
|
|
|
3359
3370
|
if (area.start.row === null || area.end.row === null) {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import type { IArea, SerializedCellData, CellStyle } from 'treb-base-types';
|
|
22
|
+
import type { IArea, SerializedCellData, CellStyle, Color } from 'treb-base-types';
|
|
23
23
|
import type { AnnotationData } from './annotation';
|
|
24
24
|
import type { GridSelection, SerializedGridSelection } from './sheet_selection';
|
|
25
25
|
import type { ConditionalFormatList } from './conditional_format';
|
|
@@ -126,6 +126,9 @@ export interface SerializedSheet {
|
|
|
126
126
|
/** sheet name */
|
|
127
127
|
name?: string;
|
|
128
128
|
|
|
129
|
+
/** tab color */
|
|
130
|
+
tab_color?: Color;
|
|
131
|
+
|
|
129
132
|
/** current active selection */
|
|
130
133
|
selection: SerializedGridSelection;
|
|
131
134
|
|