@trebco/treb 27.7.6 → 27.11.1

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 (55) hide show
  1. package/dist/treb-spreadsheet.mjs +14 -14
  2. package/dist/treb.d.ts +28 -26
  3. package/notes/conditional-fomratring.md +29 -0
  4. package/package.json +1 -1
  5. package/treb-base-types/src/area.ts +181 -0
  6. package/{treb-grid/src/util/dom_utilities.ts → treb-base-types/src/dom-utilities.ts} +29 -20
  7. package/treb-base-types/src/evaluate-options.ts +21 -0
  8. package/treb-base-types/src/gradient.ts +97 -0
  9. package/treb-base-types/src/import.ts +2 -1
  10. package/treb-base-types/src/index.ts +3 -0
  11. package/treb-base-types/src/theme.ts +3 -5
  12. package/treb-calculator/src/calculator.ts +190 -28
  13. package/treb-calculator/src/dag/calculation_leaf_vertex.ts +97 -0
  14. package/treb-calculator/src/dag/graph.ts +10 -22
  15. package/treb-calculator/src/dag/{leaf_vertex.ts → state_leaf_vertex.ts} +3 -3
  16. package/treb-calculator/src/descriptors.ts +10 -3
  17. package/treb-calculator/src/expression-calculator.ts +1 -1
  18. package/treb-calculator/src/function-library.ts +25 -22
  19. package/treb-calculator/src/functions/base-functions.ts +166 -5
  20. package/treb-calculator/src/index.ts +6 -6
  21. package/treb-calculator/src/notifier-types.ts +1 -1
  22. package/treb-calculator/src/utilities.ts +2 -2
  23. package/treb-charts/src/util.ts +2 -2
  24. package/treb-embed/src/custom-element/global.d.ts +3 -1
  25. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +13 -19
  26. package/treb-embed/src/embedded-spreadsheet.ts +378 -132
  27. package/treb-embed/src/spinner.ts +3 -3
  28. package/treb-embed/style/layout.scss +1 -1
  29. package/treb-export/src/drawing2/chart2.ts +11 -2
  30. package/treb-export/src/export-worker/export-worker.ts +0 -13
  31. package/treb-export/src/export2.ts +197 -2
  32. package/treb-export/src/import2.ts +169 -4
  33. package/treb-export/src/workbook-style2.ts +59 -10
  34. package/treb-export/src/workbook2.ts +10 -1
  35. package/treb-grid/src/editors/autocomplete.ts +28 -24
  36. package/treb-grid/src/editors/editor.ts +3 -4
  37. package/treb-grid/src/editors/formula_bar.ts +1 -1
  38. package/treb-grid/src/index.ts +2 -1
  39. package/treb-grid/src/layout/base_layout.ts +34 -31
  40. package/treb-grid/src/layout/grid_layout.ts +17 -28
  41. package/treb-grid/src/render/selection-renderer.ts +2 -3
  42. package/treb-grid/src/render/svg_header_overlay.ts +4 -11
  43. package/treb-grid/src/render/svg_selection_block.ts +27 -34
  44. package/treb-grid/src/render/tile_renderer.ts +8 -6
  45. package/treb-grid/src/types/conditional_format.ts +168 -0
  46. package/treb-grid/src/types/grid.ts +37 -47
  47. package/treb-grid/src/types/grid_base.ts +188 -33
  48. package/treb-grid/src/types/scale-control.ts +2 -2
  49. package/treb-grid/src/types/sheet.ts +332 -28
  50. package/treb-grid/src/types/sheet_types.ts +4 -0
  51. package/treb-grid/src/types/tab_bar.ts +4 -8
  52. package/treb-utils/src/index.ts +0 -1
  53. package/treb-utils/src/resizable.ts +26 -27
  54. package/treb-utils/src/template.ts +0 -70
  55. /package/{README-shadow-DOM.md → notes/shadow-DOM.md} +0 -0
@@ -0,0 +1,168 @@
1
+
2
+ import type { CellStyle, EvaluateOptions, IArea, Color, Gradient, GradientStop, UnionValue } from 'treb-base-types';
3
+
4
+ interface VertexPlaceholder {
5
+ result: UnionValue;
6
+ updated: boolean;
7
+ }
8
+
9
+ export interface CondifionalFormatExpressionOptions {
10
+ style: CellStyle;
11
+ expression: string;
12
+ options?: EvaluateOptions;
13
+ }
14
+
15
+ /**
16
+ * conditional format predicated on an expression. if the expression
17
+ * evaluates to true, we apply the style. otherwise no.
18
+ */
19
+ export interface ConditionalFormatExpression extends CondifionalFormatExpressionOptions {
20
+
21
+ type: 'expression';
22
+ area: IArea;
23
+
24
+ /** @internal */
25
+ internal?: {
26
+ vertex?: VertexPlaceholder;
27
+ };
28
+
29
+ }
30
+
31
+ export interface ConditionalFormatGradientOptions {
32
+
33
+ /** property defaults to fill */
34
+ property?: 'fill'|'text';
35
+
36
+ /** defaults to HSL */
37
+ color_space?: 'HSL'|'RGB';
38
+
39
+ /** gradient stops, required */
40
+ stops: Array<{ value: number, color: Color }>;
41
+
42
+ /** min and max are optional. if not provided, we use the min/max of the range of data. */
43
+ min?: number;
44
+
45
+ /** min and max are optional. if not provided, we use the min/max of the range of data. */
46
+ max?: number;
47
+
48
+ }
49
+
50
+
51
+ export const StandardGradientsList = {
52
+ 'red-green': {
53
+ color_space: 'RGB',
54
+ stops: [
55
+ { value: 0, color: { theme: 5, tint: .5 }},
56
+ { value: 1, color: { theme: 9, tint: .5 }},
57
+ ] as GradientStop[],
58
+ },
59
+ 'red-yellow-green': {
60
+ color_space: 'RGB',
61
+ stops: [
62
+ { value: 0, color: { theme: 5, tint: .5 }},
63
+ { value: 0.5, color: { theme: 7, tint: .5 }},
64
+ { value: 1, color: { theme: 9, tint: .5 }},
65
+ ] as GradientStop[],
66
+ },
67
+ 'green-red': {
68
+ color_space: 'RGB',
69
+ stops: [
70
+ { value: 0, color: { theme: 9, tint: .5 }},
71
+ { value: 1, color: { theme: 5, tint: .5 }},
72
+ ] as GradientStop[],
73
+ },
74
+ 'green-yellow-red': {
75
+ color_space: 'RGB',
76
+ stops: [
77
+ { value: 0, color: { theme: 9, tint: .5 }},
78
+ { value: 0.5, color: { theme: 7, tint: .5 }},
79
+ { value: 1, color: { theme: 5, tint: .5 }},
80
+ ] as GradientStop[],
81
+ },
82
+ } as const;
83
+ export type StandardGradient = keyof typeof StandardGradientsList;
84
+
85
+ export interface ConditionalFormatGradient extends ConditionalFormatGradientOptions {
86
+ type: 'gradient';
87
+ area: IArea;
88
+
89
+ /** @internal */
90
+ internal?: {
91
+ gradient?: Gradient;
92
+ vertex?: VertexPlaceholder;
93
+ };
94
+ }
95
+
96
+ export interface ConditionalFormatCellMatchOptions {
97
+ style: CellStyle;
98
+ expression: string;
99
+ options?: EvaluateOptions;
100
+ }
101
+
102
+ export interface ConditionalFormatCellMatch extends ConditionalFormatCellMatchOptions {
103
+ type: 'cell-match';
104
+ area: IArea;
105
+
106
+ /** @internal */
107
+ internal?: {
108
+ vertex?: VertexPlaceholder;
109
+ };
110
+ }
111
+
112
+ export interface ConditionalFormatCellMatchOptions {
113
+ style: CellStyle;
114
+ expression: string;
115
+ options?: EvaluateOptions;
116
+ }
117
+
118
+ export interface ConditionalFormatCellMatch extends ConditionalFormatCellMatchOptions {
119
+ type: 'cell-match';
120
+ area: IArea;
121
+
122
+ /** @internal */
123
+ internal?: {
124
+ vertex?: VertexPlaceholder;
125
+ };
126
+ }
127
+
128
+ export interface ConditionalFormatDuplicateValuesOptions {
129
+ style: CellStyle;
130
+
131
+ /** true to highlight unique cells, false to highlight duplicates. defaults to false. */
132
+ unique?: boolean;
133
+ }
134
+
135
+ export interface ConditionalFormatDuplicateValues extends ConditionalFormatDuplicateValuesOptions {
136
+
137
+ type: 'duplicate-values';
138
+ area: IArea;
139
+
140
+ /** @internal */
141
+ internal?: {
142
+ vertex?: VertexPlaceholder;
143
+ };
144
+
145
+ }
146
+
147
+ /**
148
+ * union, plus we're adding a state used to track application.
149
+ * that state is serialized if it's true.
150
+ * we also add an internal field that will be type-specific, and not serialized.
151
+ *
152
+ * ...everybody has a vertex now, we could standardize it
153
+ *
154
+ */
155
+ export type ConditionalFormat = { internal?: unknown } & (
156
+ ConditionalFormatDuplicateValues |
157
+ ConditionalFormatExpression |
158
+ ConditionalFormatCellMatch |
159
+ ConditionalFormatGradient
160
+ );
161
+
162
+ /**
163
+ * the list of formats, in reverse order of precedence. as a starting
164
+ * point we're using the naive approach, just applying everything in
165
+ * order. that may change.
166
+ */
167
+ export type ConditionalFormatList = ConditionalFormat[];
168
+
@@ -111,7 +111,7 @@ import { CommandKey
111
111
 
112
112
  import type { DataModel, SerializedModel } from './data_model';
113
113
 
114
- import { DOMUtilities } from '../util/dom_utilities';
114
+ import { DOMUtilities } from 'treb-base-types';
115
115
  import { GridBase } from './grid_base';
116
116
  import type { SetRangeOptions } from './set_range_options';
117
117
  import type { ClipboardCellData } from './clipboard_data';
@@ -262,6 +262,9 @@ export class Grid extends GridBase {
262
262
  /** new key capture overlay/ICE */
263
263
  private overlay_editor?: OverlayEditor;
264
264
 
265
+ /** moving autocomplete to a class field */
266
+ private autocomplete?: Autocomplete;
267
+
265
268
  /** formula bar editor (optional) */
266
269
  private formula_bar?: FormulaBar;
267
270
 
@@ -568,13 +571,13 @@ export class Grid extends GridBase {
568
571
 
569
572
  // FIXME: why is this not in layout? it is layout.
570
573
 
571
- view.node = document.createElement('div');
574
+ view.node = DOMUtilities.Div();
572
575
  view.node.dataset.scale = this.layout.scale.toString();
573
576
  view.node.style.fontSize = `${10 * this.layout.scale}pt`;
574
577
 
575
- view.content_node = DOMUtilities.CreateDiv('annotation-content', view.node);
576
- const move_target = DOMUtilities.CreateDiv('annotation-move-target', view.node);
577
- const resize_target = DOMUtilities.CreateDiv('annotation-resize-target', view.node);
578
+ view.content_node = DOMUtilities.Div('annotation-content', view.node);
579
+ const move_target = DOMUtilities.Div('annotation-move-target', view.node);
580
+ const resize_target = DOMUtilities.Div('annotation-resize-target', view.node);
578
581
 
579
582
  if (view.node) {
580
583
  const node = view.node;
@@ -1275,13 +1278,13 @@ export class Grid extends GridBase {
1275
1278
  const higher_level_container = view_node.querySelector('.treb-spreadsheet-body') as HTMLElement;
1276
1279
  const container = higher_level_container.querySelector('div') as HTMLElement;
1277
1280
 
1278
- let autocomplete: Autocomplete | undefined;
1281
+ // let autocomplete: Autocomplete | undefined;
1279
1282
 
1280
1283
  if (this.options.formula_bar) {
1281
- if (!autocomplete) {
1282
- autocomplete = new Autocomplete({ theme: this.theme, container });
1284
+ if (!this.autocomplete) {
1285
+ this.autocomplete = new Autocomplete({ theme: this.theme, container });
1283
1286
  }
1284
- this.InitFormulaBar(view_node, autocomplete);
1287
+ this.InitFormulaBar(view_node, this.autocomplete);
1285
1288
  }
1286
1289
 
1287
1290
  if (this.options.tab_bar) {
@@ -1408,10 +1411,10 @@ export class Grid extends GridBase {
1408
1411
 
1409
1412
  // Sheet.sheet_events.Subscribe(this.HandleSheetEvent.bind(this));
1410
1413
 
1411
- if (!autocomplete) {
1412
- autocomplete = new Autocomplete({ theme: this.theme, container });
1414
+ if (!this.autocomplete) {
1415
+ this.autocomplete = new Autocomplete({ theme: this.theme, container });
1413
1416
  }
1414
- this.InitOverlayEditor(autocomplete);
1417
+ this.InitOverlayEditor(this.autocomplete);
1415
1418
 
1416
1419
  this.AttachListeners();
1417
1420
 
@@ -1720,15 +1723,16 @@ export class Grid extends GridBase {
1720
1723
  this.RenderSelections();
1721
1724
  }
1722
1725
 
1723
- /**
1726
+ /* *
1724
1727
  * FIXME: who uses this? anyone?
1725
- */
1728
+ * /
1726
1729
  public GetNumberFormat(address: ICellAddress): string|undefined {
1727
1730
  const style = this.active_sheet.CellStyleData(address);
1728
1731
  if (style && style.number_format) {
1729
1732
  return NumberFormatCache.Get(style.number_format).toString();
1730
1733
  }
1731
1734
  }
1735
+ */
1732
1736
 
1733
1737
  /**
1734
1738
  * I can't figure out a way in typescript to overload the GetRange function
@@ -1986,7 +1990,7 @@ export class Grid extends GridBase {
1986
1990
  }
1987
1991
 
1988
1992
  /** repaint after an external event (calculation) */
1989
- public Update(force = false, area?: Area): void {
1993
+ public Update(force = false, area?: IArea|IArea[]): void {
1990
1994
  this.DelayedRender(force, area);
1991
1995
  }
1992
1996
 
@@ -2694,35 +2698,6 @@ export class Grid extends GridBase {
2694
2698
 
2695
2699
  this.OverlayKeyDown(event.event);
2696
2700
 
2697
- /*
2698
- let cloned_event: KeyboardEvent;
2699
- if (UA.trident) {
2700
- cloned_event = document.createEvent('KeyboardEvent');
2701
- const modifiers = [];
2702
- if (event.event.ctrlKey) modifiers.push('Control');
2703
- if (event.event.altKey) modifiers.push('Alt');
2704
- if (event.event.shiftKey) modifiers.push('Shift');
2705
-
2706
- // have to mask type for trident
2707
- (cloned_event as any).initKeyboardEvent(
2708
- event.event.type,
2709
- false,
2710
- false,
2711
- event.event.view,
2712
- event.event.key,
2713
- event.event.location,
2714
- modifiers.join(' '),
2715
- event.event.repeat,
2716
- Localization.locale);
2717
- }
2718
- else {
2719
- cloned_event = new KeyboardEvent(event.event.type, event.event);
2720
- }
2721
-
2722
- if (cloned_event && this.container) {
2723
- this.container.dispatchEvent(cloned_event);
2724
- }
2725
- */
2726
2701
  }
2727
2702
  break;
2728
2703
 
@@ -2792,9 +2767,7 @@ export class Grid extends GridBase {
2792
2767
 
2793
2768
  }
2794
2769
 
2795
-
2796
-
2797
- private DelayedRender(force = false, area?: Area, full_tile = false) {
2770
+ private DelayedRender(force = false, area?: IArea|IArea[], full_tile = false) {
2798
2771
 
2799
2772
  // if area is passed, set dirty before calling repaint
2800
2773
 
@@ -3764,6 +3737,17 @@ export class Grid extends GridBase {
3764
3737
 
3765
3738
  const selecting_argument = this.SelectingArgument();
3766
3739
 
3740
+ /*
3741
+ let blocking_tooltip = false;
3742
+ if (selecting_argument) {
3743
+ if (this.autocomplete?.tooltip_visible) {
3744
+ this.autocomplete.SetBlock();
3745
+ this.autocomplete.Hide();
3746
+ blocking_tooltip = true;
3747
+ }
3748
+ }
3749
+ */
3750
+
3767
3751
  if (!selecting_argument && this.additional_selections.length) {
3768
3752
  this.ClearAdditionalSelections();
3769
3753
  }
@@ -4053,6 +4037,12 @@ export class Grid extends GridBase {
4053
4037
  // console.info('end');
4054
4038
  this.UpdateAddressLabel();
4055
4039
 
4040
+ /*
4041
+ if (blocking_tooltip) {
4042
+ this.autocomplete?.ResetBlock();
4043
+ }
4044
+ */
4045
+
4056
4046
  if (selecting_argument) {
4057
4047
  if (this.overlay_editor?.editing) {
4058
4048
  // ...
@@ -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
 
@@ -2146,6 +2151,29 @@ export class GridBase {
2146
2151
 
2147
2152
  }
2148
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
+
2149
2177
  /**
2150
2178
  * splitting this logic into a new function so we can reuse it
2151
2179
  * for invalidating broken references. generally we'll call this
@@ -2165,45 +2193,39 @@ export class GridBase {
2165
2193
  // cells
2166
2194
  sheet.cells.IterateAll((cell: Cell) => {
2167
2195
  if (cell.ValueIsFormula()) {
2168
- let modified = false;
2169
- const parsed = this.parser.Parse(cell.value || '');
2170
- if (parsed.expression) {
2171
- this.parser.Walk(parsed.expression, (element: ExpressionUnit) => {
2172
- if (element.type === 'address') {
2173
- if (element.sheet && element.sheet.toLowerCase() === old_name) {
2174
- element.sheet = name;
2175
- 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++;
2176
2215
  }
2177
2216
  }
2178
- return true; // continue walk
2179
- });
2180
- if (modified) {
2181
- cell.value = '=' + this.parser.Render(parsed.expression, { missing: '' });
2182
- changes++;
2183
- }
2217
+ break;
2184
2218
  }
2185
2219
  }
2186
- });
2220
+ }
2187
2221
 
2188
2222
  // annotations
2189
2223
  for (const annotation of sheet.annotations) {
2190
2224
  if (annotation.data.formula) {
2191
- let modified = false;
2192
- const parsed = this.parser.Parse(annotation.data.formula || '');
2193
- if (parsed.expression) {
2194
- this.parser.Walk(parsed.expression, (element: ExpressionUnit) => {
2195
- if (element.type === 'address') {
2196
- if (element.sheet && element.sheet.toLowerCase() === old_name) {
2197
- element.sheet = name;
2198
- modified = true;
2199
- }
2200
- }
2201
- return true; // continue walk
2202
- });
2203
- if (modified) {
2204
- annotation.data.formula = '=' + this.parser.Render(parsed.expression, { missing: '' });
2205
- changes++;
2206
- }
2225
+ const updated = this.PatchExpressionSheetName(annotation.data.formula, old_name, name);
2226
+ if (updated) {
2227
+ annotation.data.formula = updated;
2228
+ changes++;
2207
2229
  }
2208
2230
  }
2209
2231
  }
@@ -2430,6 +2452,7 @@ export class GridBase {
2430
2452
  });
2431
2453
  if (transformed) {
2432
2454
 
2455
+ /*
2433
2456
  if (!this.flags.warned_r1c1) {
2434
2457
 
2435
2458
  // 1-time warning
@@ -2437,6 +2460,7 @@ export class GridBase {
2437
2460
  this.flags.warned_r1c1 = true;
2438
2461
  console.warn('NOTE: R1C1 support is experimental. the semantics may change in the future.');
2439
2462
  }
2463
+ */
2440
2464
 
2441
2465
  value = '=' + this.parser.Render(result.expression, { missing: '' });
2442
2466
  }
@@ -2634,7 +2658,121 @@ export class GridBase {
2634
2658
 
2635
2659
  }
2636
2660
 
2637
-
2661
+ /**
2662
+ * patch an expression for insert/delete row/column operations.
2663
+ * FIXME: should move, maybe to parser? (...)
2664
+ *
2665
+ * NOTE: did I write this twice? we only need one. check which one is better.
2666
+ * @see PatchFormulasInternal
2667
+ *
2668
+ * @returns the updated expression, or `undefined` if no changes were made.
2669
+ */
2670
+ protected PatchExpression(expression: string, options: PatchOptions) {
2671
+
2672
+ let count = 0;
2673
+ const parse_result = this.parser.Parse(expression);
2674
+ if (parse_result.expression) {
2675
+
2676
+ this.parser.Walk(parse_result.expression, unit => {
2677
+ if (unit.type === 'range') {
2678
+ if (!unit.start.sheet || (unit.start.sheet.toLowerCase() === options.sheet.name.toLowerCase())) {
2679
+ const updated = Area.PatchArea(unit, options);
2680
+ if (updated) {
2681
+ unit.start.row = updated.start.row;
2682
+ unit.start.column = updated.start.column;
2683
+ unit.end.row = updated.end.row;
2684
+ unit.end.column = updated.end.column;
2685
+ }
2686
+ else {
2687
+
2688
+ // FIXME: maybe have options for this? we don't really have a
2689
+ // good way to replace nodes atm
2690
+ unit.start.row = unit.end.row = unit.start.column = unit.end.column = -1;
2691
+
2692
+ }
2693
+ }
2694
+ count++;
2695
+ return false;
2696
+ }
2697
+ else if (unit.type === 'address') {
2698
+ const updated = Area.PatchArea({start: unit, end: unit}, options);
2699
+ if (updated) {
2700
+ unit.row = updated.start.row;
2701
+ unit.column = updated.start.column;
2702
+ }
2703
+ else {
2704
+
2705
+ // see above
2706
+ unit.row = unit.column = -1;
2707
+ }
2708
+ count++;
2709
+ return false;
2710
+ }
2711
+ return true;
2712
+ });
2713
+
2714
+ if (count) {
2715
+ const rendered = this.parser.Render(parse_result.expression, {
2716
+ missing: '',
2717
+ });
2718
+ // console.info("FROM", expression, "TO", rendered);
2719
+ return rendered;
2720
+ }
2721
+
2722
+ }
2723
+
2724
+ return undefined;
2725
+
2726
+ }
2727
+
2728
+ /**
2729
+ * patch sheet conditionals for insert/delete row/column operations.
2730
+ * some of them may be deleted.
2731
+ */
2732
+ protected PatchConditionals(options: PatchOptions) {
2733
+
2734
+ if (options.sheet.conditional_formats?.length) {
2735
+
2736
+ const delete_list: Set<ConditionalFormat> = new Set();
2737
+ for (const format of options.sheet.conditional_formats) {
2738
+
2739
+ // first adjust the format area
2740
+
2741
+ const updated = Area.PatchArea(format.area, options);
2742
+ if (updated) {
2743
+ format.area = JSON.parse(JSON.stringify(updated));
2744
+ }
2745
+ else {
2746
+ delete_list.add(format);
2747
+ continue; // don't bother with formula
2748
+ }
2749
+
2750
+ // next update the formula, if necessary. what do we do if the
2751
+ // area has disappeared? should be a #REF error, not sure we
2752
+ // can encode that properly
2753
+
2754
+ switch (format.type) {
2755
+ case 'expression':
2756
+ case 'cell-match':
2757
+ {
2758
+ const updated = this.PatchExpression(format.expression, options);
2759
+
2760
+ if (updated) {
2761
+ format.expression = updated;
2762
+ }
2763
+ }
2764
+ break;
2765
+ }
2766
+
2767
+ }
2768
+
2769
+ if (delete_list.size) {
2770
+ options.sheet.conditional_formats = options.sheet.conditional_formats.filter(test => !delete_list.has(test));
2771
+ }
2772
+
2773
+ }
2774
+
2775
+ }
2638
2776
 
2639
2777
  /**
2640
2778
  * FIXME: should be API method
@@ -2664,6 +2802,15 @@ export class GridBase {
2664
2802
  return { error: true };
2665
2803
  }
2666
2804
 
2805
+ // conditionals
2806
+ this.PatchConditionals({
2807
+ sheet: target_sheet,
2808
+ before_column: 0,
2809
+ column_count: 0,
2810
+ before_row: command.before_row,
2811
+ row_count: command.count
2812
+ });
2813
+
2667
2814
  // see InsertColumnsInternal re: tables. rows are less complicated,
2668
2815
  // except that if you delete the header row we want to remove the
2669
2816
  // table entirely.
@@ -2978,6 +3125,14 @@ export class GridBase {
2978
3125
  return { error: true };
2979
3126
  }
2980
3127
 
3128
+ // conditionals
3129
+ this.PatchConditionals({
3130
+ sheet: target_sheet,
3131
+ before_column: command.before_column,
3132
+ column_count: command.count,
3133
+ before_row: 0,
3134
+ row_count: 0 });
3135
+
2981
3136
  // patch tables. we removed this from the sheet routine entirely,
2982
3137
  // we need to rebuild any affected tables now.
2983
3138
 
@@ -19,7 +19,7 @@
19
19
  *
20
20
  */
21
21
 
22
- import { DOMUtilities as DOM } from '../util/dom_utilities';
22
+ import { DOMUtilities as DOM } from 'treb-base-types';
23
23
  import { NumberFormat, NumberFormatCache, ValueParser } from 'treb-format';
24
24
  import { ValueType } from 'treb-base-types';
25
25
  import { EventSource } from 'treb-utils';
@@ -54,7 +54,7 @@ export class ScaleControl extends EventSource<ScaleEvent> {
54
54
  // const div = DOM.CreateDiv('treb-scale-control-2', container);
55
55
 
56
56
  this.input = DOM.Create('input', 'treb-scale-input', /* div */ container);
57
- const popup = DOM.CreateDiv('treb-slider-container', /* div */ container);
57
+ const popup = DOM.Div('treb-slider-container', /* div */ container);
58
58
 
59
59
  /*
60
60
  this.input.addEventListener('keyup', (event) => {