@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
package/dist/treb.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v27.5. Copyright 2018-2023 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v27.9. Copyright 2018-2023 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
2
2
 
3
3
  /**
4
4
  * add our tag to the map
@@ -316,7 +316,7 @@ export declare class EmbeddedSpreadsheet {
316
316
  * to support outside tooling by highlighting a list of arguments and
317
317
  * responding to selection.
318
318
  */
319
- ExternalEditor(editor?: Partial<ExternalEditorType>): void;
319
+ ExternalEditor(config?: Partial<ExternalEditorConfig>): void;
320
320
 
321
321
  /**
322
322
  * Use this function to batch multiple document changes. Essentially the
@@ -1055,7 +1055,7 @@ export interface SetRangeOptions {
1055
1055
  */
1056
1056
  r1c1?: boolean;
1057
1057
  }
1058
- export interface ExternalEditorType {
1058
+ export interface ExternalEditorConfig {
1059
1059
 
1060
1060
  /**
1061
1061
  * list of dependencies to highlight. we support undefined entries in
@@ -1074,6 +1074,20 @@ export interface ExternalEditorType {
1074
1074
  * practice.
1075
1075
  */
1076
1076
  update: ExternalEditorCallback;
1077
+
1078
+ /**
1079
+ * a list of nodes that will serve as editors. when you attach, we will do
1080
+ * an initial pass of context highlighting. we highlight on text changes
1081
+ * and insert references if you make a selection in the spreadsheet while
1082
+ * an editor is focused.
1083
+ */
1084
+ nodes: HTMLElement[];
1085
+
1086
+ /**
1087
+ * assume that we're editing a formula. does not require leading `=`.
1088
+ * defaults to `true` for historical reasons.
1089
+ */
1090
+ assume_formula?: boolean;
1077
1091
  }
1078
1092
  export type DependencyList = Array<IArea | ICellAddress | undefined>;
1079
1093
  export type ExternalEditorCallback = (selection?: string) => DependencyList | undefined;
@@ -1277,6 +1291,26 @@ export interface TableSortOptions {
1277
1291
  }
1278
1292
  export type TableSortType = 'text' | 'numeric' | 'auto';
1279
1293
 
1294
+ /**
1295
+ * options for the evaluate function
1296
+ */
1297
+ export interface EvaluateOptions {
1298
+
1299
+ /**
1300
+ * argument separator to use when parsing input. set this option to
1301
+ * use a consistent argument separator independent of current locale.
1302
+ */
1303
+ argument_separator?: ',' | ';';
1304
+
1305
+ /**
1306
+ * allow R1C1-style references. the Evaluate function cannot use
1307
+ * relative references (e.g. R[-1]C[0]), so those will always fail.
1308
+ * however it may be useful to use direct R1C1 references (e.g. R3C4),
1309
+ * so we optionally support that behind this flag.
1310
+ */
1311
+ r1c1?: boolean;
1312
+ }
1313
+
1280
1314
  /**
1281
1315
  * this is the document type used by TREB. it has a lot of small variations
1282
1316
  * for historical reasons and backwards compatibility. usually it's preferable
@@ -1807,23 +1841,3 @@ export interface ExportOptions {
1807
1841
  /** use number formats when exporting numbers */
1808
1842
  formatted?: boolean;
1809
1843
  }
1810
-
1811
- /**
1812
- * options for the evaluate function
1813
- */
1814
- export interface EvaluateOptions {
1815
-
1816
- /**
1817
- * argument separator to use when parsing input. set this option to
1818
- * use a consistent argument separator independent of current locale.
1819
- */
1820
- argument_separator?: ',' | ';';
1821
-
1822
- /**
1823
- * allow R1C1-style references. the Evaluate function cannot use
1824
- * relative references (e.g. R[-1]C[0]), so those will always fail.
1825
- * however it may be useful to use direct R1C1 references (e.g. R3C4),
1826
- * so we optionally support that behind this flag.
1827
- */
1828
- r1c1?: boolean;
1829
- }
@@ -0,0 +1,29 @@
1
+ conditional formatting TODO list
2
+
3
+ - import/export
4
+
5
+ - run through the command queue
6
+
7
+ - dragging selection knob should expand conditional formats
8
+ (if you have the whole thing selected). at the moment it
9
+ seems to be hard-copying the format, which is bad.
10
+
11
+ ---
12
+
13
+ done
14
+
15
+ - consolidate expression and cell-match types, in the sheet part,
16
+ handle arrays and single cells
17
+
18
+ note that these are now essentially the same thing, with a small
19
+ difference when we attach the conditional format to the graph. so
20
+ we could combine them. but, because there's a hard distinction in
21
+ XLSX, we will probably need to keep track of which is which.
22
+
23
+ - if you do the above you can remove the "applied" flag
24
+
25
+ - update range (and formula) based on insert/delete row/col
26
+ (+ sheet name change)
27
+
28
+ - use a gradient function
29
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "27.5.3",
3
+ "version": "27.9.0",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -15,9 +15,9 @@
15
15
  "@types/node": "^20.4.0",
16
16
  "@typescript-eslint/eslint-plugin": "^6.1.0",
17
17
  "@typescript-eslint/parser": "^6.1.0",
18
- "archiver": "^5.3.0",
18
+ "archiver": "^6.0.1",
19
19
  "cssnano": "^6.0.0",
20
- "esbuild": "^0.18.11",
20
+ "esbuild": "^0.19.2",
21
21
  "eslint": "^8.19.0",
22
22
  "fast-xml-parser": "^4.0.7",
23
23
  "html-minifier": "^4.0.0",
@@ -55,6 +55,13 @@ export interface IArea {
55
55
  end: ICellAddress;
56
56
  }
57
57
 
58
+ export interface PatchOptions {
59
+ before_column: number;
60
+ column_count: number;
61
+ before_row: number;
62
+ row_count: number;
63
+ }
64
+
58
65
  /**
59
66
  * type guard function
60
67
  * FIXME: is there a naming convention for these?
@@ -197,7 +204,181 @@ export class Area implements IArea { // }, IterableIterator<ICellAddress> {
197
204
  });
198
205
  }
199
206
 
207
+ /**
208
+ * adjust an area in response to an insert/delete operation.
209
+ * I noticed we were doing this in several places. moved here to unify.
210
+ *
211
+ * @param source - the starting area. we'll create a new object to return
212
+ * (we will not mutate in place)
213
+ */
214
+ public static PatchArea(source: IArea, options: PatchOptions): Area | false {
215
+
216
+ const { before_column, column_count, before_row, row_count } = options;
217
+
218
+ let area = new Area(source.start, source.end);
219
+ const sheet_id = source.start.sheet_id;
220
+
221
+ if (column_count && before_column <= area.end.column) {
222
+
223
+ /*
224
+ // (1) we are before the insert point, not affected
225
+
226
+ if (before_column > range.end.column) {
227
+ continue;
228
+ }
229
+ */
230
+
231
+ if (column_count > 0) {
232
+
233
+ // (2) it's an insert and we are past the insert point:
234
+ // increment [start] and [end] by [count]
235
+
236
+ if (before_column <= area.start.column) {
237
+ area.Shift(0, column_count);
238
+ }
239
+
240
+ // (3) it's an insert and we contain the insert point:
241
+ // increment [end] by [count]
242
+
243
+ else if (before_column > area.start.column && before_column <= area.end.column) {
244
+ area.ConsumeAddress({row: area.end.row, column: area.end.column + column_count});
245
+ }
246
+
247
+ else {
248
+ console.warn(`AA X case 1`, before_column, column_count, JSON.stringify(area));
249
+ }
250
+
251
+ }
252
+ else if (column_count < 0) {
253
+
254
+ // (4) it's a delete and we are past the delete point (before+count):
255
+ // decrement [start] and [end] by [count]
256
+
257
+ if (before_column - column_count <= area.start.column) {
258
+ area.Shift(0, column_count);
259
+ }
260
+
261
+ // (5) it's a delete and contains the entire range
262
+
263
+ else if (before_column <= area.start.column && before_column - column_count > area.end.column) {
264
+
265
+ // we can actually just return at this point
266
+ return false;
267
+
268
+ }
269
+
270
+ // (6) it's a delete and contains part of the range. clip the range.
271
+
272
+ else if (before_column <= area.start.column) {
273
+ const last_column = before_column - column_count - 1;
274
+ area = new Area({
275
+ row: area.start.row, column: last_column + 1 + column_count, sheet_id }, {
276
+ row: area.end.row, column: area.end.column + column_count });
277
+ }
278
+
279
+ else if (before_column <= area.end.column) {
280
+ const last_column = before_column - column_count - 1;
281
+
282
+ if (last_column >= area.end.column) {
283
+ area = new Area({
284
+ row: area.start.row, column: area.start.column, sheet_id }, {
285
+ row: area.end.row, column: before_column - 1 });
286
+ }
287
+ else {
288
+ area = new Area({
289
+ row: area.start.row, column: area.start.column, sheet_id }, {
290
+ row: area.end.row, column: area.start.column + area.columns + column_count - 1});
291
+ }
292
+
293
+ }
294
+
295
+ else {
296
+ console.warn(`AA X case 2`, before_column, column_count, JSON.stringify(area));
297
+ }
200
298
 
299
+ }
300
+ }
301
+
302
+ if (row_count && before_row <= area.end.row) {
303
+
304
+ /*
305
+ // (1) we are before the insert point, not affected
306
+
307
+ if (before_column > range.end.column) {
308
+ continue;
309
+ }
310
+ */
311
+
312
+ if (row_count > 0) {
313
+
314
+ // (2) it's an insert and we are past the insert point:
315
+ // increment [start] and [end] by [count]
316
+
317
+ if (before_row <= area.start.row) {
318
+ area.Shift(row_count, 0);
319
+ }
320
+
321
+ // (3) it's an insert and we contain the insert point:
322
+ // increment [end] by [count]
323
+
324
+ else if (before_row > area.start.row && before_row <= area.end.row) {
325
+ area.ConsumeAddress({row: area.end.row + row_count, column: area.end.column});
326
+ }
327
+
328
+ else {
329
+ console.warn(`AA X case 3`, before_row, row_count, JSON.stringify(area));
330
+ }
331
+
332
+ }
333
+ else if (row_count < 0) {
334
+
335
+ // (4) it's a delete and we are past the delete point (before+count):
336
+ // decrement [start] and [end] by [count]
337
+
338
+ if (before_row - row_count <= area.start.row) {
339
+ area.Shift(row_count, 0);
340
+ }
341
+
342
+ // (5) it's a delete and contains the entire range
343
+
344
+ else if (before_row <= area.start.row && before_row - row_count > area.end.row) {
345
+ return false;
346
+ }
347
+
348
+ // (6) it's a delete and contains part of the range. clip the range.
349
+
350
+ else if (before_row <= area.start.row) {
351
+ const last_row = before_row - row_count - 1;
352
+ area = new Area({
353
+ column: area.start.column, row: last_row + 1 + row_count, sheet_id }, {
354
+ column: area.end.column, row: area.end.row + row_count });
355
+ }
356
+
357
+ else if (before_row <= area.end.row) {
358
+ const last_row = before_row - row_count - 1;
359
+ if (last_row >= area.end.row) {
360
+ area = new Area({
361
+ column: area.start.column, row: area.start.row, sheet_id }, {
362
+ column: area.end.column, row: before_row - 1 });
363
+ }
364
+ else {
365
+ area = new Area({
366
+ column: area.start.column, row: area.start.row, sheet_id }, {
367
+ column: area.end.column, row: area.start.row + area.rows + row_count - 1 });
368
+ }
369
+
370
+ }
371
+
372
+ else {
373
+ console.warn(`AA X case 4`, before_row, row_count, JSON.stringify(area));
374
+ }
375
+
376
+ }
377
+ }
378
+
379
+ return area;
380
+
381
+ }
201
382
 
202
383
  /** accessor returns a _copy_ of the start address */
203
384
  public get start(): ICellAddress {
@@ -0,0 +1,21 @@
1
+
2
+ /**
3
+ * options for the evaluate function
4
+ */
5
+ export interface EvaluateOptions {
6
+
7
+ /**
8
+ * argument separator to use when parsing input. set this option to
9
+ * use a consistent argument separator independent of current locale.
10
+ */
11
+ argument_separator?: ','|';';
12
+
13
+ /**
14
+ * allow R1C1-style references. the Evaluate function cannot use
15
+ * relative references (e.g. R[-1]C[0]), so those will always fail.
16
+ * however it may be useful to use direct R1C1 references (e.g. R3C4),
17
+ * so we optionally support that behind this flag.
18
+ */
19
+ r1c1?: boolean;
20
+
21
+ }
@@ -0,0 +1,97 @@
1
+
2
+ import { Measurement } from 'treb-utils';
3
+ import { type Color } from './style';
4
+ import { type Theme, ThemeColor2 } from './theme';
5
+ import { ColorFunctions } from './color';
6
+
7
+ export interface GradientStop {
8
+ value: number;
9
+ color: Color;
10
+ }
11
+
12
+ export type ColorSpace = 'RGB'|'HSL';
13
+
14
+ export class Gradient {
15
+
16
+ public mapped: Array<GradientStop & { resolved: number[] }>;
17
+
18
+ constructor(stops: GradientStop[], theme: Theme, public color_space: ColorSpace = 'HSL') {
19
+
20
+ this.mapped = stops.map(stop => {
21
+
22
+ if (stop.value < 0 || stop.value > 1) {
23
+ throw new Error('invalid stop value');
24
+ }
25
+
26
+ const rgb = Measurement.MeasureColor(ThemeColor2(theme, stop.color));
27
+
28
+ let resolved: number[] = [];
29
+
30
+ if (color_space === 'HSL') {
31
+ const hsl = ColorFunctions.RGBToHSL(rgb[0], rgb[1], rgb[2]);
32
+ resolved = [hsl.h, hsl.s, hsl.l];
33
+ }
34
+ else {
35
+ resolved = [...rgb];
36
+ }
37
+
38
+ return {
39
+ ...stop, resolved,
40
+ };
41
+
42
+ });
43
+
44
+ this.mapped.sort((a, b) => a.value - b.value);
45
+
46
+ // FIXME: we should expand the gradient, but for now we'll just clamp
47
+
48
+ if (this.mapped[0].value > 0) {
49
+ this.mapped.unshift({
50
+ ...this.mapped[0], value: 0
51
+ });
52
+ }
53
+
54
+ if (this.mapped[this.mapped.length - 1].value < 1) {
55
+ this.mapped.push({
56
+ ...this.mapped[this.mapped.length - 1], value: 1
57
+ });
58
+ }
59
+
60
+ }
61
+
62
+ public RenderColor(values: number[]) {
63
+ if (this.color_space === 'RGB') {
64
+ return { text: `rgb(${values})` };
65
+ }
66
+ return { text: `hsl(${values[0]},${values[1] * 100}%,${values[2] * 100}%)` };
67
+ }
68
+
69
+ public Interpolate(value: number): Color {
70
+
71
+ value = Math.min(1, Math.max(0, value));
72
+ for (const [index, stop] of this.mapped.entries()) {
73
+
74
+ if (value === stop.value) {
75
+ return this.RenderColor(stop.resolved);
76
+ }
77
+ if (value < stop.value) {
78
+ const a = this.mapped[index - 1];
79
+ const b = stop;
80
+
81
+ const range = b.value - a.value; // FIXME: cache
82
+ const advance = value - a.value;
83
+
84
+ const values = [0,1,2].map(index => {
85
+ return a.resolved[index] + (b.resolved[index] - a.resolved[index]) / range * advance;
86
+ });
87
+
88
+ return this.RenderColor(values);
89
+
90
+ }
91
+ }
92
+
93
+ return { text: '' };
94
+
95
+ }
96
+
97
+ }
@@ -25,7 +25,7 @@ import type { IArea } from './area';
25
25
  import type { AnnotationLayout } from './layout';
26
26
  import type { DataValidation } from './cell';
27
27
  import type { Table } from './table';
28
- import type { AnnotationType } from 'treb-grid';
28
+ import type { AnnotationType, ConditionalFormat } from 'treb-grid';
29
29
 
30
30
  export interface CellParseResult {
31
31
  row: number,
@@ -58,6 +58,7 @@ export interface ImportedSheetData {
58
58
  column_widths: number[];
59
59
  row_heights: number[];
60
60
  styles: CellStyle[];
61
+ conditional_formats: ConditionalFormat[];
61
62
 
62
63
  // optional, for backcompat
63
64
  sheet_style?: number;
@@ -36,6 +36,8 @@ export * from './layout';
36
36
  export * from './render_text';
37
37
  export * from './api_types';
38
38
  export * from './table';
39
+ export * from './gradient';
40
+ export * from './evaluate-options';
39
41
 
40
42
  // import * as Style from './style';
41
43
  // export { Style };