@trebco/treb 29.3.4 → 29.5.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 (44) hide show
  1. package/dist/treb-spreadsheet-light.mjs +12 -12
  2. package/dist/treb-spreadsheet.mjs +12 -12
  3. package/dist/treb.d.ts +36 -41
  4. package/package.json +1 -1
  5. package/treb-base-types/src/area.ts +7 -0
  6. package/treb-base-types/src/cell.ts +2 -46
  7. package/treb-base-types/src/cells.ts +14 -8
  8. package/treb-base-types/src/gradient.ts +2 -2
  9. package/treb-base-types/src/import.ts +2 -2
  10. package/treb-base-types/src/style.ts +79 -6
  11. package/treb-base-types/src/theme.ts +24 -15
  12. package/treb-calculator/src/calculator.ts +22 -12
  13. package/treb-calculator/src/dag/graph.ts +12 -3
  14. package/treb-calculator/src/expression-calculator.ts +66 -74
  15. package/treb-calculator/src/functions/base-functions.ts +2 -2
  16. package/treb-calculator/src/functions/sparkline.ts +2 -2
  17. package/treb-calculator/src/functions/statistics-functions.ts +31 -1
  18. package/treb-data-model/src/data-validation.ts +44 -0
  19. package/treb-data-model/src/data_model.ts +11 -7
  20. package/treb-data-model/src/index.ts +1 -1
  21. package/treb-data-model/src/named.ts +35 -10
  22. package/treb-data-model/src/sheet.ts +75 -15
  23. package/treb-data-model/src/sheet_types.ts +4 -0
  24. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +7 -3
  25. package/treb-embed/src/embedded-spreadsheet.ts +50 -28
  26. package/treb-embed/src/progress-dialog.ts +4 -1
  27. package/treb-embed/src/types.ts +9 -0
  28. package/treb-export/src/drawing2/chart2.ts +20 -38
  29. package/treb-export/src/drawing2/drawing2.ts +2 -107
  30. package/treb-export/src/export-worker/export-worker.ts +1 -1
  31. package/treb-export/src/{export2.ts → export.ts} +439 -628
  32. package/treb-export/src/import2.ts +63 -26
  33. package/treb-export/src/workbook-style2.ts +16 -14
  34. package/treb-export/src/workbook2.ts +2 -18
  35. package/treb-export/src/xml-utils.ts +50 -2
  36. package/treb-export/src/zip-wrapper.ts +1 -1
  37. package/treb-grid/src/editors/overlay_editor.ts +3 -3
  38. package/treb-grid/src/layout/base_layout.ts +5 -14
  39. package/treb-grid/src/render/tile_renderer.ts +49 -48
  40. package/treb-grid/src/types/grid.ts +164 -26
  41. package/treb-grid/src/types/grid_base.ts +93 -17
  42. package/treb-grid/src/types/grid_command.ts +2 -1
  43. package/treb-parser/src/parser-types.ts +10 -0
  44. package/treb-parser/src/parser.ts +55 -17
@@ -25,7 +25,7 @@ import type { ICellAddress,
25
25
  CellStyle,
26
26
  Theme,
27
27
  HorizontalAlign} from 'treb-base-types';
28
- import { TextPartFlag, Style, ValueType, Area, Rectangle, ThemeColor, ThemeColor2 } from 'treb-base-types';
28
+ import { TextPartFlag, Style, ValueType, Area, Rectangle, ResolveThemeColor, IsDefinedColor } from 'treb-base-types';
29
29
 
30
30
  import type { Tile } from '../types/tile';
31
31
  import { FontMetricsCache as FontMetricsCache2 } from '../util/fontmetrics2';
@@ -307,7 +307,7 @@ export class TileRenderer {
307
307
  }
308
308
 
309
309
  context.setTransform(scale, 0, 0, scale, 0, 0);
310
- context.fillStyle = this.theme.headers?.fill ? ThemeColor2(this.theme, this.theme.headers.fill) : '';
310
+ context.fillStyle = this.theme.headers?.fill ? ResolveThemeColor(this.theme, this.theme.headers.fill) : '';
311
311
 
312
312
  context.fillRect(0, 0, x, header_size.y);
313
313
  context.fillRect(0, 0, header_size.x, y);
@@ -354,7 +354,7 @@ export class TileRenderer {
354
354
  context.textBaseline = 'middle';
355
355
  context.font = Style.Font(this.theme.headers||{}, this.layout.scale);
356
356
 
357
- context.fillStyle = ThemeColor2(this.theme, this.theme.headers?.text);
357
+ context.fillStyle = ResolveThemeColor(this.theme, this.theme.headers?.text);
358
358
 
359
359
  if (this.view.active_sheet.freeze.rows && this.layout.header_offset.x > 1) {
360
360
 
@@ -407,7 +407,7 @@ export class TileRenderer {
407
407
  return;
408
408
  }
409
409
 
410
- context.fillStyle = ThemeColor2(this.theme, this.theme.headers?.text, 0);
410
+ context.fillStyle = ResolveThemeColor(this.theme, this.theme.headers?.text, 0);
411
411
 
412
412
  context.beginPath();
413
413
 
@@ -434,7 +434,7 @@ export class TileRenderer {
434
434
  return;
435
435
  }
436
436
 
437
- context.fillStyle = ThemeColor2(this.theme, this.theme.headers?.text, 0);
437
+ context.fillStyle = ResolveThemeColor(this.theme, this.theme.headers?.text, 0);
438
438
 
439
439
  context.beginPath();
440
440
 
@@ -473,7 +473,7 @@ export class TileRenderer {
473
473
  context.textBaseline = 'middle';
474
474
  context.font = Style.Font(this.theme.headers||{}, this.layout.scale);
475
475
 
476
- context.fillStyle = this.theme.headers?.fill ? ThemeColor2(this.theme, this.theme.headers.fill) : '';
476
+ context.fillStyle = this.theme.headers?.fill ? ResolveThemeColor(this.theme, this.theme.headers.fill) : '';
477
477
  context.fillRect(0, 0, tile.logical_size.width, this.layout.header_offset.y);
478
478
 
479
479
  // context.strokeStyle = this.theme.grid_color || '';
@@ -507,7 +507,7 @@ export class TileRenderer {
507
507
 
508
508
  const context = tile.getContext('2d', { alpha: false });
509
509
  if (!context) continue;
510
- context.fillStyle = this.theme.headers?.fill ? ThemeColor2(this.theme, this.theme.headers.fill) : '';
510
+ context.fillStyle = this.theme.headers?.fill ? ResolveThemeColor(this.theme, this.theme.headers.fill) : '';
511
511
 
512
512
  context.setTransform(scale, 0, 0, scale, 0, 0);
513
513
 
@@ -1065,7 +1065,7 @@ export class TileRenderer {
1065
1065
  protected ResolveColors(style: CellStyle): CellStyle {
1066
1066
 
1067
1067
  const resolved = {...style};
1068
- resolved.text = { text: ThemeColor2(this.theme, style.text, 1) };
1068
+ resolved.text = { text: ResolveThemeColor(this.theme, style.text, 1) };
1069
1069
 
1070
1070
  // TODO: other colors
1071
1071
 
@@ -1126,7 +1126,7 @@ export class TileRenderer {
1126
1126
 
1127
1127
  // paint top background
1128
1128
 
1129
- let color = ThemeColor2(this.theme, numpad[8].fill);
1129
+ let color = ResolveThemeColor(this.theme, numpad[8].fill);
1130
1130
  if (color) {
1131
1131
  context.fillStyle = color
1132
1132
  context.fillRect(left + 0, top - 1, width, 1);
@@ -1134,7 +1134,7 @@ export class TileRenderer {
1134
1134
 
1135
1135
  // paint left background
1136
1136
 
1137
- color = ThemeColor2(this.theme, numpad[4].fill);
1137
+ color = ResolveThemeColor(this.theme, numpad[4].fill);
1138
1138
  if (color) {
1139
1139
  context.fillStyle = color
1140
1140
  context.fillRect(left - 1, top, 1, height);
@@ -1142,7 +1142,7 @@ export class TileRenderer {
1142
1142
 
1143
1143
  // paint our background. note this one goes up, left
1144
1144
 
1145
- color = ThemeColor2(this.theme, style.fill);
1145
+ color = ResolveThemeColor(this.theme, style.fill);
1146
1146
  if (color) {
1147
1147
  context.fillStyle = color;
1148
1148
  context.fillRect(left - 1, top - 1, width + 1, height + 1);
@@ -1150,7 +1150,7 @@ export class TileRenderer {
1150
1150
 
1151
1151
  // fill of cell to the right
1152
1152
 
1153
- color = ThemeColor2(this.theme, numpad[6].fill);
1153
+ color = ResolveThemeColor(this.theme, numpad[6].fill);
1154
1154
  if (color) {
1155
1155
  context.fillStyle = color;
1156
1156
  context.fillRect(left + width - 1, top - 1, 1, height + 1);
@@ -1159,7 +1159,7 @@ export class TileRenderer {
1159
1159
 
1160
1160
  // fill of cell underneath
1161
1161
 
1162
- color = ThemeColor2(this.theme, numpad[2].fill);
1162
+ color = ResolveThemeColor(this.theme, numpad[2].fill);
1163
1163
  if (color) {
1164
1164
  context.fillStyle = color;
1165
1165
  context.fillRect(left - 1, top + height - 1, width + 1, 1);
@@ -1168,54 +1168,54 @@ export class TileRenderer {
1168
1168
  // --- corner borders ------------------------------------------------------
1169
1169
 
1170
1170
  if (numpad[6].border_top && !numpad[6].border_left) {
1171
- context.fillStyle = ThemeColor2(this.theme, numpad[6].border_top_fill, 1);
1171
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[6].border_top_fill, 1);
1172
1172
  context.fillRect(left + width - 1, top - 2 + numpad[6].border_top, 1, 1);
1173
1173
  }
1174
1174
  if (numpad[9].border_left) {
1175
- context.fillStyle = ThemeColor2(this.theme, numpad[9].border_left_fill, 1);
1175
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[9].border_left_fill, 1);
1176
1176
  context.fillRect(left + width - 1, top - 1, 1, 1);
1177
1177
  }
1178
1178
  if (numpad[9].border_bottom) {
1179
- context.fillStyle = ThemeColor2(this.theme, numpad[9].border_bottom_fill, 1);
1179
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[9].border_bottom_fill, 1);
1180
1180
  context.fillRect(left + width - 1, top - 2 + numpad[9].border_bottom, 1, 1);
1181
1181
  }
1182
1182
 
1183
1183
  if (numpad[4].border_top && !numpad[4].border_right) {
1184
- context.fillStyle = ThemeColor2(this.theme, numpad[4].border_right_fill, 1);
1184
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[4].border_right_fill, 1);
1185
1185
  context.fillRect(left - 1, top - 2 + numpad[4].border_top, 1, 1);
1186
1186
  }
1187
1187
  if (numpad[7].border_right) {
1188
- context.fillStyle = ThemeColor2(this.theme, numpad[7].border_right_fill, 1);
1188
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[7].border_right_fill, 1);
1189
1189
  context.fillRect(left - 1, top - 1, 1, 1);
1190
1190
  }
1191
1191
  if (numpad[7].border_bottom) {
1192
- context.fillStyle = ThemeColor2(this.theme, numpad[7].border_bottom_fill, 1);
1192
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[7].border_bottom_fill, 1);
1193
1193
  context.fillRect(left - 1, top - 2 + numpad[7].border_bottom, 1, 1);
1194
1194
  }
1195
1195
 
1196
1196
  if (numpad[6].border_bottom && !numpad[6].border_left) {
1197
- context.fillStyle = ThemeColor2(this.theme, numpad[6].border_bottom_fill, 1);
1197
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[6].border_bottom_fill, 1);
1198
1198
  context.fillRect(left + width - 1, top + height - numpad[6].border_bottom, 1, 1);
1199
1199
  }
1200
1200
  if (numpad[3].border_left) {
1201
- context.fillStyle = ThemeColor2(this.theme, numpad[3].border_left_fill, 1);
1201
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[3].border_left_fill, 1);
1202
1202
  context.fillRect(left + width - 1, top + height - 1, 1, 1);
1203
1203
  }
1204
1204
  if (numpad[3].border_top) {
1205
- context.fillStyle = ThemeColor2(this.theme, numpad[3].border_top_fill, 1);
1205
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[3].border_top_fill, 1);
1206
1206
  context.fillRect(left + width - 1, top + height - numpad[3].border_top, 1, 1);
1207
1207
  }
1208
1208
 
1209
1209
  if (numpad[4].border_bottom && !numpad[4].border_right) {
1210
- context.fillStyle = ThemeColor2(this.theme, numpad[4].border_bottom_fill, 1);
1210
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[4].border_bottom_fill, 1);
1211
1211
  context.fillRect(left - 1, top + height - numpad[4].border_bottom, 1, 1);
1212
1212
  }
1213
1213
  if (numpad[1].border_right) {
1214
- context.fillStyle = ThemeColor2(this.theme, numpad[1].border_right_fill, 1);
1214
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[1].border_right_fill, 1);
1215
1215
  context.fillRect(left - 1, top + height - 1, 1, 1);
1216
1216
  }
1217
1217
  if (numpad[1].border_top) {
1218
- context.fillStyle = ThemeColor2(this.theme, numpad[1].border_top_fill, 1);
1218
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[1].border_top_fill, 1);
1219
1219
  context.fillRect(left - 1, top + height - numpad[1].border_top, 1, 1);
1220
1220
  }
1221
1221
 
@@ -1224,12 +1224,12 @@ export class TileRenderer {
1224
1224
  // paint top border
1225
1225
 
1226
1226
  if (numpad[8].border_bottom) {
1227
- context.fillStyle = ThemeColor2(this.theme, numpad[8].border_bottom_fill, 1);
1227
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[8].border_bottom_fill, 1);
1228
1228
  if (numpad[8].border_bottom === 2) {
1229
1229
  context.fillRect(left - 1, top - 2, width + 1, 1);
1230
1230
  context.fillRect(left - 1, top - 0, width + 1, 1);
1231
- context.fillStyle = ThemeColor2(this.theme, numpad[8].fill)
1232
- || ThemeColor(this.theme, this.theme.grid_cell?.fill) || '#fff';
1231
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[8].fill)
1232
+ || ResolveThemeColor(this.theme, this.theme.grid_cell?.fill, 0) || '#fff';
1233
1233
  context.fillRect(left - 1, top - 1, width + 1, 1);
1234
1234
  }
1235
1235
  else {
@@ -1240,26 +1240,26 @@ export class TileRenderer {
1240
1240
  // paint left border
1241
1241
 
1242
1242
  if (numpad[4].border_right) {
1243
- context.fillStyle = ThemeColor2(this.theme, numpad[4].border_right_fill, 1);
1243
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[4].border_right_fill, 1);
1244
1244
  context.fillRect(left - 1, top - 1, 1, height + 1);
1245
1245
  }
1246
1246
 
1247
1247
  // paint right border?
1248
1248
 
1249
1249
  if (numpad[6].border_left) {
1250
- context.fillStyle = ThemeColor2(this.theme, numpad[6].border_left_fill, 1);
1250
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[6].border_left_fill, 1);
1251
1251
  context.fillRect(left + width - 1, top - 1, 1, height + 1);
1252
1252
  }
1253
1253
 
1254
1254
  // bottom? (...)
1255
1255
 
1256
1256
  if (numpad[2].border_top) {
1257
- context.fillStyle = ThemeColor2(this.theme, numpad[2].border_top_fill, 1);
1257
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[2].border_top_fill, 1);
1258
1258
  if (numpad[2].border_top === 2) {
1259
1259
  context.fillRect(left - 1, top + height - 2, width + 1, 1);
1260
1260
  context.fillRect(left - 1, top + height - 0, width + 1, 1);
1261
- context.fillStyle = ThemeColor2(this.theme, numpad[2].fill)
1262
- || ThemeColor(this.theme, this.theme.grid_cell?.fill) || '#fff';
1261
+ context.fillStyle = ResolveThemeColor(this.theme, numpad[2].fill)
1262
+ || ResolveThemeColor(this.theme, this.theme.grid_cell?.fill, 0) || '#fff';
1263
1263
  context.fillRect(left - 1, top + height - 1, width + 1, 1);
1264
1264
  }
1265
1265
  else {
@@ -1270,12 +1270,12 @@ export class TileRenderer {
1270
1270
  // -- our borders ----------------------------------------------------------
1271
1271
 
1272
1272
  if (style.border_top) {
1273
- context.fillStyle = ThemeColor2(this.theme, style.border_top_fill, 1);
1273
+ context.fillStyle = ResolveThemeColor(this.theme, style.border_top_fill, 1);
1274
1274
  if (style.border_top === 2) {
1275
1275
  context.fillRect(left - 1, top - 2, width + 1, 1);
1276
1276
  context.fillRect(left - 1, top + 0, width + 1, 1);
1277
- context.fillStyle = ThemeColor2(this.theme, style.fill)
1278
- || ThemeColor(this.theme, this.theme.grid_cell?.fill) || '#fff';
1277
+ context.fillStyle = ResolveThemeColor(this.theme, style.fill)
1278
+ || ResolveThemeColor(this.theme, this.theme.grid_cell?.fill, 0) || '#fff';
1279
1279
  context.fillRect(left - 1, top - 1, width + 1, 1);
1280
1280
  }
1281
1281
  else {
@@ -1284,22 +1284,22 @@ export class TileRenderer {
1284
1284
  }
1285
1285
 
1286
1286
  if (style.border_left) {
1287
- context.fillStyle = ThemeColor2(this.theme, style.border_left_fill, 1);
1287
+ context.fillStyle = ResolveThemeColor(this.theme, style.border_left_fill, 1);
1288
1288
  context.fillRect(left - 1, top - 1, 1, height + 1);
1289
1289
  }
1290
1290
 
1291
1291
  if (style.border_right) {
1292
- context.fillStyle = ThemeColor2(this.theme, style.border_right_fill, 1);
1292
+ context.fillStyle = ResolveThemeColor(this.theme, style.border_right_fill, 1);
1293
1293
  context.fillRect(left + width - 1, top - 1, 1, height + 1);
1294
1294
  }
1295
1295
 
1296
1296
  if (style.border_bottom) {
1297
- context.fillStyle = ThemeColor2(this.theme, style.border_bottom_fill, 1);
1297
+ context.fillStyle = ResolveThemeColor(this.theme, style.border_bottom_fill, 1);
1298
1298
  if (style.border_bottom === 2) {
1299
1299
  context.fillRect(left - 1, top + height - 2, width + 1, 1);
1300
1300
  context.fillRect(left - 1, top + height + 0, width + 1, 1);
1301
- context.fillStyle = ThemeColor2(this.theme, style.fill)
1302
- || ThemeColor(this.theme, this.theme.grid_cell?.fill) || '#fff';
1301
+ context.fillStyle = ResolveThemeColor(this.theme, style.fill)
1302
+ || ResolveThemeColor(this.theme, this.theme.grid_cell?.fill, 0) || '#fff';
1303
1303
  context.fillRect(left - 1, top + height - 1, width + 1, 1);
1304
1304
  }
1305
1305
  else {
@@ -1412,14 +1412,14 @@ export class TileRenderer {
1412
1412
  }
1413
1413
  else {
1414
1414
 
1415
- const fill = ThemeColor2(this.theme, style.fill);
1415
+ const fill = ResolveThemeColor(this.theme, style.fill);
1416
1416
 
1417
1417
  if (fill) {
1418
1418
  context.fillStyle = fill;
1419
1419
  context.fillRect(0, 0, width - 1, height - 1);
1420
1420
  }
1421
1421
  else {
1422
- context.fillStyle = ThemeColor(this.theme, this.theme.grid_cell?.fill) || '#fff';
1422
+ context.fillStyle = ResolveThemeColor(this.theme, this.theme.grid_cell?.fill, 0) || '#fff';
1423
1423
  context.fillRect(0, 0, width - 1, height - 1);
1424
1424
  }
1425
1425
 
@@ -1578,7 +1578,7 @@ export class TileRenderer {
1578
1578
  width,
1579
1579
  height);
1580
1580
 
1581
- context.strokeStyle = context.fillStyle = ThemeColor2(this.theme, style.text, 1);
1581
+ context.strokeStyle = context.fillStyle = ResolveThemeColor(this.theme, style.text, 1);
1582
1582
 
1583
1583
  // there's an issue with theme colors, the function may not be able
1584
1584
  // to translate so we need to update the style (using a copy) to
@@ -1871,10 +1871,11 @@ export class TileRenderer {
1871
1871
  for (const element of overflow_backgrounds) {
1872
1872
 
1873
1873
  if ( element.cell.style?.fill &&
1874
- (element.cell.style.fill.text || element.cell.style.fill.theme || element.cell.style.fill.theme === 0) &&
1874
+ IsDefinedColor(element.cell.style.fill) &&
1875
+ // (element.cell.style.fill.text || element.cell.style.fill.theme || element.cell.style.fill.theme === 0) &&
1875
1876
  !this.options.grid_over_background) {
1876
1877
 
1877
- context.fillStyle = ThemeColor(this.theme, element.cell.style.fill);
1878
+ context.fillStyle = ResolveThemeColor(this.theme, element.cell.style.fill, 0);
1878
1879
  context.fillRect(element.grid.left, element.grid.top, element.grid.width, element.grid.height);
1879
1880
  }
1880
1881
  else {
@@ -1894,7 +1895,7 @@ export class TileRenderer {
1894
1895
  0 );
1895
1896
  }
1896
1897
  else {
1897
- context.fillStyle = this.theme.grid_cell?.fill ? ThemeColor(this.theme, this.theme.grid_cell.fill) : '';
1898
+ context.fillStyle = this.theme.grid_cell?.fill ? ResolveThemeColor(this.theme, this.theme.grid_cell.fill, 0) : '';
1898
1899
  context.fillRect(element.background.left, element.background.top,
1899
1900
  element.background.width, element.background.height);
1900
1901
  }
@@ -1924,7 +1925,7 @@ export class TileRenderer {
1924
1925
  context.lineWidth = 1;
1925
1926
 
1926
1927
  context.strokeStyle = context.fillStyle =
1927
- text_data.format ? text_data.format : ThemeColor2(this.theme, style.text, 1);
1928
+ text_data.format ? text_data.format : ResolveThemeColor(this.theme, style.text, 1);
1928
1929
 
1929
1930
  context.beginPath();
1930
1931
 
@@ -40,15 +40,15 @@ import {
40
40
  ValueType,
41
41
  Localization,
42
42
  IsCellAddress,
43
- ValidationType,
44
43
  LoadThemeProperties,
45
44
  DefaultTheme,
46
45
  ComplexToString,
47
46
  IsComplex,
48
47
  TextPartFlag,
48
+ IsArea,
49
49
  } from 'treb-base-types';
50
50
 
51
- import type { ExpressionUnit } from 'treb-parser';
51
+ import type { ExpressionUnit, UnitAddress } from 'treb-parser';
52
52
  import {
53
53
  DecimalMarkType,
54
54
  ArgumentSeparatorType,
@@ -1465,20 +1465,22 @@ export class Grid extends GridBase {
1465
1465
  * and will render as a dropdown; the list can be a list of values or
1466
1466
  * a range reference.
1467
1467
  */
1468
- public SetValidation(target?: ICellAddress, data?: CellValue[]|IArea, error?: boolean): void {
1468
+ public SetValidation(target?: IArea, data?: CellValue[]|IArea, error?: boolean): void {
1469
1469
 
1470
1470
  if (!target) {
1471
1471
  if (this.primary_selection.empty) {
1472
1472
  throw new Error('invalid target in set validation');
1473
1473
  }
1474
- target = this.primary_selection.target;
1474
+ target = this.primary_selection.area;
1475
1475
  }
1476
1476
 
1477
+ const area = new Area(target.start, target.end);
1478
+
1477
1479
  // console.info({target, data});
1478
1480
 
1479
1481
  const command: DataValidationCommand = {
1480
1482
  key: CommandKey.DataValidation,
1481
- area: target,
1483
+ area: { start: area.start, end: area.end },
1482
1484
  error,
1483
1485
  };
1484
1486
 
@@ -1502,9 +1504,11 @@ export class Grid extends GridBase {
1502
1504
  //
1503
1505
 
1504
1506
  if (!this.primary_selection.empty &&
1505
- (!target.sheet_id || target.sheet_id === this.active_sheet.id) &&
1506
- (this.primary_selection.target.row === target.row) &&
1507
- (this.primary_selection.target.column === target.column)) {
1507
+ (!target.start.sheet_id || target.start.sheet_id === this.active_sheet.id) &&
1508
+ area.Contains(this.primary_selection.target)) {
1509
+
1510
+ // (this.primary_selection.target.row === target.start.row) &&
1511
+ // (this.primary_selection.target.column === target.start.column)) {
1508
1512
 
1509
1513
  // console.info('repaint selection');
1510
1514
 
@@ -1743,16 +1747,80 @@ export class Grid extends GridBase {
1743
1747
 
1744
1748
  }
1745
1749
 
1750
+ /**
1751
+ * render the given data as R1C1. the source address is a base for
1752
+ * rendering relative addresses (is there a case for this method
1753
+ * handling absolute offsets as well? ...)
1754
+ *
1755
+ * NOTE: this method modifies the input data (at least it does if
1756
+ * it's an array). so this method should only be called with scratchpad
1757
+ * data.
1758
+ *
1759
+ */
1760
+ public FormatR1C1(data: CellValue|CellValue[][], source: ICellAddress|IArea) {
1761
+
1762
+ // normalize
1763
+
1764
+ if (IsArea(source)) {
1765
+ source = source.start;
1766
+ }
1767
+
1768
+ if (!Array.isArray(data)) {
1769
+ data = [[data]];
1770
+ }
1771
+
1772
+ const base: UnitAddress = {
1773
+ type: 'address',
1774
+ label: '',
1775
+ row: source.row,
1776
+ column: source.column,
1777
+ sheet: source.sheet_id ? this.model.sheets.Name(source.sheet_id) : this.active_sheet.name,
1778
+ position: 0,
1779
+ id: 0,
1780
+ };
1781
+
1782
+ for (let r = 0; r < data.length; r++) {
1783
+ const row = data[r];
1784
+ for (let c = 0; c < row.length; c++) {
1785
+ const cell = row[c];
1786
+ if (typeof cell === 'string' && cell[0] === '=') {
1787
+
1788
+ const parse_result = this.parser.Parse(cell);
1789
+ if (parse_result.expression) {
1790
+ row[c] = '=' + this.parser.Render(parse_result.expression, {
1791
+ missing: '',
1792
+ r1c1: true,
1793
+ r1c1_base: {
1794
+ ...base,
1795
+ row: source.row + r,
1796
+ column: source.column + c,
1797
+ },
1798
+ });
1799
+ }
1800
+ }
1801
+ }
1802
+ }
1803
+
1804
+ return data;
1805
+
1806
+ }
1807
+
1746
1808
  /**
1747
1809
  * get data in a given range, optionally formulas
1748
1810
  * API method
1749
1811
  */
1750
- public GetRange(range: ICellAddress | IArea, type?: 'formula'|'formatted'): CellValue|CellValue[][]|undefined {
1812
+ public GetRange(range: ICellAddress | IArea, type?: 'formatted'|'A1'|'R1C1'): CellValue|CellValue[][]|undefined {
1751
1813
 
1752
1814
  if (IsCellAddress(range)) {
1753
1815
  const sheet = this.model.sheets.Find(range.sheet_id || this.active_sheet.id);
1754
1816
  if (sheet) {
1755
- if (type === 'formula') { return sheet.cells.RawValue(range); }
1817
+ if (type === 'A1' || type === 'R1C1') {
1818
+ const data = sheet.cells.RawValue(range);
1819
+ if (type === 'R1C1') {
1820
+ return this.FormatR1C1(data, range);
1821
+ }
1822
+ return data;
1823
+ }
1756
1824
  if (type === 'formatted') { return sheet.GetFormattedRange(range); }
1757
1825
  return sheet.cells.GetRange(range);
1758
1826
  }
@@ -1761,7 +1829,13 @@ export class Grid extends GridBase {
1761
1829
 
1762
1830
  const sheet = this.model.sheets.Find(range.start.sheet_id || this.active_sheet.id);
1763
1831
  if (sheet) {
1764
- if (type === 'formula') { return sheet.cells.RawValue(range.start, range.end); }
1832
+ if (type === 'A1' || type === 'R1C1') {
1833
+ const data = sheet.cells.RawValue(range.start, range.end);
1834
+ if (type === 'R1C1') {
1835
+ return this.FormatR1C1(data, range);
1836
+ }
1837
+ return data;
1838
+ }
1765
1839
  if (type === 'formatted') { return sheet.GetFormattedRange(range.start, range.end); }
1766
1840
  return sheet.cells.GetRange(range.start, range.end);
1767
1841
  }
@@ -1886,6 +1960,9 @@ export class Grid extends GridBase {
1886
1960
 
1887
1961
  if (!Is2DArray(data)) {
1888
1962
 
1963
+ // we don't allow this anymore (at least we say we don't)
1964
+
1965
+
1889
1966
  // flat array -- we can recycle. recycling is R style (values, not rows).
1890
1967
  // in any event convert to [][]
1891
1968
 
@@ -1917,6 +1994,56 @@ export class Grid extends GridBase {
1917
1994
  }
1918
1995
 
1919
1996
  }
1997
+ else {
1998
+ if (recycle) {
1999
+
2000
+ // recycle 2D array. we'll do this if the target range is a multiple
2001
+ // (or larger) of the source array. in blocks.
2002
+
2003
+ if (range.rows > data.length) {
2004
+ const recycle_rows = Math.floor(range.rows / data.length);
2005
+ if (recycle_rows > 1) {
2006
+ const source = [...data];
2007
+ for (let i = 0; i < recycle_rows; i++) {
2008
+ const clone = JSON.parse(JSON.stringify(source));
2009
+ data.push(...clone);
2010
+ }
2011
+ }
2012
+ }
2013
+
2014
+ let cols = 0;
2015
+ for (const row of data) {
2016
+ cols = Math.max(cols, row.length);
2017
+ }
2018
+
2019
+ if (range.columns > cols) {
2020
+ const recycle_columns = Math.floor(range.columns / cols);
2021
+ if (recycle_columns > 1) {
2022
+
2023
+ for (const row of data) {
2024
+
2025
+ // pad out all rows first, jic
2026
+ while (row.length < cols) {
2027
+ row.push(undefined);
2028
+ }
2029
+
2030
+ // now recycle
2031
+ const source = [...row];
2032
+ for (let i = 0; i < recycle_columns; i++) {
2033
+ const clone = JSON.parse(JSON.stringify(source));
2034
+ row.push(...clone);
2035
+ }
2036
+
2037
+ }
2038
+
2039
+ // ...
2040
+
2041
+ // console.info({recycle_columns});
2042
+ }
2043
+ }
2044
+
2045
+ }
2046
+ }
1920
2047
 
1921
2048
  if (transpose) { data = this.Transpose(data); }
1922
2049
 
@@ -2221,12 +2348,13 @@ export class Grid extends GridBase {
2221
2348
  // const context = Sheet.measurement_canvas.getContext('2d');
2222
2349
  // if (!context) return;
2223
2350
 
2224
- let width = 12;
2225
- const padding = 4 * 2; // FIXME: parameterize
2351
+ let width = 0;
2352
+ const padding = 12; // 4 * 2; // FIXME: parameterize
2226
2353
 
2227
2354
  if (!allow_shrink) width = sheet.GetColumnWidth(column);
2228
2355
 
2229
2356
  for (let row = 0; row < sheet.cells.rows; row++) {
2357
+
2230
2358
  const cell = sheet.CellData({ row, column });
2231
2359
  let text = cell.formatted || '';
2232
2360
  if (typeof text !== 'string') {
@@ -2243,7 +2371,9 @@ export class Grid extends GridBase {
2243
2371
  }
2244
2372
  }
2245
2373
 
2246
- sheet.SetColumnWidth(column, width);
2374
+ if (width > padding + 2) {
2375
+ sheet.SetColumnWidth(column, width);
2376
+ }
2247
2377
 
2248
2378
  }
2249
2379
 
@@ -4966,15 +5096,19 @@ export class Grid extends GridBase {
4966
5096
 
4967
5097
  }
4968
5098
 
4969
- if (cell.validation && cell.validation.error) {
5099
+ const validation = this.active_sheet.GetValidation(target)[0];
5100
+
5101
+ // only consider the first result
5102
+
5103
+ if (validation && validation.error) {
4970
5104
 
4971
5105
  let list: CellValue[]|undefined;
4972
5106
 
4973
- if (cell.validation.type === ValidationType.List) {
4974
- list = cell.validation.list;
5107
+ if (validation.type === 'list') {
5108
+ list = validation.list;
4975
5109
  }
4976
- else if (cell.validation.type === ValidationType.Range) {
4977
- list = this.GetValidationRange(cell.validation.area);
5110
+ else if (validation.type === 'range') {
5111
+ list = this.GetValidationRange(validation.area);
4978
5112
  }
4979
5113
 
4980
5114
  if (list && list.length) {
@@ -6216,15 +6350,19 @@ export class Grid extends GridBase {
6216
6350
  // sync up, so it would be a separate function but called at the
6217
6351
  // same time.
6218
6352
 
6219
- if (data.validation && !data.style?.locked) {
6353
+ // less true now that they're maintained separately
6354
+
6355
+ const validation = this.active_sheet.GetValidation(this.primary_selection.target)[0];
6356
+
6357
+ if (validation && !data.style?.locked) {
6220
6358
 
6221
6359
  let list: CellValue[] | undefined;
6222
6360
 
6223
- if (data.validation.type === ValidationType.List) {
6224
- list = data.validation.list;
6361
+ if (validation.type === 'list') {
6362
+ list = validation.list;
6225
6363
  }
6226
- else if (data.validation.type === ValidationType.Range) {
6227
- list = this.GetValidationRange(data.validation.area);
6364
+ else if (validation.type === 'range') {
6365
+ list = this.GetValidationRange(validation.area);
6228
6366
  }
6229
6367
 
6230
6368
  if (list && list.length) {
@@ -6541,7 +6679,7 @@ export class Grid extends GridBase {
6541
6679
  // tsv_row.push(cell.formatted);
6542
6680
 
6543
6681
  let text_value = '';
6544
- if (cell.calculated) {
6682
+ if (cell.calculated !== undefined) {
6545
6683
  if (cell.calculated_type === ValueType.complex) {
6546
6684
  text_value = ComplexToString(cell.calculated as Complex);
6547
6685
  }
@@ -7130,7 +7268,7 @@ export class Grid extends GridBase {
7130
7268
 
7131
7269
  if (auto) {
7132
7270
  for (const entry of column) {
7133
- this.AutoSizeColumn(sheet, entry, false);
7271
+ this.AutoSizeColumn(sheet, entry, true);
7134
7272
  }
7135
7273
  }
7136
7274
  else {