@trebco/treb 28.7.0 → 28.10.0

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.
Files changed (31) hide show
  1. package/dist/treb-spreadsheet-light.mjs +14 -14
  2. package/dist/treb-spreadsheet.mjs +12 -12
  3. package/dist/treb.d.ts +37 -6
  4. package/package.json +1 -1
  5. package/treb-base-types/src/gradient.ts +1 -1
  6. package/treb-base-types/src/localization.ts +6 -0
  7. package/treb-calculator/src/calculator.ts +23 -30
  8. package/treb-calculator/src/dag/calculation_leaf_vertex.ts +7 -0
  9. package/treb-calculator/src/dag/graph.ts +7 -0
  10. package/treb-calculator/src/functions/base-functions.ts +30 -1
  11. package/treb-charts/src/chart-functions.ts +11 -0
  12. package/treb-charts/src/chart-types.ts +18 -0
  13. package/treb-charts/src/chart-utils.ts +87 -0
  14. package/treb-charts/src/chart.ts +4 -0
  15. package/treb-charts/src/default-chart-renderer.ts +26 -1
  16. package/treb-charts/src/renderer.ts +81 -0
  17. package/treb-charts/style/charts.scss +8 -0
  18. package/treb-embed/markup/toolbar.html +35 -34
  19. package/treb-embed/src/custom-element/treb-global.ts +10 -2
  20. package/treb-embed/src/embedded-spreadsheet.ts +57 -101
  21. package/treb-embed/src/options.ts +7 -0
  22. package/treb-embed/style/layout.scss +4 -0
  23. package/treb-embed/style/toolbar.scss +37 -0
  24. package/treb-grid/src/types/conditional_format.ts +1 -1
  25. package/treb-grid/src/types/grid.ts +11 -1
  26. package/treb-grid/src/types/grid_base.ts +127 -5
  27. package/treb-grid/src/types/grid_command.ts +32 -0
  28. package/treb-grid/src/types/grid_events.ts +7 -0
  29. package/treb-grid/src/types/grid_options.ts +8 -0
  30. package/treb-grid/src/types/sheet.ts +0 -56
  31. package/treb-grid/src/types/update_flags.ts +1 -0
@@ -10,7 +10,7 @@ import type { EmbeddedSpreadsheet } from '../embedded-spreadsheet';
10
10
  export class TREBGlobal {
11
11
 
12
12
  /**
13
- * build version
13
+ * Package version
14
14
  *
15
15
  * @privateRemarks
16
16
  *
@@ -25,7 +25,15 @@ export class TREBGlobal {
25
25
  public version = process.env.BUILD_VERSION || '';
26
26
 
27
27
  /**
28
- * create a spreadsheet instance
28
+ * Create a spreadsheet. The `USER_DATA_TYPE` template parameter is the type
29
+ * assigned to the `user_data` field of the spreadsheet instance -- it can
30
+ * help simplify typing if you are storing extra data in spreadsheet
31
+ * files.
32
+ *
33
+ * Just ignore this parameter if you don't need it.
34
+ *
35
+ * @typeParam USER_DATA_TYPE - type for the `user_data` field in the
36
+ * spreadsheet instance
29
37
  */
30
38
  public CreateSpreadsheet<USER_DATA_TYPE = unknown>(options: EmbeddedSpreadsheetOptions): EmbeddedSpreadsheet<USER_DATA_TYPE> {
31
39
  const container = options.container;
@@ -69,7 +69,7 @@ import {
69
69
  } from 'treb-base-types';
70
70
 
71
71
  import { EventSource, ValidateURI } from 'treb-utils';
72
- import { NumberFormatCache, ValueParser, NumberFormat } from 'treb-format';
72
+ import { NumberFormatCache, ValueParser, NumberFormat, LotusDate, UnlotusDate } from 'treb-format';
73
73
 
74
74
 
75
75
 
@@ -486,13 +486,19 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
486
486
  this.DocumentChange();
487
487
  }
488
488
 
489
- /** opaque user data (metadata) */
489
+ /**
490
+ * opaque user data (metadata). `USER_DATA_TYPE` is a template
491
+ * parameter you can set when creating the spreadsheet.
492
+ */
490
493
  public get user_data(): USER_DATA_TYPE|undefined {
491
494
  return this.grid.model.user_data;
492
495
  }
493
496
 
494
- /** opaque user data (metadata) */
495
- public set user_data(data: USER_DATA_TYPE) {
497
+ /**
498
+ * opaque user data (metadata). `USER_DATA_TYPE` is a template
499
+ * parameter you can set when creating the spreadsheet.
500
+ */
501
+ public set user_data(data: USER_DATA_TYPE|undefined) {
496
502
  this.grid.model.user_data = data;
497
503
  this.DocumentChange();
498
504
  }
@@ -883,6 +889,8 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
883
889
 
884
890
  this.grid.grid_events.Subscribe((event) => {
885
891
 
892
+ // console.info({event});
893
+
886
894
  switch (event.type) {
887
895
 
888
896
  case 'error':
@@ -986,7 +994,14 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
986
994
 
987
995
  case 'structure':
988
996
  {
997
+ // console.info("S event", event);
998
+
989
999
  const cached_selection = this.last_selection;
1000
+ if (event.conditional_format) {
1001
+ this.calculator.UpdateConditionals();
1002
+ this.ApplyConditionalFormats(this.grid.active_sheet, false);
1003
+ }
1004
+
990
1005
  if (event.rebuild_required) {
991
1006
  this.calculator.Reset();
992
1007
 
@@ -1412,12 +1427,6 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1412
1427
  ...options,
1413
1428
  };
1414
1429
 
1415
- // we need to calculate the formula once, to get an initial state
1416
- // update: internal
1417
- // let result = this.Evaluate(this.Unresolve(area, true, false) + ' ' + options.expression, options.options);
1418
-
1419
- // ... apply ...
1420
-
1421
1430
  this.AddConditionalFormat(format);
1422
1431
  return format;
1423
1432
 
@@ -1445,18 +1454,6 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1445
1454
  ...options,
1446
1455
  };
1447
1456
 
1448
- /*
1449
- // we need to calculate the formula once, to get an initial state
1450
- let result = this.Evaluate(options.expression, options.options);
1451
-
1452
- if (Array.isArray(result)) {
1453
- result = result[0][0];
1454
- }
1455
- const applied = !!result;
1456
-
1457
- this.AddConditionalFormat({...format, applied });
1458
- */
1459
-
1460
1457
  this.AddConditionalFormat(format);
1461
1458
 
1462
1459
  return format;
@@ -1470,20 +1467,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1470
1467
  */
1471
1468
  public AddConditionalFormat(format: ConditionalFormat) {
1472
1469
 
1473
- const sheet = this.model.sheets.Find(format.area.start.sheet_id||0);
1474
-
1475
- if (!sheet) {
1476
- throw new Error('invalid reference in format');
1477
- }
1478
-
1479
- sheet.conditional_formats.push(format);
1480
-
1481
- this.calculator.UpdateConditionals(format, sheet);
1482
-
1483
- // call update if it's the current sheet
1484
- this.ApplyConditionalFormats(sheet, sheet === this.grid.active_sheet);
1485
-
1486
- this.PushUndo();
1470
+ this.grid.AddConditionalFormat(format);
1487
1471
 
1488
1472
  // fluent
1489
1473
  return format;
@@ -1496,40 +1480,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1496
1480
  * @internal
1497
1481
  */
1498
1482
  public RemoveConditionalFormat(format: ConditionalFormat) {
1499
- const area = format.area;
1500
- const sheet = area.start.sheet_id ? this.model.sheets.Find(area.start.sheet_id) : this.grid.active_sheet;
1501
-
1502
- if (!sheet) {
1503
- throw new Error('invalid reference in format');
1504
- }
1505
-
1506
- let count = 0;
1507
- sheet.conditional_formats = sheet.conditional_formats.filter(test => {
1508
- if (test === format) {
1509
- count++;
1510
- this.calculator.RemoveConditional(test);
1511
- return false;
1512
- }
1513
- return true;
1514
- });
1515
-
1516
- if (count) {
1517
- sheet.FlushConditionalFormats();
1518
- }
1519
-
1520
- // we want to call update if it's the current sheet,
1521
- // but we want a full repaint
1522
-
1523
- this.ApplyConditionalFormats(sheet, false);
1524
-
1525
- if (sheet === this.grid.active_sheet) {
1526
- this.grid.Update(true);
1527
- }
1528
-
1529
- if (count) {
1530
- this.PushUndo();
1531
- }
1532
-
1483
+ this.grid.RemoveConditionalFormat({ format });
1533
1484
  }
1534
1485
 
1535
1486
  /**
@@ -1548,37 +1499,9 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1548
1499
  }
1549
1500
  range = ref.area;
1550
1501
  }
1551
- const area = this.model.ResolveArea(range, this.grid.active_sheet);
1552
- const sheet = area.start.sheet_id ? this.model.sheets.Find(area.start.sheet_id) : this.grid.active_sheet;
1553
-
1554
- if (sheet) {
1555
- let count = 0;
1556
-
1557
- sheet.conditional_formats = sheet.conditional_formats.filter(test => {
1558
- const compare = new Area(test.area.start, test.area.end);
1559
- if (compare.Intersects(area)) {
1560
- count++;
1561
- this.calculator.RemoveConditional(test);
1562
- return false;
1563
- }
1564
- return true;
1565
- });
1566
-
1567
- if (count) {
1568
-
1569
- sheet.FlushConditionalFormats();
1570
-
1571
- this.ApplyConditionalFormats(sheet, false);
1572
-
1573
- if (sheet === this.grid.active_sheet) {
1574
- this.grid.Update(true);
1575
- }
1576
-
1577
- this.PushUndo();
1578
- }
1579
-
1580
- }
1581
1502
 
1503
+ const area = this.model.ResolveArea(range, this.grid.active_sheet);
1504
+ this.grid.RemoveConditionalFormat({ area });
1582
1505
 
1583
1506
  }
1584
1507
 
@@ -3977,6 +3900,23 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
3977
3900
  return NumberFormatCache.Get(format).Format(value);
3978
3901
  }
3979
3902
 
3903
+ /**
3904
+ * convert a javascript date (or timestamp) to a spreadsheet date
3905
+ */
3906
+ public SpreadsheetDate(javascript_date: number|Date) {
3907
+ if (javascript_date instanceof Date) {
3908
+ javascript_date = javascript_date.getTime();
3909
+ }
3910
+ return UnlotusDate(javascript_date, true);
3911
+ }
3912
+
3913
+ /**
3914
+ * convert a spreadsheet date to a javascript date
3915
+ */
3916
+ public JavascriptDate(spreadsheet_date: number) {
3917
+ return LotusDate(spreadsheet_date).getTime();
3918
+ }
3919
+
3980
3920
  /**
3981
3921
  * Apply borders to range.
3982
3922
  *
@@ -4611,7 +4551,13 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
4611
4551
 
4612
4552
  this.UpdateAnnotations();
4613
4553
 
4554
+ // we don't really need to call UpdateConditionals here unless
4555
+ // something has changed in a previously inactive sheet -- right?
4556
+
4614
4557
  this.calculator.UpdateConditionals();
4558
+
4559
+ // we do need to call apply (I think)
4560
+
4615
4561
  this.ApplyConditionalFormats(event.activate, true);
4616
4562
 
4617
4563
  }
@@ -5798,15 +5744,25 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
5798
5744
 
5799
5745
  /**
5800
5746
  * handle key down to intercept ctrl+z (undo)
5801
- *
5747
+ * UPDATE: we're also handling F9 for recalc (optionally)
5748
+ *
5802
5749
  * FIXME: redo (ctrl+y or ctrl+shift+z)
5803
5750
  */
5804
5751
  protected HandleKeyDown(event: KeyboardEvent): void {
5752
+
5753
+ // can we drop the event.code stuff in 2024? (YES)
5754
+
5805
5755
  if (event.ctrlKey && (event.code === 'KeyZ' || event.key === 'z')) {
5806
5756
  event.stopPropagation();
5807
5757
  event.preventDefault();
5808
5758
  this.Undo();
5809
5759
  }
5760
+ else if (event.key === 'F9' && this.options.recalculate_on_f9) {
5761
+ event.stopPropagation();
5762
+ event.preventDefault();
5763
+ this.Recalculate();
5764
+ }
5765
+
5810
5766
  }
5811
5767
 
5812
5768
 
@@ -304,6 +304,13 @@ export interface EmbeddedSpreadsheetOptions {
304
304
  */
305
305
  preload?: (instance: unknown) => void;
306
306
 
307
+ /**
308
+ * handle the F9 key and recalculate the spreadsheet. for compatibility.
309
+ * we're leaving this option to default `false` for now, but that may
310
+ * change in the future. key modifiers have no effect.
311
+ */
312
+ recalculate_on_f9?: boolean;
313
+
307
314
  }
308
315
 
309
316
  /**
@@ -64,6 +64,10 @@
64
64
 
65
65
  font-family: var(--treb-default-font, system-ui, $font-stack);
66
66
 
67
+ // reset in case we inherit something
68
+
69
+ line-height: normal;
70
+
67
71
  div, button, input, ul, ol, li, a, textarea, svg {
68
72
 
69
73
  // maybe this is being too aggressive. we could be a little
@@ -411,6 +411,43 @@ $swatch-size: 18px;
411
411
 
412
412
  }
413
413
 
414
+ .treb-font-scale {
415
+ padding-left: 2em;
416
+ width: 5em;
417
+ text-align: right;
418
+ }
419
+
420
+ [composite][font-scale] {
421
+ position: relative;
422
+ }
423
+
424
+ .treb-font-scale-icon {
425
+ position: absolute;
426
+ top: 50%;
427
+ left: .5em;
428
+ transform: translateY(-50%);
429
+ opacity: .9;
430
+ // border: 1px solid color-mix(in srgb, currentColor 25%, transparent);
431
+ border-radius: 3px;
432
+ // background: color-mix(in srgb, currentColor 10%, transparent);
433
+
434
+ pointer-events: none;
435
+ line-height: 1;
436
+
437
+ &::before, &::after {
438
+ content: 'A';
439
+ position: relative;
440
+ }
441
+
442
+ &::before {
443
+ font-size: 1.2em;
444
+ }
445
+ &::after {
446
+ font-size: .9em;
447
+ left: -.125em;
448
+ }
449
+ }
450
+
414
451
  }
415
452
 
416
453
  }
@@ -33,7 +33,7 @@ export interface ConditionalFormatGradientOptions {
33
33
  /** property defaults to fill */
34
34
  property?: 'fill'|'text';
35
35
 
36
- /** defaults to HSL */
36
+ /** defaults to RGB */
37
37
  color_space?: 'HSL'|'RGB';
38
38
 
39
39
  /** gradient stops, required */
@@ -4627,6 +4627,13 @@ export class Grid extends GridBase {
4627
4627
  }
4628
4628
  }
4629
4629
  else {
4630
+
4631
+ // ignore function keys
4632
+
4633
+ if (/^F\d+$/.test(event.key)) {
4634
+ return;
4635
+ }
4636
+
4630
4637
  switch (event.key) {
4631
4638
  case 'Tab':
4632
4639
  if (event.shiftKey) delta.columns--;
@@ -4687,7 +4694,10 @@ export class Grid extends GridBase {
4687
4694
  return;
4688
4695
 
4689
4696
  default:
4690
- // console.info('ek', event.key);
4697
+
4698
+ // FIXME: we're handling F9 (optionally) in the embedded
4699
+ // component. this handler should ignore all function keys.
4700
+ // not sure there's a good global for that, though. regex?
4691
4701
 
4692
4702
  if (!selection.empty) {
4693
4703
  if (event.key !== 'Escape') {
@@ -67,6 +67,10 @@ interface PatchOptions extends PatchAreaOptions {
67
67
  sheet: Sheet;
68
68
  }
69
69
 
70
+ const AssertNever = (value: never) => {
71
+ console.error('invalid case');
72
+ };
73
+
70
74
  export class GridBase {
71
75
 
72
76
  // --- public members --------------------------------------------------------
@@ -160,6 +164,24 @@ export class GridBase {
160
164
 
161
165
  // --- API methods -----------------------------------------------------------
162
166
 
167
+ public RemoveConditionalFormat(options: {
168
+ format?: ConditionalFormat;
169
+ area?: IArea;
170
+ }) {
171
+
172
+ this.ExecCommand({
173
+ key: CommandKey.RemoveConditionalFormat,
174
+ ...options,
175
+ });
176
+ }
177
+
178
+ public AddConditionalFormat(format: ConditionalFormat) {
179
+ this.ExecCommand({
180
+ key: CommandKey.AddConditionalFormat,
181
+ format,
182
+ });
183
+ }
184
+
163
185
  /** remove a table. doesn't remove any data, just removes the overlay. */
164
186
  public RemoveTable(table: Table) {
165
187
  this.ExecCommand({
@@ -1887,7 +1909,7 @@ export class GridBase {
1887
1909
  break;
1888
1910
  */
1889
1911
 
1890
- // special case
1912
+ // special cases
1891
1913
  case CommandKey.SortTable:
1892
1914
  case CommandKey.RemoveTable:
1893
1915
  if (!command.table.area.start.sheet_id) {
@@ -1895,6 +1917,12 @@ export class GridBase {
1895
1917
  }
1896
1918
  break;
1897
1919
 
1920
+ case CommandKey.AddConditionalFormat:
1921
+ if (!command.format.area.start.sheet_id) {
1922
+ command.format.area.start.sheet_id = id;
1923
+ }
1924
+ break;
1925
+
1898
1926
  // field
1899
1927
  case CommandKey.ResizeRows:
1900
1928
  case CommandKey.ResizeColumns:
@@ -1920,6 +1948,14 @@ export class GridBase {
1920
1948
  case CommandKey.Select:
1921
1949
  case CommandKey.InsertTable:
1922
1950
 
1951
+ // note that remove conditional format could have a format
1952
+ // object (with an area) instead of an area. but in that case,
1953
+ // the format object must match an existing format, so it would
1954
+ // have to have a proper area. there's no case where we would
1955
+ // want to add it. so we only handle the area case.
1956
+
1957
+ case CommandKey.RemoveConditionalFormat:
1958
+
1923
1959
  if (command.area) {
1924
1960
  if (IsCellAddress(command.area)) {
1925
1961
  if (!command.area.sheet_id) {
@@ -1934,10 +1970,13 @@ export class GridBase {
1934
1970
  }
1935
1971
  break;
1936
1972
 
1937
- // default:
1938
- // // command key here should be `never` if we've covered all the
1939
- // // cases (ts will complain)
1940
- // // console.warn('unhandled command key', command.key);
1973
+ default:
1974
+
1975
+ // this is intended to check that we've handled all cases. if you
1976
+ // add additional commands and they're not handled, this should
1977
+ // raise a ts error.
1978
+
1979
+ AssertNever(command);
1941
1980
 
1942
1981
  }
1943
1982
  }
@@ -3534,6 +3573,88 @@ export class GridBase {
3534
3573
 
3535
3574
  break;
3536
3575
 
3576
+ case CommandKey.AddConditionalFormat:
3577
+ {
3578
+
3579
+ const sheet = this.FindSheet(command.format.area);
3580
+ sheet.conditional_formats.push(command.format);
3581
+
3582
+ sheet.Invalidate(new Area(command.format.area.start, command.format.area.end));
3583
+
3584
+ if (sheet === this.active_sheet) {
3585
+ // flags.style_area = Area.Join(command.format.area, flags.style_area);
3586
+ flags.render_area = Area.Join(command.format.area, flags.render_area);
3587
+ }
3588
+ else {
3589
+ // flags.style_event = true;
3590
+ }
3591
+
3592
+ flags.structure_event = true;
3593
+ flags.conditional_formatting_event = true;
3594
+
3595
+ }
3596
+ break;
3597
+
3598
+ case CommandKey.RemoveConditionalFormat:
3599
+
3600
+ {
3601
+ let sheet: Sheet|undefined;
3602
+ let count = 0;
3603
+
3604
+ if (command.format) {
3605
+
3606
+ // we're removing by object equivalence, not strict equality.
3607
+ // this is in case we're switching contexts and the objects
3608
+ // are not strictly equal. may be unecessary. do we need to
3609
+ // normalize in some way? (...)
3610
+
3611
+ const format = JSON.stringify(command.format);
3612
+
3613
+ sheet = this.FindSheet(command.format.area);
3614
+ sheet.conditional_formats = sheet.conditional_formats.filter(test => {
3615
+ // if (test === command.format) {
3616
+ if (JSON.stringify(test) === format) {
3617
+ count++;
3618
+ flags.render_area = Area.Join(test.area, flags.render_area);
3619
+ return false;
3620
+ }
3621
+ return true;
3622
+ });
3623
+
3624
+ }
3625
+ else if (command.area) {
3626
+ const area = new Area(command.area.start, command.area.end);
3627
+ sheet = this.FindSheet(command.area);
3628
+
3629
+ sheet.conditional_formats = sheet.conditional_formats.filter(test => {
3630
+ const compare = new Area(test.area.start, test.area.end);
3631
+ if (compare.Intersects(area)) {
3632
+ count++;
3633
+ flags.render_area = Area.Join(compare, flags.render_area);
3634
+ return false;
3635
+ }
3636
+ return true;
3637
+ });
3638
+
3639
+ }
3640
+
3641
+ if (sheet && count) {
3642
+ sheet.FlushConditionalFormats();
3643
+ flags.structure_event = true;
3644
+
3645
+ // this will flush leaf vertices. but it's expensive because
3646
+ // it's rebuilding the whole graph. we could maybe reduce a
3647
+ // bit... the question is what's worse: rebuilding the graph
3648
+ // or leaving orphans for a while?
3649
+
3650
+ // flags.structure_rebuild_required = true;
3651
+
3652
+ flags.conditional_formatting_event = true;
3653
+ }
3654
+
3655
+ }
3656
+ break;
3657
+
3537
3658
  case CommandKey.InsertTable:
3538
3659
 
3539
3660
  // the most important thing here is validating that we can
@@ -4170,6 +4291,7 @@ export class GridBase {
4170
4291
  events.push({
4171
4292
  type: 'structure',
4172
4293
  rebuild_required: flags.structure_rebuild_required,
4294
+ conditional_format: flags.conditional_formatting_event,
4173
4295
  });
4174
4296
  }
4175
4297
 
@@ -22,6 +22,7 @@
22
22
  import type { ICellAddress, IArea, Style, CellStyle, Color, CellValue, Table, TableSortType, TableTheme } from 'treb-base-types';
23
23
  import type { ExpressionUnit } from 'treb-parser';
24
24
  import type { BorderConstants } from './border_constants';
25
+ import type { ConditionalFormat } from './conditional_format';
25
26
 
26
27
  /**
27
28
  * switching to an exec-command based model, so we can serialize
@@ -73,6 +74,9 @@ export enum CommandKey {
73
74
  InsertTable,
74
75
  RemoveTable,
75
76
 
77
+ AddConditionalFormat,
78
+ RemoveConditionalFormat,
79
+
76
80
  }
77
81
 
78
82
  /** base type for sheet commands -- can select sheet by name, id or index */
@@ -418,6 +422,32 @@ export interface ReorderSheetCommand {
418
422
  move_before: number;
419
423
  }
420
424
 
425
+ /**
426
+ * add conditional format
427
+ */
428
+ export interface AddConditionalFormatCommand {
429
+ key: CommandKey.AddConditionalFormat;
430
+ format: ConditionalFormat;
431
+ }
432
+
433
+ /**
434
+ * remove conditional format, either as an object or from a target
435
+ * area. as an object, we'll match using object equivalence and not
436
+ * identity.
437
+ */
438
+ export interface RemoveConditionalFormatCommand {
439
+
440
+ key: CommandKey.RemoveConditionalFormat;
441
+
442
+ /** if format is omitted, we will remove all formats from the target range */
443
+ format?: ConditionalFormat;
444
+
445
+ /** one of area or format should be supplied */
446
+ area?: IArea;
447
+
448
+ }
449
+
450
+
421
451
  /**
422
452
  * ephemeral flag added to commands.
423
453
  * /
@@ -459,6 +489,8 @@ export type Command =
459
489
  | ActivateSheetCommand
460
490
  | DataValidationCommand
461
491
  | DuplicateSheetCommand
492
+ | AddConditionalFormatCommand
493
+ | RemoveConditionalFormatCommand
462
494
  ) ; // & Ephemeral;
463
495
 
464
496
  /**
@@ -23,6 +23,7 @@ import type { GridSelection } from './grid_selection';
23
23
  import type { Annotation } from './annotation';
24
24
  import type { Sheet } from './sheet';
25
25
  import type { Area } from 'treb-base-types';
26
+ import type { ConditionalFormat } from './conditional_format';
26
27
 
27
28
  export enum ErrorCode {
28
29
 
@@ -80,6 +81,12 @@ export interface StructureEvent {
80
81
  * FIXME: merge/unmerge? (...) I think yes
81
82
  */
82
83
  rebuild_required?: boolean;
84
+
85
+ /**
86
+ * we can use this when conditional formats are modified/added/removed
87
+ */
88
+ conditional_format?: boolean;
89
+
83
90
  }
84
91
 
85
92
  export interface AnnotationEvent {
@@ -24,6 +24,14 @@ import type { StatsEntry } from './tab_bar';
24
24
 
25
25
  export type StatsFunction = (data: CellValue|CellValue[][]|undefined) => StatsEntry[];
26
26
 
27
+
28
+ /**
29
+ * @internalRemarks
30
+ *
31
+ * why are there two levels of options? can we consolidate this with
32
+ * the embedded spreadsheet options? they are never (AFAICT) used
33
+ * independently. maybe that's recent.
34
+ */
27
35
  export interface GridOptions {
28
36
 
29
37
  /** can expand rows/columns */