@trebco/treb 27.7.6 → 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 (36) hide show
  1. package/dist/treb-spreadsheet.mjs +14 -14
  2. package/dist/treb.d.ts +21 -21
  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-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 +190 -28
  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 +352 -41
  23. package/treb-export/src/export-worker/export-worker.ts +0 -13
  24. package/treb-export/src/export2.ts +187 -2
  25. package/treb-export/src/import2.ts +169 -4
  26. package/treb-export/src/workbook-style2.ts +56 -8
  27. package/treb-export/src/workbook2.ts +10 -1
  28. package/treb-grid/src/index.ts +2 -1
  29. package/treb-grid/src/layout/base_layout.ts +23 -15
  30. package/treb-grid/src/render/tile_renderer.ts +2 -1
  31. package/treb-grid/src/types/conditional_format.ts +168 -0
  32. package/treb-grid/src/types/grid.ts +5 -6
  33. package/treb-grid/src/types/grid_base.ts +186 -33
  34. package/treb-grid/src/types/sheet.ts +330 -26
  35. package/treb-grid/src/types/sheet_types.ts +4 -0
  36. /package/{README-shadow-DOM.md → notes/shadow-DOM.md} +0 -0
@@ -42,6 +42,7 @@ import type { SerializeOptions } from './serialize_options';
42
42
  import type { GridSelection } from './grid_selection';
43
43
  import { CreateSelection } from './grid_selection';
44
44
  import { Annotation } from './annotation';
45
+ import type { ConditionalFormatList } from './conditional_format';
45
46
 
46
47
  // --- constants --------------------------------------------------------------
47
48
 
@@ -147,10 +148,21 @@ export class Sheet {
147
148
 
148
149
  protected _image: HTMLImageElement|undefined = undefined;
149
150
 
151
+ /**
152
+ * set this flag when we need to update conditional formats even
153
+ * if they are not dirty (generally when one is deleted)
154
+ */
155
+ public flush_conditional_formats = false;
156
+
150
157
  public get image(): HTMLImageElement|undefined {
151
158
  return this._image;
152
159
  }
153
160
 
161
+ /**
162
+ * @internal
163
+ */
164
+ public conditional_formats: ConditionalFormatList = [];
165
+
154
166
  /**
155
167
  * @internal
156
168
  *
@@ -228,6 +240,21 @@ export class Sheet {
228
240
 
229
241
  private cell_style: CellStyle[][] = [];
230
242
 
243
+ /**
244
+ * applied conditional formats are stored them in this array;
245
+ * they will be stacked on top of cell style when rendering.
246
+ * conditional formats have top priority. [FIXME: what about tables?]
247
+ */
248
+ private conditional_format_cache: CellStyle[][][] = [];
249
+
250
+ /**
251
+ * this is a list of cells we formatted on the last pass, so we can
252
+ * compare when applying conditional formats .
253
+ *
254
+ * update: using areas
255
+ */
256
+ private conditional_format_checklist: IArea[] = [];
257
+
231
258
  // --- accessors ------------------------------------------------------------
232
259
 
233
260
  // public get column_header_count() { return this.column_header_count_; }
@@ -352,6 +379,10 @@ export class Sheet {
352
379
  sheet.default_row_height = source.default_row_height;
353
380
  }
354
381
 
382
+ if (source.conditional_formats) {
383
+ sheet.conditional_formats = source.conditional_formats;
384
+ }
385
+
355
386
  // persist ID, name
356
387
 
357
388
  if (source.id) {
@@ -1010,7 +1041,7 @@ export class Sheet {
1010
1041
 
1011
1042
  // testing
1012
1043
  // const underlying = this.CompositeStyleForCell(address, false);
1013
- const underlying = this.CompositeStyleForCell(address, false, false);
1044
+ const underlying = this.CompositeStyleForCell(address, false, false, undefined, false);
1014
1045
 
1015
1046
  const merged = Style.Composite([
1016
1047
  this.default_style_properties,
@@ -1048,7 +1079,8 @@ export class Sheet {
1048
1079
  this.cell_style[column][row] = composite; // merged;
1049
1080
 
1050
1081
  // targeted flush
1051
- this.CellData(address).FlushStyle();
1082
+ // this.CellData(address).FlushStyle();
1083
+ this.BleedFlush({start: address, end: address});
1052
1084
 
1053
1085
  }
1054
1086
 
@@ -1332,7 +1364,7 @@ export class Sheet {
1332
1364
  * @param address
1333
1365
  */
1334
1366
  public GetCopyStyle(address: ICellAddress): CellStyle {
1335
- return this.CompositeStyleForCell(address, true, false);
1367
+ return this.CompositeStyleForCell(address, true, false, undefined, false);
1336
1368
  }
1337
1369
 
1338
1370
  /**
@@ -2069,6 +2101,8 @@ export class Sheet {
2069
2101
  * this is a new GetCellStyle function, used for external access
2070
2102
  * to style (for API access). there was an old GetCellStyle function
2071
2103
  * for rendering, but that's been removed (control+F for info).
2104
+ *
2105
+ * Q: does this include conditional formatting? (...)
2072
2106
  */
2073
2107
  public GetCellStyle(area: ICellAddress|IArea, apply_theme = false): CellStyle|CellStyle[][] {
2074
2108
 
@@ -2558,6 +2592,13 @@ export class Sheet {
2558
2592
 
2559
2593
  const cell_styles = this.CompressCellStyles(cell_reference_map);
2560
2594
 
2595
+ // if we serialize this when it has Area values (instead of IArea) it
2596
+ // will export incorrectly. is that an issue anywhere else? (...)
2597
+
2598
+ const conditional_formats = this.conditional_formats.length ?
2599
+ JSON.parse(JSON.stringify(this.conditional_formats.map(format => ({...format, internal: undefined })))) :
2600
+ undefined;
2601
+
2561
2602
  const result: SerializedSheet = {
2562
2603
 
2563
2604
  // not used atm, but in the event we need to gate
@@ -2580,6 +2621,8 @@ export class Sheet {
2580
2621
  row_style,
2581
2622
  column_style,
2582
2623
 
2624
+ conditional_formats,
2625
+
2583
2626
  row_pattern: row_pattern.length ? row_pattern : undefined,
2584
2627
 
2585
2628
  // why are these serialized? (...) export!
@@ -2763,6 +2806,10 @@ export class Sheet {
2763
2806
  this.annotations.push(new Annotation(annotation));
2764
2807
  }
2765
2808
 
2809
+ for (const format of data.conditional_formats || []) {
2810
+ this.conditional_formats.push(format);
2811
+ }
2812
+
2766
2813
  if (data.hidden) {
2767
2814
  this.visible = false;
2768
2815
  }
@@ -2976,28 +3023,6 @@ export class Sheet {
2976
3023
 
2977
3024
  }
2978
3025
 
2979
- /* *
2980
- * styles are applied as a stack,
2981
- *
2982
- * sheet
2983
- * row pattern
2984
- * row
2985
- * column
2986
- * cell
2987
- *
2988
- * there are some cases where we wind up with overridden but matching
2989
- * styles that are duplicative. they can be removed, although it's not
2990
- * necessarily useful to do it in real time -- we can do it on load/save
2991
- * or perhaps on idle.
2992
- *
2993
- * /
2994
- private FlattenStyles() {
2995
-
2996
- this.CompositeStyleForCell
2997
-
2998
- }
2999
- */
3000
-
3001
3026
  /**
3002
3027
  * updates column properties. reverse-overrides cells (@see UpdateSheetStyle).
3003
3028
  */
@@ -3049,6 +3074,276 @@ export class Sheet {
3049
3074
 
3050
3075
  }
3051
3076
 
3077
+ /* *
3078
+ * flush the cache and the checklist. flush cell styles at the same
3079
+ * time. this should be called when adding/removing a conditional format.
3080
+ * optionally apply active formats again.
3081
+ *
3082
+ * is this actually necessary? what's the use case? (...)
3083
+ *
3084
+ * /
3085
+ public FlushConditionalFormats(reapply = false) {
3086
+
3087
+ for (const [row, column] of this.conditional_format_checklist) {
3088
+ this.CellData({row, column}).FlushStyle();
3089
+ }
3090
+
3091
+ this.conditional_format_checklist = [];
3092
+ this.conditional_format_cache = [];
3093
+
3094
+ if (reapply) {
3095
+ this.ApplyConditionalFormats();
3096
+ }
3097
+
3098
+ }
3099
+ */
3100
+
3101
+ public BleedFlush(area: IArea) {
3102
+
3103
+ let rows = [Math.max(0, area.start.row - 1), area.end.row + 1];
3104
+ let cols = [Math.max(0, area.start.column - 1), area.end.column + 1];
3105
+
3106
+ for (let row = rows[0]; row <= rows[1]; row++) {
3107
+ for (let column = cols[0]; column <= cols[1]; column++) {
3108
+ // const cell = this.cells.EnsureCell({row, column});
3109
+ this.cells.GetCell({row, column}, false)?.FlushStyle();
3110
+ }
3111
+ }
3112
+
3113
+ }
3114
+
3115
+ public FlushConditionalFormats() {
3116
+ this.flush_conditional_formats = true;
3117
+ }
3118
+
3119
+ /**
3120
+ * this version combines flushing the cache with building it, using
3121
+ * the application flag in the format objects.
3122
+ *
3123
+ * this function was set up to support comparing the two lists and
3124
+ * only flushing style if necessary; but that turns out to be so
3125
+ * much additional work that I'm not sure it's preferable to just
3126
+ * repaint. need to test.
3127
+ *
3128
+ * ...we're also probably looping unecessarily. since we're using
3129
+ * those leaf nodes we can probably check if the state changed, and
3130
+ * it not, skip the loop pass. I think we'd need to identify or map
3131
+ * the applications though (meaning use a stack that matches the list
3132
+ * of formats). or you could even recheck everything if one of them
3133
+ * changed, you'd still probably save a lot in cases where nothing
3134
+ * changed.
3135
+ *
3136
+ */
3137
+ public ApplyConditionalFormats() {
3138
+
3139
+ // we're not doing any pruning at the moment, so this is doing
3140
+ // a lot of unecessary looping -- we could start with one big
3141
+ // global check
3142
+
3143
+ // ...we need to account for the case where a format is removed,
3144
+ // in that case we will need to update. flag?
3145
+
3146
+ let updated = this.flush_conditional_formats; // maybe required
3147
+
3148
+ for (const format of this.conditional_formats) {
3149
+ if (format.internal?.vertex?.updated) {
3150
+ updated = true;
3151
+ break;
3152
+ }
3153
+ }
3154
+
3155
+ if (!updated) {
3156
+
3157
+ // console.info('no updates');
3158
+
3159
+ // that should save 90% of the calculation, we'll still do
3160
+ // unecessary work but it's a step in the right direction.
3161
+
3162
+ // note that this flag doesn't necessarily indicate anything
3163
+ // has changed -- it will get set if you do a global recalc,
3164
+ // because that marks everything as dirty. still a good step
3165
+ // though.
3166
+
3167
+ return;
3168
+ }
3169
+
3170
+ this.flush_conditional_formats = false; // unset
3171
+
3172
+ const temp: CellStyle[][][] = [];
3173
+ const checklist: IArea[] = [...this.conditional_format_checklist];
3174
+
3175
+ this.conditional_format_checklist = []; // flush
3176
+
3177
+ for (const format of this.conditional_formats) {
3178
+
3179
+ if (format.internal?.vertex?.updated) {
3180
+
3181
+ // console.info('updated');
3182
+
3183
+ format.internal.vertex.updated = false;
3184
+ }
3185
+
3186
+ // NOTE: if you go backwards, then you can short-circuit if a format
3187
+ // is already set. except then if you want to support "stop" rules,
3188
+ // that won't work.
3189
+ //
3190
+ // although you might still want to go backwards as it's easier to
3191
+ // apply stop rules in reverse (why? because if you are going backwards,
3192
+ // you can just drop everything on the stack when you see a
3193
+ // stop rule. if you go forwards, you need some sort of indicator
3194
+ // or flag).
3195
+
3196
+ if (format.type === 'gradient') {
3197
+ const area = JSON.parse(JSON.stringify(format.area));
3198
+ const result = format.internal?.vertex?.result;
3199
+
3200
+ if (result && format.internal?.gradient) {
3201
+ const property: 'fill'|'text' = format.property ?? 'fill';
3202
+
3203
+ if (result.type === ValueType.array) {
3204
+ for (let row = area.start.row; row <= area.end.row; row++) {
3205
+ for (let column = area.start.column; column <= area.end.column; column++) {
3206
+ const value = result.value[column - area.start.column][row - area.start.row];
3207
+ if (value.type === ValueType.number) {
3208
+ if (!temp[row]) { temp[row] = []; }
3209
+ if (!temp[row][column] ) { temp[row][column] = []; }
3210
+ const color = format.internal.gradient.Interpolate(value.value);
3211
+ temp[row][column].push({ [property]: color});
3212
+ }
3213
+ }
3214
+ }
3215
+ }
3216
+ else if (result.type === ValueType.number) {
3217
+ const color = format.internal.gradient.Interpolate(result.value);
3218
+ for (let row = area.start.row; row <= area.end.row; row++) {
3219
+ if (!temp[row]) { temp[row] = []; }
3220
+ for (let column = area.start.column; column <= area.end.column; column++) {
3221
+ if (!temp[row][column] ) { temp[row][column] = []; }
3222
+ temp[row][column].push({ [property]: color});
3223
+ }
3224
+ }
3225
+ }
3226
+
3227
+ checklist.push(area);
3228
+ this.conditional_format_checklist.push(area);
3229
+
3230
+ }
3231
+ }
3232
+ else {
3233
+
3234
+ // handle types expression, cell-match and duplicate-values
3235
+
3236
+ const area = JSON.parse(JSON.stringify(format.area));
3237
+ const result = format.internal?.vertex?.result;
3238
+
3239
+ if (result) {
3240
+
3241
+ if (result.type === ValueType.array) {
3242
+ for (let row = area.start.row; row <= area.end.row; row++) {
3243
+ for (let column = area.start.column; column <= area.end.column; column++) {
3244
+ const value = result.value[column - area.start.column][row - area.start.row];
3245
+ if ((value.type === ValueType.boolean || value.type === ValueType.number) && !!value.value) {
3246
+ if (!temp[row]) { temp[row] = []; }
3247
+ if (!temp[row][column] ) { temp[row][column] = []; }
3248
+ temp[row][column].push(format.style);
3249
+ }
3250
+ }
3251
+ }
3252
+ }
3253
+ else {
3254
+ if (result.type === ValueType.boolean || result.type === ValueType.number) {
3255
+ if(!!result.value) {
3256
+ for (let row = area.start.row; row <= area.end.row; row++) {
3257
+ if (!temp[row]) { temp[row] = []; }
3258
+ for (let column = area.start.column; column <= area.end.column; column++) {
3259
+ if (!temp[row][column] ) { temp[row][column] = []; }
3260
+ temp[row][column].push(format.style);
3261
+ }
3262
+ }
3263
+ }
3264
+ }
3265
+ }
3266
+
3267
+ checklist.push(area);
3268
+ this.conditional_format_checklist.push(area);
3269
+
3270
+ }
3271
+
3272
+ }
3273
+
3274
+ }
3275
+
3276
+ for (const area of checklist) {
3277
+ this.BleedFlush(area);
3278
+ }
3279
+
3280
+ this.conditional_format_cache = temp;
3281
+
3282
+ }
3283
+
3284
+ /*
3285
+ public ApplyConditionalFormats() {
3286
+
3287
+ this.FlushConditionalFormatCache();
3288
+
3289
+ for (const entry of this.conditional_formats) {
3290
+
3291
+ console.info({entry});
3292
+
3293
+ if (entry.applied) {
3294
+ this.ApplyConditionalFormatCache(entry);
3295
+ }
3296
+ }
3297
+
3298
+ }
3299
+
3300
+ public FlushConditionalFormatCache() {
3301
+
3302
+ // FIXME: need to flush any styles that are set, unless they match;
3303
+ // perhaps we should use an alternate cache so we can compare? TODO/FIXME
3304
+
3305
+ for (const [row, row_data] of this.conditional_format_cache.entries()) {
3306
+ if (row_data) {
3307
+ for (const [column, column_data] of row_data.entries()) {
3308
+ if (column_data) {
3309
+
3310
+ this.CellData({row, column}).FlushStyle();
3311
+
3312
+ }
3313
+ }
3314
+ }
3315
+ }
3316
+
3317
+ this.conditional_format_cache = [];
3318
+
3319
+ }
3320
+
3321
+ public ApplyConditionalFormatCache(format: ConditionalFormat) {
3322
+
3323
+ for (let row = format.area.start.row; row <= format.area.end.row; row++ ) {
3324
+ for (let column = format.area.start.column; column <= format.area.end.column; column++ ) {
3325
+ if (!this.conditional_format_cache[row]) {
3326
+ this.conditional_format_cache[row] = [];
3327
+ }
3328
+ if (!this.conditional_format_cache[row][column]) {
3329
+ this.conditional_format_cache[row][column] = [];
3330
+ }
3331
+ this.conditional_format_cache[row][column].push(format.style);
3332
+ this.CellData({row, column}).FlushStyle();
3333
+
3334
+ }
3335
+ }
3336
+
3337
+ }
3338
+ */
3339
+
3340
+ private ConditionalFormatForCell(address: ICellAddress): CellStyle[] {
3341
+ if (this.conditional_format_cache[address.row]) {
3342
+ return this.conditional_format_cache[address.row][address.column] || [];
3343
+ }
3344
+ return [];
3345
+ }
3346
+
3052
3347
  /**
3053
3348
  * generates the composite style for the given cell. this
3054
3349
  * should only be used to generate a cache of styles (Q: really? PERF?)
@@ -3057,7 +3352,12 @@ export class Sheet {
3057
3352
  * want to check what happens if the cell style is not applied; if nothing
3058
3353
  * happens, then we can drop the cell style (or the property in the style).
3059
3354
  */
3060
- private CompositeStyleForCell(address: ICellAddress, apply_cell_style = true, apply_row_pattern = true, apply_default = true) {
3355
+ private CompositeStyleForCell(
3356
+ address: ICellAddress,
3357
+ apply_cell_style = true,
3358
+ apply_row_pattern = true,
3359
+ apply_default = true,
3360
+ apply_conditional = true, ) {
3061
3361
 
3062
3362
  const { row, column } = address;
3063
3363
  const stack: CellStyle[] = [];
@@ -3085,6 +3385,10 @@ export class Sheet {
3085
3385
  stack.push(this.cell_style[column][row]);
3086
3386
  }
3087
3387
 
3388
+ if (apply_conditional) {
3389
+ stack.push(...this.ConditionalFormatForCell(address));
3390
+ }
3391
+
3088
3392
  return Style.Composite(stack);
3089
3393
  }
3090
3394
 
@@ -22,6 +22,7 @@
22
22
  import type { IArea, SerializedCellData, Style, CellStyle } from 'treb-base-types';
23
23
  import type { Annotation, AnnotationData } from './annotation';
24
24
  import type { GridSelection, SerializedGridSelection } from './grid_selection';
25
+ import type { ConditionalFormatList } from './conditional_format';
25
26
 
26
27
  export interface UpdateHints {
27
28
  data?: boolean;
@@ -68,6 +69,9 @@ export interface SerializedSheet {
68
69
  */
69
70
  cell_styles: CellStyleRecord[]; // Array<{row: number; column: number; ref: number, rows?: number}>;
70
71
 
72
+ /** @internal */
73
+ conditional_formats?: ConditionalFormatList;
74
+
71
75
  /**
72
76
  * @deprecated use `styles` instead
73
77
  */
File without changes