@trebco/treb 28.11.1 → 28.13.2
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.
- package/dist/treb-spreadsheet-light.mjs +11 -11
- package/dist/treb-spreadsheet.mjs +11 -11
- package/dist/treb.d.ts +9 -1
- package/package.json +1 -1
- package/treb-calculator/src/calculator.ts +235 -68
- package/treb-calculator/src/descriptors.ts +5 -0
- package/treb-calculator/src/functions/base-functions.ts +119 -7
- package/treb-calculator/src/functions/text-functions.ts +45 -55
- package/treb-calculator/src/primitives.ts +11 -0
- package/treb-calculator/src/utilities.ts +55 -0
- package/treb-embed/src/embedded-spreadsheet.ts +161 -27
- package/treb-embed/style/dark-theme.scss +1 -0
- package/treb-export/src/export-worker/export-worker.ts +1 -0
- package/treb-export/src/import2.ts +9 -3
- package/treb-export/src/workbook-style2.ts +78 -14
- package/treb-grid/src/editors/editor.ts +7 -0
- package/treb-grid/src/types/grid.ts +20 -9
- package/treb-grid/src/types/grid_base.ts +10 -105
- package/treb-grid/src/types/serialize_options.ts +5 -0
- package/treb-parser/src/parser-types.ts +27 -4
- package/treb-parser/src/parser.ts +74 -36
|
@@ -309,6 +309,17 @@ export const Equals = (a: UnionValue, b: UnionValue): UnionValue => {
|
|
|
309
309
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
// this is standard (icase) string equality. we might also need
|
|
313
|
+
// to handle wildcard string matching, although it's not the
|
|
314
|
+
// default case for = operators.
|
|
315
|
+
|
|
316
|
+
if (a.type === ValueType.string && b.type === ValueType.string) {
|
|
317
|
+
return {
|
|
318
|
+
type: ValueType.boolean,
|
|
319
|
+
value: a.value.toLowerCase() === b.value.toLowerCase(),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
312
323
|
return { type: ValueType.boolean, value: a.value == b.value }; // note ==
|
|
313
324
|
};
|
|
314
325
|
|
|
@@ -302,3 +302,58 @@ export const ApplyAsArray2 = (base: (a: any, b: any, ...rest: any[]) => UnionVal
|
|
|
302
302
|
}
|
|
303
303
|
};
|
|
304
304
|
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* parse a string with wildcards into a regex pattern
|
|
308
|
+
*
|
|
309
|
+
* from
|
|
310
|
+
* https://exceljet.net/glossary/wildcard
|
|
311
|
+
*
|
|
312
|
+
* Excel has 3 wildcards you can use in your formulas:
|
|
313
|
+
*
|
|
314
|
+
* Asterisk (*) - zero or more characters
|
|
315
|
+
* Question mark (?) - any one character
|
|
316
|
+
* Tilde (~) - escape for literal character (~*) a literal question mark (~?), or a literal tilde (~~)
|
|
317
|
+
*
|
|
318
|
+
* they're pretty liberal with escaping, nothing is an error, just roll with it
|
|
319
|
+
*
|
|
320
|
+
*/
|
|
321
|
+
export const ParseWildcards = (text: string): string => {
|
|
322
|
+
|
|
323
|
+
const result: string[] = [];
|
|
324
|
+
const length = text.length;
|
|
325
|
+
|
|
326
|
+
const escaped_chars = '[\\^$.|?*+()';
|
|
327
|
+
|
|
328
|
+
for (let i = 0; i < length; i++) {
|
|
329
|
+
let char = text[i];
|
|
330
|
+
switch (char) {
|
|
331
|
+
|
|
332
|
+
case '*':
|
|
333
|
+
result.push('.', '*');
|
|
334
|
+
break;
|
|
335
|
+
|
|
336
|
+
case '?':
|
|
337
|
+
result.push('.');
|
|
338
|
+
break;
|
|
339
|
+
|
|
340
|
+
case '~':
|
|
341
|
+
char = text[++i] || '';
|
|
342
|
+
|
|
343
|
+
// eslint-disable-next-line no-fallthrough
|
|
344
|
+
default:
|
|
345
|
+
for (let j = 0; j < escaped_chars.length; j++) {
|
|
346
|
+
if (char === escaped_chars[j]) {
|
|
347
|
+
result.push('\\');
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
result.push(char);
|
|
352
|
+
break;
|
|
353
|
+
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return result.join('');
|
|
358
|
+
|
|
359
|
+
};
|
|
@@ -42,6 +42,8 @@ import type {
|
|
|
42
42
|
CondifionalFormatExpressionOptions,
|
|
43
43
|
ConditionalFormatCellMatchOptions,
|
|
44
44
|
ConditionalFormatCellMatch,
|
|
45
|
+
MacroFunction,
|
|
46
|
+
SerializedNamedExpression,
|
|
45
47
|
} from 'treb-grid';
|
|
46
48
|
|
|
47
49
|
import {
|
|
@@ -413,7 +415,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
413
415
|
}
|
|
414
416
|
|
|
415
417
|
/** for destruction */
|
|
416
|
-
protected
|
|
418
|
+
protected view_node?: HTMLElement;
|
|
417
419
|
|
|
418
420
|
/** for destruction */
|
|
419
421
|
protected key_listener?: (event: KeyboardEvent) => void;
|
|
@@ -844,7 +846,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
844
846
|
// elements. but we don't add a default; rather we use a template
|
|
845
847
|
|
|
846
848
|
const template = container.querySelector('.treb-view-template') as HTMLTemplateElement;
|
|
847
|
-
this.
|
|
849
|
+
this.view_node = template.content.firstElementChild?.cloneNode(true) as HTMLElement;
|
|
848
850
|
|
|
849
851
|
// this is a little weird but we're inserting at the front. the
|
|
850
852
|
// reason for this is that we only want to use one resize handle,
|
|
@@ -852,13 +854,13 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
852
854
|
|
|
853
855
|
// we could work around this, really we're just being lazy.
|
|
854
856
|
|
|
855
|
-
container.prepend(this.
|
|
857
|
+
container.prepend(this.view_node);
|
|
856
858
|
|
|
857
859
|
|
|
858
860
|
// this.node = container;
|
|
859
861
|
// this.node = this.view;
|
|
860
862
|
|
|
861
|
-
this.
|
|
863
|
+
this.view_node.addEventListener('focusin', () => {
|
|
862
864
|
if (this.focus_target !== this) {
|
|
863
865
|
this.Publish({ type: 'focus-view' });
|
|
864
866
|
this.focus_target = this;
|
|
@@ -878,14 +880,14 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
878
880
|
|
|
879
881
|
// const view = container.querySelector('.treb-view') as HTMLElement;
|
|
880
882
|
|
|
881
|
-
this.grid.Initialize(this.
|
|
883
|
+
this.grid.Initialize(this.view_node, toll_initial_render);
|
|
882
884
|
|
|
883
885
|
// dnd
|
|
884
886
|
|
|
885
887
|
if (this.options.dnd) {
|
|
886
|
-
this.
|
|
887
|
-
this.
|
|
888
|
-
this.
|
|
888
|
+
this.view_node.addEventListener('dragenter', (event) => this.HandleDrag(event));
|
|
889
|
+
this.view_node.addEventListener('dragover', (event) => this.HandleDrag(event));
|
|
890
|
+
this.view_node.addEventListener('drop', (event) => this.HandleDrop(event));
|
|
889
891
|
}
|
|
890
892
|
|
|
891
893
|
// set up grid events
|
|
@@ -1244,19 +1246,19 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
1244
1246
|
sheet.grid.grid_events.CancelAll();
|
|
1245
1247
|
sheet.events.CancelAll();
|
|
1246
1248
|
|
|
1247
|
-
if (sheet.
|
|
1249
|
+
if (sheet.view_node?.parentElement) {
|
|
1248
1250
|
|
|
1249
1251
|
// remove listener
|
|
1250
1252
|
if (sheet.key_listener) {
|
|
1251
|
-
sheet.
|
|
1253
|
+
sheet.view_node.parentElement.removeEventListener('keydown', sheet.key_listener);
|
|
1252
1254
|
}
|
|
1253
1255
|
|
|
1254
1256
|
// remove node
|
|
1255
|
-
sheet.
|
|
1257
|
+
sheet.view_node.parentElement.removeChild(sheet.view_node);
|
|
1256
1258
|
}
|
|
1257
1259
|
|
|
1258
1260
|
// in case other view was focused
|
|
1259
|
-
this.
|
|
1261
|
+
this.view_node?.focus();
|
|
1260
1262
|
|
|
1261
1263
|
// usually this results in us getting larger, we may need to update
|
|
1262
1264
|
this.Resize();
|
|
@@ -1285,7 +1287,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
1285
1287
|
const view = this.CreateView();
|
|
1286
1288
|
view.grid.EnsureActiveSheet(true);
|
|
1287
1289
|
|
|
1288
|
-
view.
|
|
1290
|
+
view.view_node?.addEventListener('focusin', () => {
|
|
1289
1291
|
if (this.focus_target !== view) {
|
|
1290
1292
|
this.Publish({ type: 'focus-view' });
|
|
1291
1293
|
this.focus_target = view;
|
|
@@ -2244,12 +2246,16 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2244
2246
|
this.parser.flags.r1c1 = r1c1;
|
|
2245
2247
|
|
|
2246
2248
|
if (argument_separator === ',') {
|
|
2247
|
-
this.parser.
|
|
2248
|
-
|
|
2249
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
2250
|
+
|
|
2251
|
+
// this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
2252
|
+
// this.parser.decimal_mark = DecimalMarkType.Period;
|
|
2249
2253
|
}
|
|
2250
2254
|
else {
|
|
2251
|
-
this.parser.
|
|
2252
|
-
|
|
2255
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Comma);
|
|
2256
|
+
|
|
2257
|
+
// this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
2258
|
+
// this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
2253
2259
|
}
|
|
2254
2260
|
|
|
2255
2261
|
const result = this.parser.Parse(formula);
|
|
@@ -2354,12 +2360,16 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2354
2360
|
*/
|
|
2355
2361
|
|
|
2356
2362
|
if (argument_separator === ',') {
|
|
2357
|
-
this.parser.
|
|
2358
|
-
|
|
2363
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
2364
|
+
|
|
2365
|
+
// this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
2366
|
+
// this.parser.decimal_mark = DecimalMarkType.Period;
|
|
2359
2367
|
}
|
|
2360
2368
|
else {
|
|
2361
|
-
this.parser.
|
|
2362
|
-
|
|
2369
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Comma);
|
|
2370
|
+
|
|
2371
|
+
// this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
2372
|
+
// this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
2363
2373
|
}
|
|
2364
2374
|
|
|
2365
2375
|
// const r1c1_state = this.parser.flags.r1c1;
|
|
@@ -2749,13 +2759,14 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2749
2759
|
|
|
2750
2760
|
// FIXME: type
|
|
2751
2761
|
|
|
2752
|
-
const serialized: SerializedModel = this.grid.Serialize({
|
|
2762
|
+
const serialized: SerializedModel = this.Serialize({ // this.grid.Serialize({
|
|
2753
2763
|
rendered_values: true,
|
|
2754
2764
|
expand_arrays: true,
|
|
2755
2765
|
export_colors: true,
|
|
2756
2766
|
decorated_cells: true,
|
|
2757
2767
|
tables: true,
|
|
2758
2768
|
share_resources: false,
|
|
2769
|
+
export_functions: true,
|
|
2759
2770
|
});
|
|
2760
2771
|
|
|
2761
2772
|
// why do _we_ put this in, instead of the grid method?
|
|
@@ -3523,6 +3534,9 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
3523
3534
|
/**
|
|
3524
3535
|
* Create a macro function.
|
|
3525
3536
|
*
|
|
3537
|
+
* FIXME: this needs a control for argument separator, like other
|
|
3538
|
+
* functions that use formulas (@see SetRange)
|
|
3539
|
+
*
|
|
3526
3540
|
* @public
|
|
3527
3541
|
*/
|
|
3528
3542
|
public DefineFunction(name: string, argument_names: string | string[] = '', function_def = '0'): void {
|
|
@@ -3592,7 +3606,8 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
3592
3606
|
...options,
|
|
3593
3607
|
};
|
|
3594
3608
|
|
|
3595
|
-
const grid_data = this.grid.Serialize(options);
|
|
3609
|
+
// const grid_data = this.grid.Serialize(options);
|
|
3610
|
+
const grid_data = this.Serialize(options);
|
|
3596
3611
|
|
|
3597
3612
|
// NOTE: these are not really env vars. we replace them at build time
|
|
3598
3613
|
// via a webpack plugin. using the env syntax lets them look "real" at
|
|
@@ -4405,6 +4420,121 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
4405
4420
|
|
|
4406
4421
|
// --- internal (protected) methods ------------------------------------------
|
|
4407
4422
|
|
|
4423
|
+
// --- moved from grid/grid base ---------------------------------------------
|
|
4424
|
+
|
|
4425
|
+
|
|
4426
|
+
/**
|
|
4427
|
+
* serialize data model. moved from grid/grid base. this is moved so we
|
|
4428
|
+
* have access to the calculator, which we want so we can do function
|
|
4429
|
+
* translation on some new functions that don't necessarily map 1:1 to
|
|
4430
|
+
* XLSX functions. we can also do cleanup on functions where we're less
|
|
4431
|
+
* strict about arguments (ROUND, for example).
|
|
4432
|
+
*
|
|
4433
|
+
*/
|
|
4434
|
+
protected Serialize(options: SerializeOptions = {}): SerializedModel {
|
|
4435
|
+
|
|
4436
|
+
const active_sheet = this.grid.active_sheet; // I thought this was in view? (...)
|
|
4437
|
+
|
|
4438
|
+
active_sheet.selection = JSON.parse(JSON.stringify(this.grid.GetSelection()));
|
|
4439
|
+
|
|
4440
|
+
// same for scroll offset
|
|
4441
|
+
|
|
4442
|
+
const scroll_offset = this.grid.ScrollOffset();
|
|
4443
|
+
if (scroll_offset) {
|
|
4444
|
+
active_sheet.scroll_offset = scroll_offset; // this.grid.layout.scroll_offset;
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4447
|
+
// NOTE: annotations moved to sheets, they will be serialized in the sheets
|
|
4448
|
+
|
|
4449
|
+
const sheet_data = this.model.sheets.list.map((sheet) => sheet.toJSON(options));
|
|
4450
|
+
|
|
4451
|
+
// OK, not serializing tables in cells anymore. old comment about this:
|
|
4452
|
+
//
|
|
4453
|
+
// at the moment, tables are being serialized in cells. if we put them
|
|
4454
|
+
// in here, then we have two records of the same data. that would be bad.
|
|
4455
|
+
// I think this is probably the correct place, but if we put them here
|
|
4456
|
+
// we need to stop serializing in cells. and I'm not sure that there are
|
|
4457
|
+
// not some side-effects to that. hopefully not, but (...)
|
|
4458
|
+
//
|
|
4459
|
+
|
|
4460
|
+
let tables: Table[] | undefined;
|
|
4461
|
+
if (this.model.tables.size > 0) {
|
|
4462
|
+
tables = Array.from(this.model.tables.values());
|
|
4463
|
+
}
|
|
4464
|
+
|
|
4465
|
+
// NOTE: moving into a structured object (the sheet data is also structured,
|
|
4466
|
+
// of course) but we are moving things out of sheet (just named ranges atm))
|
|
4467
|
+
|
|
4468
|
+
let macro_functions: MacroFunction[] | undefined;
|
|
4469
|
+
|
|
4470
|
+
if (this.model.macro_functions.size) {
|
|
4471
|
+
macro_functions = [];
|
|
4472
|
+
for (const macro of this.model.macro_functions.values()) {
|
|
4473
|
+
macro_functions.push({
|
|
4474
|
+
...macro,
|
|
4475
|
+
expression: undefined,
|
|
4476
|
+
});
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
|
|
4480
|
+
// when serializing named expressions, we have to make sure
|
|
4481
|
+
// that there's a sheet name in any address/range.
|
|
4482
|
+
|
|
4483
|
+
const named_expressions: SerializedNamedExpression[] = [];
|
|
4484
|
+
if (this.model.named_expressions) {
|
|
4485
|
+
|
|
4486
|
+
for (const [name, expr] of this.model.named_expressions) {
|
|
4487
|
+
this.parser.Walk(expr, unit => {
|
|
4488
|
+
if (unit.type === 'address' || unit.type === 'range') {
|
|
4489
|
+
const test = unit.type === 'range' ? unit.start : unit;
|
|
4490
|
+
|
|
4491
|
+
test.absolute_column = test.absolute_row = true;
|
|
4492
|
+
|
|
4493
|
+
if (!test.sheet) {
|
|
4494
|
+
if (test.sheet_id) {
|
|
4495
|
+
const sheet = this.model.sheets.Find(test.sheet_id);
|
|
4496
|
+
if (sheet) {
|
|
4497
|
+
test.sheet = sheet.name;
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
if (!test.sheet) {
|
|
4501
|
+
test.sheet = active_sheet.name;
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
|
|
4505
|
+
if (unit.type === 'range') {
|
|
4506
|
+
unit.end.absolute_column = unit.end.absolute_row = true;
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
return false;
|
|
4510
|
+
}
|
|
4511
|
+
else if (unit.type === 'call' && options.export_functions) {
|
|
4512
|
+
// ...
|
|
4513
|
+
}
|
|
4514
|
+
return true;
|
|
4515
|
+
});
|
|
4516
|
+
const rendered = this.parser.Render(expr, { missing: '' });
|
|
4517
|
+
named_expressions.push({
|
|
4518
|
+
name, expression: rendered
|
|
4519
|
+
});
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4523
|
+
return {
|
|
4524
|
+
sheet_data,
|
|
4525
|
+
active_sheet: active_sheet.id,
|
|
4526
|
+
named_ranges: this.model.named_ranges.Count() ?
|
|
4527
|
+
this.model.named_ranges.Serialize() :
|
|
4528
|
+
undefined,
|
|
4529
|
+
macro_functions,
|
|
4530
|
+
tables,
|
|
4531
|
+
named_expressions: named_expressions.length ? named_expressions : undefined,
|
|
4532
|
+
};
|
|
4533
|
+
|
|
4534
|
+
}
|
|
4535
|
+
|
|
4536
|
+
// --- /moved ----------------------------------------------------------------
|
|
4537
|
+
|
|
4408
4538
|
/**
|
|
4409
4539
|
*
|
|
4410
4540
|
*/
|
|
@@ -5531,14 +5661,18 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
5531
5661
|
// FIXME: also we should unify on types for decimal, argument separator
|
|
5532
5662
|
|
|
5533
5663
|
if (data.decimal_mark === '.') {
|
|
5534
|
-
parser.decimal_mark = DecimalMarkType.Period;
|
|
5535
|
-
parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
5664
|
+
// parser.decimal_mark = DecimalMarkType.Period;
|
|
5665
|
+
// parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
5666
|
+
parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
5667
|
+
|
|
5536
5668
|
target_decimal_mark = DecimalMarkType.Comma;
|
|
5537
5669
|
target_argument_separator = ArgumentSeparatorType.Semicolon;
|
|
5538
5670
|
}
|
|
5539
5671
|
else {
|
|
5540
|
-
parser.decimal_mark = DecimalMarkType.Comma;
|
|
5541
|
-
parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
5672
|
+
// parser.decimal_mark = DecimalMarkType.Comma;
|
|
5673
|
+
// parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
5674
|
+
parser.SetLocaleSettings(DecimalMarkType.Comma);
|
|
5675
|
+
|
|
5542
5676
|
target_decimal_mark = DecimalMarkType.Period;
|
|
5543
5677
|
target_argument_separator = ArgumentSeparatorType.Comma;
|
|
5544
5678
|
}
|
|
@@ -81,7 +81,10 @@ export class Importer {
|
|
|
81
81
|
t?: string;
|
|
82
82
|
s?: string;
|
|
83
83
|
};
|
|
84
|
-
v?: string|number
|
|
84
|
+
v?: string|number|{
|
|
85
|
+
t$: string;
|
|
86
|
+
a$?: any;
|
|
87
|
+
};
|
|
85
88
|
f?: string|{
|
|
86
89
|
t$: string;
|
|
87
90
|
a$?: {
|
|
@@ -214,14 +217,17 @@ export class Importer {
|
|
|
214
217
|
}
|
|
215
218
|
|
|
216
219
|
if (typeof element.v !== 'undefined') {
|
|
217
|
-
|
|
220
|
+
|
|
221
|
+
const V = (typeof element.v === 'object') ? element.v?.t$ : element.v;
|
|
222
|
+
|
|
223
|
+
const num = Number(V.toString());
|
|
218
224
|
if (!isNaN(num)) {
|
|
219
225
|
calculated_type = 'number'; // ValueType.number;
|
|
220
226
|
calculated_value = num;
|
|
221
227
|
}
|
|
222
228
|
else {
|
|
223
229
|
calculated_type = 'string'; // ValueType.string;
|
|
224
|
-
calculated_value =
|
|
230
|
+
calculated_value = V.toString();
|
|
225
231
|
}
|
|
226
232
|
}
|
|
227
233
|
|
|
@@ -29,6 +29,9 @@ import { XMLUtils } from './xml-utils';
|
|
|
29
29
|
|
|
30
30
|
import { Unescape } from './unescape_xml';
|
|
31
31
|
|
|
32
|
+
// what's the default font size? ... 11pt?
|
|
33
|
+
const DEFAULT_FONT_SIZE = 11;
|
|
34
|
+
|
|
32
35
|
export interface Font {
|
|
33
36
|
size?: number;
|
|
34
37
|
name?: string;
|
|
@@ -269,13 +272,24 @@ export class StyleCache {
|
|
|
269
272
|
|
|
270
273
|
}
|
|
271
274
|
|
|
275
|
+
|
|
272
276
|
if (composite.font_size?.unit && composite.font_size.value) {
|
|
273
|
-
if (composite.font_size.unit
|
|
274
|
-
|
|
277
|
+
if (composite.font_size.unit === 'em') {
|
|
278
|
+
font.size = composite.font_size.value * DEFAULT_FONT_SIZE;
|
|
275
279
|
}
|
|
276
|
-
else {
|
|
280
|
+
else if (composite.font_size.unit === '%') {
|
|
281
|
+
font.size = composite.font_size.value * DEFAULT_FONT_SIZE / 100;
|
|
282
|
+
}
|
|
283
|
+
else if (composite.font_size.unit === 'pt' ){
|
|
277
284
|
font.size = composite.font_size.value;
|
|
278
285
|
}
|
|
286
|
+
else if (composite.font_size.unit === 'px' ){
|
|
287
|
+
font.size = composite.font_size.value * .75; // ?
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
console.warn(`Unhandled font size unit`, composite.font_size);
|
|
291
|
+
}
|
|
292
|
+
|
|
279
293
|
}
|
|
280
294
|
|
|
281
295
|
if (composite.bold) font.bold = true;
|
|
@@ -657,6 +671,19 @@ export class StyleCache {
|
|
|
657
671
|
|
|
658
672
|
// borders
|
|
659
673
|
|
|
674
|
+
const BorderEdgeToColor = (edge: BorderEdge): Color|undefined => {
|
|
675
|
+
|
|
676
|
+
// TODO: indexed
|
|
677
|
+
|
|
678
|
+
if (typeof edge.theme !== 'undefined') {
|
|
679
|
+
return {
|
|
680
|
+
theme: edge.theme,
|
|
681
|
+
tint: edge.tint,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
};
|
|
686
|
+
|
|
660
687
|
const border = this.borders[xf.border || 0];
|
|
661
688
|
if (border) {
|
|
662
689
|
if (border.bottom.style) {
|
|
@@ -666,10 +693,20 @@ export class StyleCache {
|
|
|
666
693
|
else {
|
|
667
694
|
props.border_bottom = 1;
|
|
668
695
|
}
|
|
696
|
+
props.border_bottom_fill = BorderEdgeToColor(border.bottom);
|
|
697
|
+
}
|
|
698
|
+
if (border.left.style) {
|
|
699
|
+
props.border_left = 1;
|
|
700
|
+
props.border_left_fill = BorderEdgeToColor(border.left);
|
|
701
|
+
}
|
|
702
|
+
if (border.top.style) {
|
|
703
|
+
props.border_top = 1;
|
|
704
|
+
props.border_top_fill = BorderEdgeToColor(border.top);
|
|
705
|
+
}
|
|
706
|
+
if (border.right.style) {
|
|
707
|
+
props.border_right = 1;
|
|
708
|
+
props.border_right_fill = BorderEdgeToColor(border.right);
|
|
669
709
|
}
|
|
670
|
-
if (border.left.style) props.border_left = 1;
|
|
671
|
-
if (border.top.style) props.border_top = 1;
|
|
672
|
-
if (border.right.style) props.border_right = 1;
|
|
673
710
|
}
|
|
674
711
|
|
|
675
712
|
return props;
|
|
@@ -1144,31 +1181,58 @@ export class StyleCache {
|
|
|
1144
1181
|
|
|
1145
1182
|
composite = FindAll('styleSheet/borders/border');
|
|
1146
1183
|
|
|
1184
|
+
const ElementToBorderEdge = (element: any, edge: BorderEdge) => {
|
|
1185
|
+
|
|
1186
|
+
if (element?.a$) {
|
|
1187
|
+
edge.style = element.a$.style;
|
|
1188
|
+
if (typeof element.color === 'object') {
|
|
1189
|
+
if (typeof element.color.a$?.indexed !== 'undefined') {
|
|
1190
|
+
edge.color = Number(element.color.a$.indexed);
|
|
1191
|
+
}
|
|
1192
|
+
if (typeof element.color.a$?.theme !== 'undefined') {
|
|
1193
|
+
edge.theme = Number(element.color.a$.theme);
|
|
1194
|
+
}
|
|
1195
|
+
if (typeof element.color.a$?.tint !== 'undefined') {
|
|
1196
|
+
edge.tint = Number(element.color.a$.tint);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
};
|
|
1202
|
+
|
|
1203
|
+
|
|
1147
1204
|
this.borders = composite.map(element => {
|
|
1148
1205
|
|
|
1149
1206
|
const border: BorderStyle = JSON.parse(JSON.stringify(default_border));
|
|
1150
1207
|
|
|
1208
|
+
/*
|
|
1151
1209
|
// we're relying on these being empty strings -> falsy, not a good look
|
|
1152
1210
|
|
|
1153
1211
|
if (element.left) {
|
|
1154
|
-
border.left.style = element.left.a$.style;
|
|
1155
|
-
border.left.color = Number(element.left.color?.a$?.indexed);
|
|
1212
|
+
// border.left.style = element.left.a$.style;
|
|
1213
|
+
// border.left.color = Number(element.left.color?.a$?.indexed);
|
|
1156
1214
|
}
|
|
1157
1215
|
|
|
1158
1216
|
if (element.right) {
|
|
1159
|
-
border.right.style = element.right.a$.style;
|
|
1160
|
-
border.right.color = Number(element.right.color?.a$?.indexed);
|
|
1217
|
+
// border.right.style = element.right.a$.style;
|
|
1218
|
+
// border.right.color = Number(element.right.color?.a$?.indexed);
|
|
1161
1219
|
}
|
|
1162
1220
|
|
|
1163
1221
|
if (element.top) {
|
|
1164
|
-
border.top.style = element.top.a$.style;
|
|
1165
|
-
border.top.color = Number(element.top.color?.a$?.indexed);
|
|
1222
|
+
// border.top.style = element.top.a$.style;
|
|
1223
|
+
// border.top.color = Number(element.top.color?.a$?.indexed);
|
|
1166
1224
|
}
|
|
1167
1225
|
|
|
1168
1226
|
if (element.bottom) {
|
|
1169
|
-
border.bottom.style = element.bottom.a$.style;
|
|
1170
|
-
border.bottom.color = Number(element.bottom.color?.a$?.indexed);
|
|
1227
|
+
// border.bottom.style = element.bottom.a$.style;
|
|
1228
|
+
// border.bottom.color = Number(element.bottom.color?.a$?.indexed);
|
|
1171
1229
|
}
|
|
1230
|
+
*/
|
|
1231
|
+
|
|
1232
|
+
ElementToBorderEdge(element.left, border.left);
|
|
1233
|
+
ElementToBorderEdge(element.right, border.right);
|
|
1234
|
+
ElementToBorderEdge(element.top, border.top);
|
|
1235
|
+
ElementToBorderEdge(element.bottom, border.bottom);
|
|
1172
1236
|
|
|
1173
1237
|
return border;
|
|
1174
1238
|
|
|
@@ -789,6 +789,13 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
|
|
|
789
789
|
|
|
790
790
|
if (this.active_editor && !this.assume_formula) {
|
|
791
791
|
this.active_editor.node.spellcheck = !(this.text_formula);
|
|
792
|
+
|
|
793
|
+
// if not assuming formula, and it's not a formula, then exit.
|
|
794
|
+
|
|
795
|
+
if (!this.text_formula) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
|
|
792
799
|
}
|
|
793
800
|
|
|
794
801
|
// this is a short-circuit so we don't format the same text twice.
|
|
@@ -835,9 +835,9 @@ export class Grid extends GridBase {
|
|
|
835
835
|
}
|
|
836
836
|
}
|
|
837
837
|
|
|
838
|
-
|
|
838
|
+
/* *
|
|
839
839
|
* specialization: update selection, scroll offset
|
|
840
|
-
|
|
840
|
+
* /
|
|
841
841
|
public Serialize(options: SerializeOptions = {}): SerializedModel {
|
|
842
842
|
|
|
843
843
|
// selection moved to sheet, but it's not "live"; so we need to
|
|
@@ -855,6 +855,7 @@ export class Grid extends GridBase {
|
|
|
855
855
|
return super.Serialize(options);
|
|
856
856
|
|
|
857
857
|
}
|
|
858
|
+
*/
|
|
858
859
|
|
|
859
860
|
/**
|
|
860
861
|
* show or hide headers
|
|
@@ -1830,18 +1831,23 @@ export class Grid extends GridBase {
|
|
|
1830
1831
|
argument_separator: this.parser.argument_separator,
|
|
1831
1832
|
decimal_mark: this.parser.decimal_mark,
|
|
1832
1833
|
}
|
|
1834
|
+
|
|
1835
|
+
this.parser.Save();
|
|
1836
|
+
|
|
1833
1837
|
let convert = false;
|
|
1834
1838
|
|
|
1835
1839
|
if (options.argument_separator === ',' && this.parser.argument_separator !== ArgumentSeparatorType.Comma) {
|
|
1836
|
-
this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
1837
|
-
this.parser.decimal_mark = DecimalMarkType.Period;
|
|
1840
|
+
// this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
1841
|
+
// this.parser.decimal_mark = DecimalMarkType.Period;
|
|
1842
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
1838
1843
|
|
|
1839
1844
|
convert = true;
|
|
1840
1845
|
}
|
|
1841
1846
|
|
|
1842
1847
|
if (options.argument_separator === ';' && this.parser.argument_separator !== ArgumentSeparatorType.Semicolon) {
|
|
1843
|
-
this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
1844
|
-
this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
1848
|
+
// this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
1849
|
+
// this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
1850
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Comma);
|
|
1845
1851
|
|
|
1846
1852
|
convert = true;
|
|
1847
1853
|
}
|
|
@@ -1882,8 +1888,10 @@ export class Grid extends GridBase {
|
|
|
1882
1888
|
|
|
1883
1889
|
// reset
|
|
1884
1890
|
|
|
1885
|
-
this.parser.argument_separator = current.argument_separator;
|
|
1886
|
-
this.parser.decimal_mark = current.decimal_mark;
|
|
1891
|
+
// this.parser.argument_separator = current.argument_separator;
|
|
1892
|
+
// this.parser.decimal_mark = current.decimal_mark;
|
|
1893
|
+
|
|
1894
|
+
this.parser.Restore();
|
|
1887
1895
|
|
|
1888
1896
|
}
|
|
1889
1897
|
|
|
@@ -4439,7 +4447,10 @@ export class Grid extends GridBase {
|
|
|
4439
4447
|
|
|
4440
4448
|
if (this.overlay_editor?.selection) {
|
|
4441
4449
|
const value = this.overlay_editor?.edit_node.textContent || undefined;
|
|
4442
|
-
|
|
4450
|
+
|
|
4451
|
+
// let's support command+shift+enter on mac
|
|
4452
|
+
const array = (event.key === 'Enter' && (event.ctrlKey || (UA.is_mac && event.metaKey)) && event.shiftKey);
|
|
4453
|
+
|
|
4443
4454
|
this.SetInferredType(this.overlay_editor.selection, value, array);
|
|
4444
4455
|
}
|
|
4445
4456
|
|