@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.
@@ -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 { 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';
@@ -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
- const parse_result = this.parser.Parse(formula); // l10n?
170
- if (parse_result.expression) {
171
- this.parser.Walk(parse_result.expression, (unit) => {
172
- if (unit.type === 'call') {
173
- if (/^_xll\./.test(unit.name)) {
174
- unit.name = unit.name.substring(5);
175
- }
176
- else if (/^_xlfn\./.test(unit.name)) {
177
- unit.name = unit.name.substring(6);
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
- return true;
181
- });
182
- value = '=' + this.parser.Render(parse_result.expression, { missing: '' });
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
- conditional_formats.push(format);
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||''})` || '').join(', ')})`;
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||''})` || '').join(', ')})`;
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||''})` || '').join(', ')})`;
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 './import2';
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
- greaterThanOrEquals: '>=',
47
+ greaterThanOrEqual: '>=',
48
+ // greaterThanOrEquals: '>=',
45
49
  lessThan: '<',
46
- lessThanOrEquals: '<=',
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 = ('$^&*(-+={[<>/~%' + 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
@@ -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 unary_operators: PrecedenceList = { '-': 100, '+': 100 };
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 binary_operators) {
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;