@trebco/treb 27.5.3 → 27.9.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 (48) hide show
  1. package/dist/treb-spreadsheet.mjs +14 -14
  2. package/dist/treb.d.ts +37 -23
  3. package/notes/conditional-fomratring.md +29 -0
  4. package/package.json +3 -3
  5. package/treb-base-types/src/area.ts +181 -0
  6. package/treb-base-types/src/evaluate-options.ts +21 -0
  7. package/treb-base-types/src/gradient.ts +97 -0
  8. package/treb-base-types/src/import.ts +2 -1
  9. package/treb-base-types/src/index.ts +2 -0
  10. package/treb-calculator/src/calculator.ts +205 -132
  11. package/treb-calculator/src/dag/calculation_leaf_vertex.ts +97 -0
  12. package/treb-calculator/src/dag/graph.ts +10 -22
  13. package/treb-calculator/src/dag/{leaf_vertex.ts → state_leaf_vertex.ts} +3 -3
  14. package/treb-calculator/src/descriptors.ts +10 -3
  15. package/treb-calculator/src/expression-calculator.ts +1 -1
  16. package/treb-calculator/src/function-library.ts +25 -22
  17. package/treb-calculator/src/functions/base-functions.ts +166 -5
  18. package/treb-calculator/src/index.ts +6 -6
  19. package/treb-calculator/src/notifier-types.ts +1 -1
  20. package/treb-calculator/src/utilities.ts +2 -2
  21. package/treb-charts/src/util.ts +2 -2
  22. package/treb-embed/src/embedded-spreadsheet.ts +382 -71
  23. package/treb-embed/style/formula-bar.scss +2 -0
  24. package/treb-embed/style/theme-defaults.scss +46 -15
  25. package/treb-export/src/export-worker/export-worker.ts +0 -13
  26. package/treb-export/src/export2.ts +187 -2
  27. package/treb-export/src/import2.ts +169 -4
  28. package/treb-export/src/workbook-style2.ts +56 -8
  29. package/treb-export/src/workbook2.ts +10 -1
  30. package/treb-grid/src/editors/editor.ts +1276 -0
  31. package/treb-grid/src/editors/external_editor.ts +113 -0
  32. package/treb-grid/src/editors/formula_bar.ts +450 -474
  33. package/treb-grid/src/editors/overlay_editor.ts +437 -512
  34. package/treb-grid/src/index.ts +2 -1
  35. package/treb-grid/src/layout/base_layout.ts +24 -16
  36. package/treb-grid/src/render/tile_renderer.ts +2 -1
  37. package/treb-grid/src/types/conditional_format.ts +168 -0
  38. package/treb-grid/src/types/data_model.ts +130 -3
  39. package/treb-grid/src/types/external_editor_config.ts +47 -0
  40. package/treb-grid/src/types/grid.ts +96 -45
  41. package/treb-grid/src/types/grid_base.ts +187 -35
  42. package/treb-grid/src/types/scale-control.ts +1 -1
  43. package/treb-grid/src/types/sheet.ts +330 -26
  44. package/treb-grid/src/types/sheet_types.ts +4 -0
  45. package/treb-grid/src/util/dom_utilities.ts +58 -25
  46. package/treb-grid/src/editors/formula_editor_base.ts +0 -912
  47. package/treb-grid/src/types/external_editor.ts +0 -27
  48. /package/{README-shadow-DOM.md → notes/shadow-DOM.md} +0 -0
@@ -80,13 +80,15 @@ import { TileRange } from '../layout/base_layout';
80
80
  import { GridLayout } from '../layout/grid_layout';
81
81
 
82
82
  import type { GridSelection } from './grid_selection';
83
- import { OverlayEditor, OverlayEditorResult } from '../editors/overlay_editor';
83
+ import { OverlayEditor } from '../editors/overlay_editor';
84
84
 
85
85
  import { TileRenderer } from '../render/tile_renderer';
86
86
  import type { GridEvent } from './grid_events';
87
87
  import { ErrorCode } from './grid_events';
88
88
  import type { LegacySerializedSheet } from './sheet_types';
89
+ // import { FormulaBar } from '../editors/formula_bar';
89
90
  import { FormulaBar } from '../editors/formula_bar';
91
+
90
92
  import type { GridOptions } from './grid_options';
91
93
  import { BorderConstants } from './border_constants';
92
94
  import type { SerializeOptions } from './serialize_options';
@@ -114,7 +116,8 @@ import { GridBase } from './grid_base';
114
116
  import type { SetRangeOptions } from './set_range_options';
115
117
  import type { ClipboardCellData } from './clipboard_data';
116
118
 
117
- import type { ExternalEditorType } from './external_editor';
119
+ import type { ExternalEditorConfig } from './external_editor_config';
120
+ import { ExternalEditor } from '../editors/external_editor';
118
121
 
119
122
  interface DoubleClickData {
120
123
  timeout?: number;
@@ -148,7 +151,7 @@ export class Grid extends GridBase {
148
151
  this.UpdateAnnotationLayout();
149
152
  this.layout.UpdateAnnotation(this.active_sheet.annotations);
150
153
  this.layout.ApplyTheme(this.theme);
151
- this.overlay_editor?.UpdateTheme(value);
154
+ this.overlay_editor?.UpdateScale(value);
152
155
  this.tab_bar?.UpdateScale(value);
153
156
 
154
157
  this.grid_events.Publish({
@@ -264,14 +267,18 @@ export class Grid extends GridBase {
264
267
 
265
268
  private RESIZE_PIXEL_BUFFER = 5;
266
269
 
267
- private select_argument = false; // temp, WIP
270
+ /**
271
+ * formalizing the concept of external selection to support outside tooling.
272
+ *
273
+ * FIXME: stop testing on this field. we need a better way to figure out
274
+ * if the external editor is active.
275
+ */
276
+ private external_editor_config?: Partial<ExternalEditorConfig>;
268
277
 
269
278
  /**
270
- * not sure what select_argument was for (possibly outside client), but
271
- * we're formalizing the concept of external selection to support outside
272
- * tooling.
279
+ * support for external editor. created on demand.
273
280
  */
274
- private external_editor: Partial<ExternalEditorType> | undefined;
281
+ private external_editor?: ExternalEditor;
275
282
 
276
283
  /**
277
284
  * flag indicating we're resizing, or hovering over a resize.
@@ -357,12 +364,11 @@ export class Grid extends GridBase {
357
364
  */
358
365
  constructor(
359
366
  options: GridOptions = {},
360
- parser: Parser,
361
367
  model: DataModel,
362
368
  theme: Theme = DefaultTheme,
363
369
  initialze_dom = true ) {
364
370
 
365
- super(options, parser, model);
371
+ super(options, model);
366
372
 
367
373
  this.decimal_separator_code = Localization.decimal_separator.charCodeAt(0);
368
374
 
@@ -1234,7 +1240,7 @@ export class Grid extends GridBase {
1234
1240
  this.UpdateLayout(); // in case we have changed font size
1235
1241
  // this.selection_renderer.Flush();
1236
1242
 
1237
- this.overlay_editor?.UpdateTheme(this.layout.scale);
1243
+ this.overlay_editor?.UpdateScale(this.layout.scale);
1238
1244
 
1239
1245
  // if (this.formula_bar) this.formula_bar.UpdateTheme();
1240
1246
 
@@ -1659,21 +1665,48 @@ export class Grid extends GridBase {
1659
1665
  * want to have to use a duplicate internal field, it's clumsy and this
1660
1666
  * class isn't public-facing so it's not super important.
1661
1667
  */
1662
- public ExternalEditor(external_editor: Partial<ExternalEditorType>|undefined) {
1668
+ public ExternalEditor(config: Partial<ExternalEditorConfig>|undefined) {
1669
+
1670
+ this.external_editor_config = config;
1671
+
1672
+ if (config) {
1663
1673
 
1664
- this.external_editor = external_editor;
1674
+ const areas: Area[] = (config.dependencies || []).filter(
1675
+ <T>(test: T|undefined): test is T => !!test).map(reference =>
1676
+ IsCellAddress(reference) ? new Area(reference) : new Area(reference.start, reference.end));
1665
1677
 
1666
- if (external_editor) {
1667
- if (external_editor.dependencies) {
1678
+ if (config.nodes?.length) {
1668
1679
 
1669
- // FIXME: this is getting messy and we're doing it twice, consolidate
1680
+ if (!this.external_editor) {
1681
+ const editor = new ExternalEditor(this.model, this.view);
1682
+ this.external_editor = editor;
1670
1683
 
1671
- this.HighlightDependencies(external_editor.dependencies.filter(
1672
- <T>(entry: T|undefined): entry is T => !!entry).map(reference =>
1673
- IsCellAddress(reference) ? new Area(reference) : new Area(reference.start, reference.end)));
1684
+ // should this persist, or should we only subscribe when we're active? (...)
1685
+ // in theory, at least, it won't send any events unless something changes
1686
+
1687
+ editor.Subscribe(event => this.HighlightDependencies(editor.dependencies));
1688
+ }
1689
+
1690
+ this.external_editor.AttachNodes(config.nodes, config.assume_formula ?? true);
1691
+
1674
1692
  }
1693
+ else {
1694
+ if (this.external_editor) {
1695
+ this.external_editor.Reset();
1696
+ }
1697
+ }
1698
+
1699
+ if (config.dependencies) {
1700
+ this.HighlightDependencies(areas);
1701
+ }
1702
+
1675
1703
  }
1676
1704
  else {
1705
+
1706
+ if (this.external_editor) {
1707
+ this.external_editor.Reset();
1708
+ }
1709
+
1677
1710
  this.ClearAdditionalSelections();
1678
1711
  this.RenderSelections(true);
1679
1712
  }
@@ -1687,15 +1720,16 @@ export class Grid extends GridBase {
1687
1720
  this.RenderSelections();
1688
1721
  }
1689
1722
 
1690
- /**
1723
+ /* *
1691
1724
  * FIXME: who uses this? anyone?
1692
- */
1725
+ * /
1693
1726
  public GetNumberFormat(address: ICellAddress): string|undefined {
1694
1727
  const style = this.active_sheet.CellStyleData(address);
1695
1728
  if (style && style.number_format) {
1696
1729
  return NumberFormatCache.Get(style.number_format).toString();
1697
1730
  }
1698
1731
  }
1732
+ */
1699
1733
 
1700
1734
  /**
1701
1735
  * I can't figure out a way in typescript to overload the GetRange function
@@ -1953,7 +1987,7 @@ export class Grid extends GridBase {
1953
1987
  }
1954
1988
 
1955
1989
  /** repaint after an external event (calculation) */
1956
- public Update(force = false, area?: Area): void {
1990
+ public Update(force = false, area?: IArea|IArea[]): void {
1957
1991
  this.DelayedRender(force, area);
1958
1992
  }
1959
1993
 
@@ -2534,8 +2568,8 @@ export class Grid extends GridBase {
2534
2568
 
2535
2569
  this.formula_bar = new FormulaBar(
2536
2570
  grid_container,
2537
- this.parser,
2538
- this.theme,
2571
+ // this.parser,
2572
+ // this.theme,
2539
2573
  this.model,
2540
2574
  this.view,
2541
2575
  this.options, autocomplete);
@@ -2711,13 +2745,12 @@ export class Grid extends GridBase {
2711
2745
 
2712
2746
  this.overlay_editor = new OverlayEditor(
2713
2747
  this.container,
2714
- this.parser,
2715
2748
  this.theme,
2716
2749
  this.model,
2717
2750
  this.view,
2718
2751
  autocomplete);
2719
2752
 
2720
- this.overlay_editor.UpdateTheme(this.layout.scale);
2753
+ this.overlay_editor.UpdateScale(this.layout.scale);
2721
2754
  this.overlay_editor.autocomplete_matcher = this.autocomplete_matcher;
2722
2755
 
2723
2756
  this.overlay_editor.Subscribe(event => {
@@ -2745,7 +2778,11 @@ export class Grid extends GridBase {
2745
2778
  break;
2746
2779
 
2747
2780
  case 'end-selection':
2781
+ case 'reset-selection':
2748
2782
  this.ClearSelection(this.active_selection);
2783
+ if (this.overlay_editor?.target_address?.sheet_id && this.active_sheet.id !== this.overlay_editor.target_address.sheet_id) {
2784
+ this.ActivateSheetID(this.overlay_editor.target_address.sheet_id);
2785
+ }
2749
2786
  this.DelayedRender();
2750
2787
  break;
2751
2788
 
@@ -2756,9 +2793,7 @@ export class Grid extends GridBase {
2756
2793
 
2757
2794
  }
2758
2795
 
2759
-
2760
-
2761
- private DelayedRender(force = false, area?: Area, full_tile = false) {
2796
+ private DelayedRender(force = false, area?: IArea|IArea[], full_tile = false) {
2762
2797
 
2763
2798
  // if area is passed, set dirty before calling repaint
2764
2799
 
@@ -3732,7 +3767,7 @@ export class Grid extends GridBase {
3732
3767
  this.ClearAdditionalSelections();
3733
3768
  }
3734
3769
 
3735
- if (!selecting_argument || !this.formula_bar?.selecting) {
3770
+ if (!selecting_argument || (!this.formula_bar?.selecting && !this.external_editor?.selecting)) {
3736
3771
 
3737
3772
  // not sure why this breaks the formula bar handler
3738
3773
 
@@ -4021,11 +4056,19 @@ export class Grid extends GridBase {
4021
4056
  if (this.overlay_editor?.editing) {
4022
4057
  // ...
4023
4058
  }
4024
- else if (this.external_editor) {
4025
- // ...
4026
- }
4027
- else if (this.select_argument) {
4028
- // ...
4059
+ else if (this.external_editor_config) {
4060
+
4061
+ // there are two possible cases: either an update function
4062
+ // or a full-on editor. we should probably test for both?
4063
+
4064
+ if (this.external_editor?.active) {
4065
+ this.external_editor.FocusEditor();
4066
+ }
4067
+ if (this.external_editor_config.update) {
4068
+ // not necessary?
4069
+ // console.info('call update?');
4070
+ }
4071
+
4029
4072
  }
4030
4073
  else if (this.formula_bar) {
4031
4074
  this.formula_bar.FocusEditor();
@@ -4289,7 +4332,7 @@ export class Grid extends GridBase {
4289
4332
  label = Area.CellAddressToLabel(data.merge_area.start);
4290
4333
  }
4291
4334
 
4292
- if (this.external_editor || this.active_sheet.id !== this.editing_cell.sheet_id) {
4335
+ if (this.external_editor_config || this.active_sheet.id !== this.editing_cell.sheet_id) {
4293
4336
  const name = this.active_sheet.name;
4294
4337
 
4295
4338
  if (QuotedSheetNameRegex.test(name)) {
@@ -4311,9 +4354,15 @@ export class Grid extends GridBase {
4311
4354
  else if (this.formula_bar && this.formula_bar.selecting) {
4312
4355
  this.formula_bar.InsertReference(label, 0);
4313
4356
  }
4314
- else if (this.external_editor) {
4315
- if (this.external_editor.update) {
4316
- const result = this.external_editor.update.call(0, label);
4357
+ else if (this.external_editor_config) {
4358
+
4359
+ if (this.external_editor?.active) {
4360
+ this.external_editor.FocusEditor();
4361
+ this.external_editor.InsertReference(label, 0);
4362
+ }
4363
+
4364
+ if (this.external_editor_config.update) {
4365
+ const result = this.external_editor_config.update.call(0, label);
4317
4366
  if (result && Array.isArray(result)) {
4318
4367
  this.HighlightDependencies(
4319
4368
  result.filter(<T>(entry: T|undefined): entry is T => !!entry).map(reference =>
@@ -4321,12 +4370,14 @@ export class Grid extends GridBase {
4321
4370
  }
4322
4371
  }
4323
4372
  }
4373
+ /*
4324
4374
  else if (this.select_argument) {
4325
4375
  this.grid_events.Publish({
4326
4376
  type: 'alternate-selection',
4327
4377
  selection: this.active_selection,
4328
4378
  });
4329
4379
  }
4380
+ */
4330
4381
  }
4331
4382
 
4332
4383
  /**
@@ -4335,11 +4386,11 @@ export class Grid extends GridBase {
4335
4386
  *
4336
4387
  * FIXME: why is this not an accessor?
4337
4388
  */
4338
- private SelectingArgument() {
4389
+ private SelectingArgument(): boolean {
4339
4390
  return (this.overlay_editor?.editing && this.overlay_editor?.selecting)
4340
4391
  || (this.formula_bar && this.formula_bar.selecting)
4341
- || (this.external_editor)
4342
- || (this.select_argument);
4392
+ || (!!this.external_editor_config);
4393
+ // || (this.select_argument);
4343
4394
  }
4344
4395
 
4345
4396
  /**
@@ -4371,16 +4422,16 @@ export class Grid extends GridBase {
4371
4422
  editor_open = true;
4372
4423
  const result = this.overlay_editor.HandleKeyDown(event);
4373
4424
  switch (result) {
4374
- case OverlayEditorResult.handled:
4425
+ case 'handled':
4375
4426
  return;
4376
4427
 
4377
- case OverlayEditorResult.discard:
4428
+ case 'discard':
4378
4429
  this.editing_state = EditingState.NotEditing;
4379
4430
  this.DismissEditor();
4380
4431
  this.DelayedRender();
4381
4432
  return;
4382
4433
 
4383
- case OverlayEditorResult.commit:
4434
+ case 'commit':
4384
4435
 
4385
4436
  // FIXME: unify this (to the extent possible) w/ the other editor
4386
4437
 
@@ -40,7 +40,7 @@ import type { DataModel, MacroFunction, SerializedModel, SerializedNamedExpressi
40
40
  import type { Parser, UnitAddress} from 'treb-parser';
41
41
  import { type ExpressionUnit, IllegalSheetNameRegex, ParseCSV, ArgumentSeparatorType, DecimalMarkType } from 'treb-parser';
42
42
  import { Area, IsCellAddress, ValidationType, ValueType, DefaultTableSortOptions } from 'treb-base-types';
43
- import type { ICellAddress, IArea, Cell, CellValue , Style, CellStyle, Table, TableSortOptions, TableTheme, Complex } from 'treb-base-types';
43
+ import type { ICellAddress, IArea, Cell, CellValue , Style, CellStyle, Table, TableSortOptions, TableTheme, Complex, PatchOptions as PatchAreaOptions } from 'treb-base-types';
44
44
  import { Sheet } from './sheet';
45
45
  import type { FunctionDescriptor} from '../editors/autocomplete_matcher';
46
46
  import { AutocompleteMatcher, DescriptorType } from '../editors/autocomplete_matcher';
@@ -61,6 +61,11 @@ import type { UpdateFlags } from './update_flags';
61
61
  import type { FreezePane, LegacySerializedSheet } from './sheet_types';
62
62
  import type { Annotation } from './annotation';
63
63
  import type { ClipboardCellData } from './clipboard_data';
64
+ import type { ConditionalFormat } from './conditional_format';
65
+
66
+ interface PatchOptions extends PatchAreaOptions {
67
+ sheet: Sheet;
68
+ }
64
69
 
65
70
  export class GridBase {
66
71
 
@@ -132,7 +137,6 @@ export class GridBase {
132
137
 
133
138
  constructor(
134
139
  options: GridOptions = {},
135
- parser: Parser,
136
140
  model: DataModel) {
137
141
 
138
142
  this.model = model;
@@ -144,7 +148,7 @@ export class GridBase {
144
148
 
145
149
  // shared parser
146
150
 
147
- this.parser = parser;
151
+ this.parser = model.parser;
148
152
 
149
153
  // apply default options, meaning that you need to explicitly set/unset
150
154
  // in order to change behavior. FIXME: this is ok for flat structure, but
@@ -2147,6 +2151,29 @@ export class GridBase {
2147
2151
 
2148
2152
  }
2149
2153
 
2154
+ protected PatchExpressionSheetName(expression: string, old_name: string, name: string): string|undefined {
2155
+
2156
+ let modified = false;
2157
+ const parsed = this.parser.Parse(expression || '');
2158
+ if (parsed.expression) {
2159
+ this.parser.Walk(parsed.expression, (element: ExpressionUnit) => {
2160
+ if (element.type === 'address') {
2161
+ if (element.sheet && element.sheet.toLowerCase() === old_name) {
2162
+ element.sheet = name;
2163
+ modified = true;
2164
+ }
2165
+ }
2166
+ return true; // continue walk
2167
+ });
2168
+ if (modified) {
2169
+ return '=' + this.parser.Render(parsed.expression, { missing: '' });
2170
+ }
2171
+ }
2172
+
2173
+ return undefined;
2174
+
2175
+ }
2176
+
2150
2177
  /**
2151
2178
  * splitting this logic into a new function so we can reuse it
2152
2179
  * for invalidating broken references. generally we'll call this
@@ -2166,45 +2193,39 @@ export class GridBase {
2166
2193
  // cells
2167
2194
  sheet.cells.IterateAll((cell: Cell) => {
2168
2195
  if (cell.ValueIsFormula()) {
2169
- let modified = false;
2170
- const parsed = this.parser.Parse(cell.value || '');
2171
- if (parsed.expression) {
2172
- this.parser.Walk(parsed.expression, (element: ExpressionUnit) => {
2173
- if (element.type === 'address') {
2174
- if (element.sheet && element.sheet.toLowerCase() === old_name) {
2175
- element.sheet = name;
2176
- modified = true;
2196
+ const updated = this.PatchExpressionSheetName(cell.value||'', old_name, name);
2197
+ if (updated) {
2198
+ cell.value = updated;
2199
+ changes++;
2200
+ }
2201
+ }
2202
+ });
2203
+
2204
+ // conditionals
2205
+ if (sheet.conditional_formats?.length) {
2206
+ for (const format of sheet.conditional_formats) {
2207
+ switch (format.type) {
2208
+ case 'cell-match':
2209
+ case 'expression':
2210
+ {
2211
+ const updated = this.PatchExpressionSheetName(format.expression, old_name, name);
2212
+ if (updated) {
2213
+ format.expression = updated;
2214
+ changes++;
2177
2215
  }
2178
2216
  }
2179
- return true; // continue walk
2180
- });
2181
- if (modified) {
2182
- cell.value = '=' + this.parser.Render(parsed.expression, { missing: '' });
2183
- changes++;
2184
- }
2217
+ break;
2185
2218
  }
2186
2219
  }
2187
- });
2220
+ }
2188
2221
 
2189
2222
  // annotations
2190
2223
  for (const annotation of sheet.annotations) {
2191
2224
  if (annotation.data.formula) {
2192
- let modified = false;
2193
- const parsed = this.parser.Parse(annotation.data.formula || '');
2194
- if (parsed.expression) {
2195
- this.parser.Walk(parsed.expression, (element: ExpressionUnit) => {
2196
- if (element.type === 'address') {
2197
- if (element.sheet && element.sheet.toLowerCase() === old_name) {
2198
- element.sheet = name;
2199
- modified = true;
2200
- }
2201
- }
2202
- return true; // continue walk
2203
- });
2204
- if (modified) {
2205
- annotation.data.formula = '=' + this.parser.Render(parsed.expression, { missing: '' });
2206
- changes++;
2207
- }
2225
+ const updated = this.PatchExpressionSheetName(annotation.data.formula, old_name, name);
2226
+ if (updated) {
2227
+ annotation.data.formula = updated;
2228
+ changes++;
2208
2229
  }
2209
2230
  }
2210
2231
  }
@@ -2635,7 +2656,121 @@ export class GridBase {
2635
2656
 
2636
2657
  }
2637
2658
 
2638
-
2659
+ /**
2660
+ * patch an expression for insert/delete row/column operations.
2661
+ * FIXME: should move, maybe to parser? (...)
2662
+ *
2663
+ * NOTE: did I write this twice? we only need one. check which one is better.
2664
+ * @see PatchFormulasInternal
2665
+ *
2666
+ * @returns the updated expression, or `undefined` if no changes were made.
2667
+ */
2668
+ protected PatchExpression(expression: string, options: PatchOptions) {
2669
+
2670
+ let count = 0;
2671
+ const parse_result = this.parser.Parse(expression);
2672
+ if (parse_result.expression) {
2673
+
2674
+ this.parser.Walk(parse_result.expression, unit => {
2675
+ if (unit.type === 'range') {
2676
+ if (!unit.start.sheet || (unit.start.sheet.toLowerCase() === options.sheet.name.toLowerCase())) {
2677
+ const updated = Area.PatchArea(unit, options);
2678
+ if (updated) {
2679
+ unit.start.row = updated.start.row;
2680
+ unit.start.column = updated.start.column;
2681
+ unit.end.row = updated.end.row;
2682
+ unit.end.column = updated.end.column;
2683
+ }
2684
+ else {
2685
+
2686
+ // FIXME: maybe have options for this? we don't really have a
2687
+ // good way to replace nodes atm
2688
+ unit.start.row = unit.end.row = unit.start.column = unit.end.column = -1;
2689
+
2690
+ }
2691
+ }
2692
+ count++;
2693
+ return false;
2694
+ }
2695
+ else if (unit.type === 'address') {
2696
+ const updated = Area.PatchArea({start: unit, end: unit}, options);
2697
+ if (updated) {
2698
+ unit.row = updated.start.row;
2699
+ unit.column = updated.start.column;
2700
+ }
2701
+ else {
2702
+
2703
+ // see above
2704
+ unit.row = unit.column = -1;
2705
+ }
2706
+ count++;
2707
+ return false;
2708
+ }
2709
+ return true;
2710
+ });
2711
+
2712
+ if (count) {
2713
+ const rendered = this.parser.Render(parse_result.expression, {
2714
+ missing: '',
2715
+ });
2716
+ // console.info("FROM", expression, "TO", rendered);
2717
+ return rendered;
2718
+ }
2719
+
2720
+ }
2721
+
2722
+ return undefined;
2723
+
2724
+ }
2725
+
2726
+ /**
2727
+ * patch sheet conditionals for insert/delete row/column operations.
2728
+ * some of them may be deleted.
2729
+ */
2730
+ protected PatchConditionals(options: PatchOptions) {
2731
+
2732
+ if (options.sheet.conditional_formats?.length) {
2733
+
2734
+ const delete_list: Set<ConditionalFormat> = new Set();
2735
+ for (const format of options.sheet.conditional_formats) {
2736
+
2737
+ // first adjust the format area
2738
+
2739
+ const updated = Area.PatchArea(format.area, options);
2740
+ if (updated) {
2741
+ format.area = JSON.parse(JSON.stringify(updated));
2742
+ }
2743
+ else {
2744
+ delete_list.add(format);
2745
+ continue; // don't bother with formula
2746
+ }
2747
+
2748
+ // next update the formula, if necessary. what do we do if the
2749
+ // area has disappeared? should be a #REF error, not sure we
2750
+ // can encode that properly
2751
+
2752
+ switch (format.type) {
2753
+ case 'expression':
2754
+ case 'cell-match':
2755
+ {
2756
+ const updated = this.PatchExpression(format.expression, options);
2757
+
2758
+ if (updated) {
2759
+ format.expression = updated;
2760
+ }
2761
+ }
2762
+ break;
2763
+ }
2764
+
2765
+ }
2766
+
2767
+ if (delete_list.size) {
2768
+ options.sheet.conditional_formats = options.sheet.conditional_formats.filter(test => !delete_list.has(test));
2769
+ }
2770
+
2771
+ }
2772
+
2773
+ }
2639
2774
 
2640
2775
  /**
2641
2776
  * FIXME: should be API method
@@ -2665,6 +2800,15 @@ export class GridBase {
2665
2800
  return { error: true };
2666
2801
  }
2667
2802
 
2803
+ // conditionals
2804
+ this.PatchConditionals({
2805
+ sheet: target_sheet,
2806
+ before_column: 0,
2807
+ column_count: 0,
2808
+ before_row: command.before_row,
2809
+ row_count: command.count
2810
+ });
2811
+
2668
2812
  // see InsertColumnsInternal re: tables. rows are less complicated,
2669
2813
  // except that if you delete the header row we want to remove the
2670
2814
  // table entirely.
@@ -2979,6 +3123,14 @@ export class GridBase {
2979
3123
  return { error: true };
2980
3124
  }
2981
3125
 
3126
+ // conditionals
3127
+ this.PatchConditionals({
3128
+ sheet: target_sheet,
3129
+ before_column: command.before_column,
3130
+ column_count: command.count,
3131
+ before_row: 0,
3132
+ row_count: 0 });
3133
+
2982
3134
  // patch tables. we removed this from the sheet routine entirely,
2983
3135
  // we need to rebuild any affected tables now.
2984
3136
 
@@ -137,7 +137,7 @@ export class ScaleControl extends EventSource<ScaleEvent> {
137
137
 
138
138
  });
139
139
 
140
- this.slider = DOM.Create<HTMLInputElement>('input', undefined, popup, undefined, {
140
+ this.slider = DOM.Create('input', undefined, popup, undefined, {
141
141
  type: 'range',
142
142
  min: '50',
143
143
  max: '200',