@trebco/treb 30.6.3 → 30.8.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.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v30.6. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v30.8. 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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "30.6.3",
3
+ "version": "30.8.2",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -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
- expression = this.Unresolve(entry.area, context, true, false) + ' ' + entry.expression;
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(this.parser.argument_separator)
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|undefined;
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 |
@@ -3338,9 +3338,6 @@ export class Sheet {
3338
3338
  for (const format of this.conditional_formats) {
3339
3339
 
3340
3340
  if (format.internal?.vertex?.updated) {
3341
-
3342
- // console.info('updated');
3343
-
3344
3341
  format.internal.vertex.updated = false;
3345
3342
  }
3346
3343
 
@@ -3354,6 +3351,10 @@ export class Sheet {
3354
3351
  // stop rule. if you go forwards, you need some sort of indicator
3355
3352
  // or flag).
3356
3353
 
3354
+ // there's more to this, because there are rules that apply to areas,
3355
+ // which might stop, and there's priority. so we probably need those
3356
+ // flags eventually.
3357
+
3357
3358
  const area = JSON.parse(JSON.stringify(format.area));
3358
3359
 
3359
3360
  if (area.start.row === null || area.end.row === null) {
@@ -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];
@@ -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 { ImportedSheetData, AnchoredAnnotation, CellParseResult, AnnotationLayout, Corner as LayoutCorner, IArea, GradientStop, Color, HTMLColor, ThemeColor } from 'treb-base-types';
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';
@@ -341,7 +341,7 @@ export class Importer {
341
341
 
342
342
  }
343
343
 
344
- public ParseConditionalFormat(address: RangeType|AddressType, rule: any): ConditionalFormat|undefined {
344
+ public ParseConditionalFormat(address: RangeType|AddressType, rule: any): ConditionalFormat|ConditionalFormat[]|undefined {
345
345
 
346
346
  const area = this.AddressToArea(address);
347
347
  const operators = ConditionalFormatOperators;
@@ -365,11 +365,13 @@ export class Importer {
365
365
  area,
366
366
  style,
367
367
  unique: (rule.a$.type === 'uniqueValues'),
368
+ priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
369
+
368
370
  };
369
371
  }
370
372
 
371
373
  case 'cellIs':
372
- if (rule.a$.operator && rule.formula) {
374
+ if (rule.a$.operator && (rule.formula || typeof rule.formula === 'number')) {
373
375
  let style = {};
374
376
 
375
377
  if (rule.a$.dxfId) {
@@ -379,10 +381,26 @@ export class Importer {
379
381
  }
380
382
  }
381
383
 
384
+ if (rule.a$.operator === 'between') {
385
+ if (Array.isArray(rule.formula) && rule.formula.length === 2
386
+ && typeof rule.formula[0] === 'number' && typeof rule.formula[1] === 'number') {
387
+
388
+ return {
389
+ type: 'cell-match',
390
+ expression: '',
391
+ between: rule.formula, // special case? ugh
392
+ area,
393
+ style,
394
+ priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
395
+ };
396
+
397
+ }
398
+ }
399
+
382
400
  const operator = operators[rule.a$.operator || ''];
383
401
 
384
402
  if (!operator) {
385
- console.info('unhandled cellIs operator:', rule.a$.operator);
403
+ console.info('unhandled cellIs operator:', rule.a$.operator, {rule});
386
404
  }
387
405
  else {
388
406
  return {
@@ -390,10 +408,14 @@ export class Importer {
390
408
  expression: operator + ' ' + rule.formula,
391
409
  area,
392
410
  style,
411
+ priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
393
412
  };
394
413
  }
395
414
 
396
415
  }
416
+ else {
417
+ console.info("miss?", rule);
418
+ }
397
419
  break;
398
420
 
399
421
  case 'containsErrors':
@@ -427,11 +449,51 @@ export class Importer {
427
449
  }
428
450
  }
429
451
 
452
+ if (rule.a$.type === 'expression' && (area.start.row !== area.end.row || area.start.column !== area.end.column)) {
453
+
454
+ // (1) this is only required if there are relative references
455
+ // in the formula. so we could check and short-circuit.
456
+ //
457
+ // (2) I'd like to find a way to apply this as a single formula,
458
+ // so there's only one rule required.
459
+
460
+ this.parser.Save();
461
+ this.parser.SetLocaleSettings(DecimalMarkType.Period);
462
+
463
+ const list: ConditionalFormat[] = [];
464
+ const a2 = new Area(area.start, area.end);
465
+
466
+ const parse_result = this.parser.Parse(rule.formula);
467
+ if (parse_result.expression) {
468
+ for (const cell of a2) {
469
+ const f = this.parser.Render(parse_result.expression, {
470
+ missing: '',
471
+ offset: { rows: cell.row - area.start.row, columns: cell.column - area.start.column }
472
+ });
473
+
474
+ list.push({
475
+ type: 'expression',
476
+ expression: f,
477
+ style,
478
+ area: { start: cell, end: cell },
479
+ priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
480
+ })
481
+
482
+ // console.info(f);
483
+ }
484
+ }
485
+
486
+ this.parser.Restore();
487
+ return list;
488
+
489
+ }
490
+
430
491
  return {
431
492
  type: 'expression',
432
493
  expression: rule.formula,
433
494
  area,
434
495
  style,
496
+ priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
435
497
  };
436
498
 
437
499
  }
@@ -479,6 +541,8 @@ export class Importer {
479
541
  stops,
480
542
  color_space: 'RGB',
481
543
  area,
544
+ priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
545
+
482
546
  };
483
547
 
484
548
  }
@@ -545,7 +609,12 @@ export class Importer {
545
609
  for (const rule of rules) {
546
610
  const format = this.ParseConditionalFormat(area, rule);
547
611
  if (format) {
548
- conditional_formats.push(format);
612
+ if (Array.isArray(format)) {
613
+ conditional_formats.push(...format);
614
+ }
615
+ else {
616
+ conditional_formats.push(format);
617
+ }
549
618
  }
550
619
  }
551
620
  }
@@ -39,10 +39,15 @@ 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: '>',
47
+ greaterThanOrEqual: '>=',
44
48
  greaterThanOrEquals: '>=',
45
49
  lessThan: '<',
50
+ lessThanOrEqual: '<=',
46
51
  lessThanOrEquals: '<=',
47
52
  equal: '=',
48
53
  notEqual: '<>',
@@ -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 = ('$^&*(-+={[<>/~%' + Localization.argument_separator).split(''); // FIXME: i18n
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