@trebco/treb 30.16.0 → 31.0.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/api-generator/api-generator.ts +3 -1
- package/api-generator/package.json +2 -1
- package/dist/treb-export-worker.mjs +2 -2
- package/dist/treb-spreadsheet.mjs +13 -13
- package/dist/treb.d.ts +19 -2
- package/package.json +8 -7
- package/treb-base-types/src/font-stack.ts +144 -0
- package/treb-base-types/src/style.ts +121 -11
- package/treb-base-types/src/theme.ts +53 -8
- package/treb-calculator/src/calculator.ts +13 -13
- package/treb-calculator/src/descriptors.ts +12 -4
- package/treb-calculator/src/expression-calculator.ts +17 -4
- package/treb-calculator/src/functions/base-functions.ts +57 -4
- package/treb-calculator/src/functions/statistics-functions.ts +9 -6
- package/treb-calculator/tsconfig.json +11 -0
- package/treb-charts/style/charts.scss +7 -1
- package/treb-data-model/src/annotation.ts +6 -0
- package/treb-data-model/src/data_model.ts +14 -3
- package/treb-data-model/src/sheet.ts +57 -56
- package/treb-embed/markup/toolbar.html +15 -1
- package/treb-embed/src/custom-element/spreadsheet-constructor.ts +38 -0
- package/treb-embed/src/embedded-spreadsheet.ts +119 -29
- package/treb-embed/src/options.ts +3 -0
- package/treb-embed/src/selection-state.ts +1 -0
- package/treb-embed/src/toolbar-message.ts +6 -0
- package/treb-embed/src/types.ts +9 -0
- package/treb-embed/style/defaults.scss +12 -1
- package/treb-embed/style/font-stacks.scss +105 -0
- package/treb-embed/style/layout.scss +1 -0
- package/treb-embed/style/theme-defaults.scss +12 -2
- package/treb-embed/style/toolbar.scss +16 -0
- package/treb-grid/src/editors/overlay_editor.ts +36 -3
- package/treb-grid/src/layout/base_layout.ts +52 -37
- package/treb-grid/src/layout/grid_layout.ts +7 -0
- package/treb-grid/src/render/tile_renderer.ts +154 -148
- package/treb-grid/src/types/grid.ts +188 -54
- package/treb-grid/src/types/grid_events.ts +1 -1
- package/treb-grid/src/types/grid_options.ts +3 -0
- package/treb-grid/src/util/fontmetrics.ts +134 -0
- package/treb-parser/src/parser.ts +12 -3
- package/treb-utils/src/measurement.ts +2 -3
- package/tsproject.json +1 -1
- package/treb-calculator/modern.tsconfig.json +0 -11
- package/treb-grid/src/util/fontmetrics2.ts +0 -182
- package/treb-parser/src/parser.test.ts +0 -298
- /package/treb-embed/{modern.tsconfig.json → tsconfig.json} +0 -0
- /package/treb-export/{modern.tsconfig.json → tsconfig.json} +0 -0
|
@@ -30,11 +30,10 @@ import type {
|
|
|
30
30
|
Complex,
|
|
31
31
|
Color,
|
|
32
32
|
CellStyle,
|
|
33
|
-
IRectangle} from 'treb-base-types';
|
|
33
|
+
IRectangle } from 'treb-base-types';
|
|
34
34
|
|
|
35
35
|
import {
|
|
36
36
|
Area,
|
|
37
|
-
Style,
|
|
38
37
|
Is2DArray,
|
|
39
38
|
Rectangle,
|
|
40
39
|
ValueType,
|
|
@@ -46,6 +45,7 @@ import {
|
|
|
46
45
|
IsComplex,
|
|
47
46
|
TextPartFlag,
|
|
48
47
|
IsArea,
|
|
48
|
+
Style,
|
|
49
49
|
} from 'treb-base-types';
|
|
50
50
|
|
|
51
51
|
import type { ExpressionUnit, RenderOptions, UnitAddress } from 'treb-parser';
|
|
@@ -139,6 +139,14 @@ export class Grid extends GridBase {
|
|
|
139
139
|
// new...
|
|
140
140
|
public headless = false;
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* we're tracking the current selected style so we can use it for new
|
|
144
|
+
* cells. conceptually, if you are typing in a font (stack) like handwritten,
|
|
145
|
+
* and you enter a new cell, you probably want to type in the same font
|
|
146
|
+
* there. so we want to make that happen. TODO: size
|
|
147
|
+
*/
|
|
148
|
+
public readonly edit_state: CellStyle = {};
|
|
149
|
+
|
|
142
150
|
public get scale(): number {
|
|
143
151
|
return this.layout.scale;
|
|
144
152
|
}
|
|
@@ -148,7 +156,7 @@ export class Grid extends GridBase {
|
|
|
148
156
|
this.layout.scale = value;
|
|
149
157
|
this.UpdateLayout();
|
|
150
158
|
this.UpdateAnnotationLayout();
|
|
151
|
-
this.layout.UpdateAnnotation(this.active_sheet.annotations);
|
|
159
|
+
this.layout.UpdateAnnotation(this.active_sheet.annotations, this.theme);
|
|
152
160
|
this.layout.ApplyTheme(this.theme);
|
|
153
161
|
this.overlay_editor?.UpdateScale(value);
|
|
154
162
|
this.tab_bar?.UpdateScale(value);
|
|
@@ -173,6 +181,18 @@ export class Grid extends GridBase {
|
|
|
173
181
|
*/
|
|
174
182
|
public readonly theme: Theme; // ExtendedTheme;
|
|
175
183
|
|
|
184
|
+
/**
|
|
185
|
+
* this was private, which made sense, but there's a case where the
|
|
186
|
+
* client (embedded sheet) wants to check if an annotation is selected.
|
|
187
|
+
* we need to allow that somehow, ideally without any reacharounds.
|
|
188
|
+
*
|
|
189
|
+
* I guess the concern is a client could modify it, but at this point
|
|
190
|
+
* we really only have one client and we trust it. making this public.
|
|
191
|
+
* we could maybe switch to an accessor or have a "is this selected?" method.
|
|
192
|
+
*/
|
|
193
|
+
public selected_annotation?: Annotation;
|
|
194
|
+
|
|
195
|
+
|
|
176
196
|
// --- private members -------------------------------------------------------
|
|
177
197
|
|
|
178
198
|
// testing
|
|
@@ -196,9 +216,6 @@ export class Grid extends GridBase {
|
|
|
196
216
|
/** */
|
|
197
217
|
private editing_selection: GridSelection|undefined;
|
|
198
218
|
|
|
199
|
-
/** */
|
|
200
|
-
private selected_annotation?: Annotation;
|
|
201
|
-
|
|
202
219
|
/** */
|
|
203
220
|
private editing_annotation?: Annotation;
|
|
204
221
|
|
|
@@ -787,16 +804,33 @@ export class Grid extends GridBase {
|
|
|
787
804
|
if (event) {
|
|
788
805
|
this.grid_events.Publish(event);
|
|
789
806
|
}
|
|
790
|
-
|
|
807
|
+
else {
|
|
808
|
+
|
|
809
|
+
// probably a click on the annotation. if it is not already
|
|
810
|
+
// selected, send an event.
|
|
811
|
+
|
|
812
|
+
if (this.selected_annotation !== annotation) {
|
|
813
|
+
this.grid_events.Publish({
|
|
814
|
+
type: 'annotation',
|
|
815
|
+
annotation,
|
|
816
|
+
event: 'select',
|
|
817
|
+
})
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
}
|
|
821
|
+
|
|
791
822
|
if (annotation.data.layout) {
|
|
792
823
|
this.EnsureAddress(annotation.data.layout.br.address, 1);
|
|
793
824
|
}
|
|
794
825
|
|
|
795
826
|
});
|
|
827
|
+
|
|
796
828
|
},
|
|
797
829
|
|
|
798
830
|
focusin: () => {
|
|
799
831
|
|
|
832
|
+
// console.info("AFI");
|
|
833
|
+
|
|
800
834
|
for (const element of this.layout.GetFrozenAnnotations(annotation)) {
|
|
801
835
|
element.classList.add('clone-focus');
|
|
802
836
|
}
|
|
@@ -816,7 +850,9 @@ export class Grid extends GridBase {
|
|
|
816
850
|
|
|
817
851
|
focusout: (event) => {
|
|
818
852
|
|
|
819
|
-
// console.info(
|
|
853
|
+
// console.info("AFO");
|
|
854
|
+
|
|
855
|
+
// console.info('annotation focusout', annotation, event);
|
|
820
856
|
|
|
821
857
|
for (const element of this.layout.GetFrozenAnnotations(annotation)) {
|
|
822
858
|
element.classList.remove('clone-focus');
|
|
@@ -838,10 +874,24 @@ export class Grid extends GridBase {
|
|
|
838
874
|
|
|
839
875
|
}
|
|
840
876
|
else {
|
|
841
|
-
|
|
842
|
-
|
|
877
|
+
|
|
878
|
+
// here's where we need to make a change. if the new focus
|
|
879
|
+
// (the related target) is outside of the sheet hierarchy, we
|
|
880
|
+
// want to persist the selection, or at least remember it.
|
|
881
|
+
|
|
882
|
+
// the next time we focus in on the grid we want to have the
|
|
883
|
+
// opportunity to restore this focus. I think persisting the
|
|
884
|
+
// focus may be a problem? not sure...
|
|
885
|
+
|
|
886
|
+
const focus_in_layout = this.layout.FocusInLayout(event.relatedTarget||undefined);
|
|
887
|
+
|
|
888
|
+
if (focus_in_layout) {
|
|
889
|
+
if (this.selected_annotation === annotation) {
|
|
890
|
+
this.selected_annotation = undefined;
|
|
891
|
+
}
|
|
892
|
+
this.ShowGridSelection();
|
|
843
893
|
}
|
|
844
|
-
|
|
894
|
+
|
|
845
895
|
}
|
|
846
896
|
},
|
|
847
897
|
|
|
@@ -965,7 +1015,7 @@ export class Grid extends GridBase {
|
|
|
965
1015
|
}
|
|
966
1016
|
|
|
967
1017
|
if (add_to_layout) {
|
|
968
|
-
this.layout.AddAnnotation(annotation);
|
|
1018
|
+
this.layout.AddAnnotation(annotation, this.theme);
|
|
969
1019
|
if (annotation.data.layout) {
|
|
970
1020
|
this.EnsureAddress(annotation.data.layout.br.address, 1, toll_events);
|
|
971
1021
|
}
|
|
@@ -1356,7 +1406,7 @@ export class Grid extends GridBase {
|
|
|
1356
1406
|
let composite: Theme = JSON.parse(JSON.stringify(DefaultTheme));
|
|
1357
1407
|
|
|
1358
1408
|
if (this.view_node) {
|
|
1359
|
-
const theme_properties = LoadThemeProperties(this.view_node);
|
|
1409
|
+
const theme_properties = LoadThemeProperties(this.view_node, this.options.support_font_stacks);
|
|
1360
1410
|
composite = {...theme_properties};
|
|
1361
1411
|
}
|
|
1362
1412
|
|
|
@@ -1396,7 +1446,7 @@ export class Grid extends GridBase {
|
|
|
1396
1446
|
// update style for theme
|
|
1397
1447
|
this.StyleDefaultFromTheme();
|
|
1398
1448
|
|
|
1399
|
-
this.active_sheet.UpdateDefaultRowHeight();
|
|
1449
|
+
this.active_sheet.UpdateDefaultRowHeight(this.theme);
|
|
1400
1450
|
this.active_sheet.FlushCellStyles();
|
|
1401
1451
|
|
|
1402
1452
|
this.layout.ApplyTheme(this.theme);
|
|
@@ -1646,10 +1696,62 @@ export class Grid extends GridBase {
|
|
|
1646
1696
|
}
|
|
1647
1697
|
|
|
1648
1698
|
/**
|
|
1649
|
-
*
|
|
1650
|
-
|
|
1699
|
+
*
|
|
1700
|
+
*/
|
|
1701
|
+
public ApplyAnnotationStyle(style: CellStyle = {}, delta = true) {
|
|
1702
|
+
|
|
1703
|
+
// get the logic from committing a formula, I guess? it should run
|
|
1704
|
+
// through the command queue to update any related views.
|
|
1705
|
+
//
|
|
1706
|
+
// actually, FIXME? I think updating annotations generally is not
|
|
1707
|
+
// running through the command queue, that's a larger issue we need
|
|
1708
|
+
// to look at.
|
|
1709
|
+
|
|
1710
|
+
if (this.selected_annotation) {
|
|
1711
|
+
|
|
1712
|
+
const annotation = this.selected_annotation;
|
|
1713
|
+
annotation.data.style = JSON.parse(JSON.stringify(
|
|
1714
|
+
delta ? Style.Composite([annotation.data.style || {}, style]) : style
|
|
1715
|
+
));
|
|
1716
|
+
const node = annotation.view[this.view_index]?.node;
|
|
1717
|
+
|
|
1718
|
+
this.layout.UpdateAnnotation(annotation, this.theme);
|
|
1719
|
+
|
|
1720
|
+
if (node) {
|
|
1721
|
+
node.focus();
|
|
1722
|
+
}
|
|
1723
|
+
this.grid_events.Publish({ type: 'annotation', event: 'update', annotation });
|
|
1724
|
+
this.DelayedRender();
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
/**
|
|
1730
|
+
*
|
|
1731
|
+
*/
|
|
1732
|
+
public AnnotationSelected() {
|
|
1733
|
+
return !!this.selected_annotation;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
/**
|
|
1737
|
+
* focus on the container. not sure what that text parameter was,
|
|
1738
|
+
* legacy? TODO: remove
|
|
1651
1739
|
*/
|
|
1652
|
-
public Focus(text = ''): void {
|
|
1740
|
+
public Focus(text = '', click = false): void {
|
|
1741
|
+
|
|
1742
|
+
if (this.selected_annotation) {
|
|
1743
|
+
|
|
1744
|
+
if (click) {
|
|
1745
|
+
this.selected_annotation = undefined;
|
|
1746
|
+
this.ShowGridSelection();
|
|
1747
|
+
}
|
|
1748
|
+
else {
|
|
1749
|
+
// console.info("reselect annotation...", this.selected_annotation);
|
|
1750
|
+
// console.info("check", this.view_index, this.selected_annotation.view[this.view_index].node);
|
|
1751
|
+
this.selected_annotation.view[this.view_index].node?.focus();
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1653
1755
|
|
|
1654
1756
|
// FIXME: cache a pointer
|
|
1655
1757
|
if (UA.is_mobile) {
|
|
@@ -2465,40 +2567,55 @@ export class Grid extends GridBase {
|
|
|
2465
2567
|
this.theme.grid_cell?.font_size || { unit: 'pt', value: 10 };
|
|
2466
2568
|
}
|
|
2467
2569
|
|
|
2570
|
+
private AutoSizeRow(sheet: Sheet, row: number, allow_shrink = true): void {
|
|
2571
|
+
|
|
2572
|
+
if (!this.tile_renderer) {
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
const current_height = sheet.GetRowHeight(row);
|
|
2577
|
+
let max_height = 0;
|
|
2578
|
+
const padding = 6; // 4 * 2; // FIXME: parameterize
|
|
2579
|
+
|
|
2580
|
+
for (let column = 0; column < sheet.cells.columns; column++) {
|
|
2581
|
+
const cell = sheet.CellData({ row, column });
|
|
2582
|
+
const { height } = this.tile_renderer.MeasureText(cell, sheet.GetColumnWidth(column), 1);
|
|
2583
|
+
max_height = Math.max(max_height, height + padding);
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
if (!allow_shrink) {
|
|
2587
|
+
max_height = Math.max(current_height, max_height);
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
if (max_height > padding + 2) {
|
|
2591
|
+
sheet.SetRowHeight(row, max_height);
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2468
2597
|
private AutoSizeColumn(sheet: Sheet, column: number, allow_shrink = true): void {
|
|
2469
2598
|
|
|
2470
2599
|
if (!this.tile_renderer) {
|
|
2471
2600
|
return;
|
|
2472
2601
|
}
|
|
2473
2602
|
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
let width = 0;
|
|
2603
|
+
const current_width = sheet.GetColumnWidth(column);
|
|
2604
|
+
let max_width = 0;
|
|
2478
2605
|
const padding = 12; // 4 * 2; // FIXME: parameterize
|
|
2479
2606
|
|
|
2480
|
-
if (!allow_shrink) width = sheet.GetColumnWidth(column);
|
|
2481
|
-
|
|
2482
2607
|
for (let row = 0; row < sheet.cells.rows; row++) {
|
|
2483
|
-
|
|
2484
2608
|
const cell = sheet.CellData({ row, column });
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
if (text && text.length) {
|
|
2491
|
-
const metrics = this.tile_renderer.MeasureText(text, Style.Font(cell.style || {}));
|
|
2609
|
+
const { width } = this.tile_renderer.MeasureText(cell, current_width, 1);
|
|
2610
|
+
max_width = Math.max(max_width, width + padding);
|
|
2611
|
+
}
|
|
2492
2612
|
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
// width = Math.max(width, Math.ceil(context.measureText(text).width) + padding);
|
|
2496
|
-
width = Math.max(width, Math.ceil(metrics.width) + padding);
|
|
2497
|
-
}
|
|
2613
|
+
if (!allow_shrink) {
|
|
2614
|
+
max_width = Math.max(current_width, max_width);
|
|
2498
2615
|
}
|
|
2499
2616
|
|
|
2500
|
-
if (
|
|
2501
|
-
sheet.SetColumnWidth(column,
|
|
2617
|
+
if (max_width > padding + 2) {
|
|
2618
|
+
sheet.SetColumnWidth(column, max_width);
|
|
2502
2619
|
}
|
|
2503
2620
|
|
|
2504
2621
|
}
|
|
@@ -3074,7 +3191,7 @@ export class Grid extends GridBase {
|
|
|
3074
3191
|
}
|
|
3075
3192
|
|
|
3076
3193
|
this.render_tiles = this.layout.VisibleTiles();
|
|
3077
|
-
this.layout.UpdateAnnotation(this.active_sheet.annotations);
|
|
3194
|
+
this.layout.UpdateAnnotation(this.active_sheet.annotations, this.theme);
|
|
3078
3195
|
|
|
3079
3196
|
// FIXME: why is this here, as opposed to coming from the command
|
|
3080
3197
|
// exec method? are we doubling up? (...)
|
|
@@ -3369,7 +3486,7 @@ export class Grid extends GridBase {
|
|
|
3369
3486
|
|
|
3370
3487
|
this.layout.UpdateTileHeights(true, row);
|
|
3371
3488
|
this.Repaint(false, true); // repaint full tiles
|
|
3372
|
-
this.layout.UpdateAnnotation(this.active_sheet.annotations);
|
|
3489
|
+
this.layout.UpdateAnnotation(this.active_sheet.annotations, this.theme);
|
|
3373
3490
|
|
|
3374
3491
|
});
|
|
3375
3492
|
|
|
@@ -3451,7 +3568,7 @@ export class Grid extends GridBase {
|
|
|
3451
3568
|
}
|
|
3452
3569
|
*/
|
|
3453
3570
|
if (!this.SelectingArgument()) {
|
|
3454
|
-
this.Focus();
|
|
3571
|
+
this.Focus(undefined, true);
|
|
3455
3572
|
}
|
|
3456
3573
|
|
|
3457
3574
|
|
|
@@ -3592,7 +3709,8 @@ export class Grid extends GridBase {
|
|
|
3592
3709
|
x: tooltip_base + delta,
|
|
3593
3710
|
});
|
|
3594
3711
|
|
|
3595
|
-
//
|
|
3712
|
+
// I don't get how this works. it's not scaling?
|
|
3713
|
+
|
|
3596
3714
|
this.layout.SetColumnWidth(column, width);
|
|
3597
3715
|
|
|
3598
3716
|
for (const { annotation, x } of move_annotation_list) {
|
|
@@ -3679,7 +3797,7 @@ export class Grid extends GridBase {
|
|
|
3679
3797
|
this.ExecCommand({
|
|
3680
3798
|
key: CommandKey.ResizeColumns,
|
|
3681
3799
|
column: columns,
|
|
3682
|
-
width: width / this.scale,
|
|
3800
|
+
width: width, // / this.scale,
|
|
3683
3801
|
});
|
|
3684
3802
|
|
|
3685
3803
|
for (const { annotation } of move_annotation_list) {
|
|
@@ -3710,7 +3828,7 @@ export class Grid extends GridBase {
|
|
|
3710
3828
|
// @see Mousedown_RowHeader
|
|
3711
3829
|
|
|
3712
3830
|
if (!this.SelectingArgument()) {
|
|
3713
|
-
this.Focus();
|
|
3831
|
+
this.Focus(undefined, true);
|
|
3714
3832
|
}
|
|
3715
3833
|
|
|
3716
3834
|
/*
|
|
@@ -4023,7 +4141,7 @@ export class Grid extends GridBase {
|
|
|
4023
4141
|
|
|
4024
4142
|
// not sure why this breaks the formula bar handler
|
|
4025
4143
|
|
|
4026
|
-
this.Focus();
|
|
4144
|
+
this.Focus(undefined, true);
|
|
4027
4145
|
|
|
4028
4146
|
}
|
|
4029
4147
|
|
|
@@ -4711,7 +4829,7 @@ export class Grid extends GridBase {
|
|
|
4711
4829
|
// let's support command+shift+enter on mac
|
|
4712
4830
|
const array = (event.key === 'Enter' && (event.ctrlKey || (UA.is_mac && event.metaKey)) && event.shiftKey);
|
|
4713
4831
|
|
|
4714
|
-
this.SetInferredType(this.overlay_editor.selection, value, array);
|
|
4832
|
+
this.SetInferredType(this.overlay_editor.selection, value, array, undefined, this.overlay_editor.edit_style);
|
|
4715
4833
|
}
|
|
4716
4834
|
|
|
4717
4835
|
this.DismissEditor();
|
|
@@ -5215,7 +5333,7 @@ export class Grid extends GridBase {
|
|
|
5215
5333
|
* of commands. the former is the default for editor commits; the latter
|
|
5216
5334
|
* is used for paste.
|
|
5217
5335
|
*/
|
|
5218
|
-
private SetInferredType(selection: GridSelection, value: string|undefined, array = false, exec = true) {
|
|
5336
|
+
private SetInferredType(selection: GridSelection, value: string|undefined, array = false, exec = true, apply_style?: CellStyle) {
|
|
5219
5337
|
|
|
5220
5338
|
// validation: cannot change part of an array without changing the
|
|
5221
5339
|
// whole array. so check the array. separately, if you are entering
|
|
@@ -5293,10 +5411,19 @@ export class Grid extends GridBase {
|
|
|
5293
5411
|
|
|
5294
5412
|
if (cell.merge_area) target = cell.merge_area.start; // this probably can't happen at this point
|
|
5295
5413
|
|
|
5414
|
+
const commands: Command[] = [];
|
|
5415
|
+
|
|
5416
|
+
if (apply_style) {
|
|
5417
|
+
commands.push({
|
|
5418
|
+
key: CommandKey.UpdateStyle,
|
|
5419
|
+
style: apply_style,
|
|
5420
|
+
area: array ? selection.area : selection.target,
|
|
5421
|
+
})
|
|
5422
|
+
}
|
|
5423
|
+
|
|
5296
5424
|
// first check functions
|
|
5297
5425
|
|
|
5298
5426
|
const is_function = (typeof value === 'string' && value.trim()[0] === '=');
|
|
5299
|
-
const commands: Command[] = [];
|
|
5300
5427
|
|
|
5301
5428
|
if (is_function) {
|
|
5302
5429
|
|
|
@@ -5828,6 +5955,12 @@ export class Grid extends GridBase {
|
|
|
5828
5955
|
|
|
5829
5956
|
let cell_value = cell.value;
|
|
5830
5957
|
|
|
5958
|
+
let edit_state: CellStyle|undefined;
|
|
5959
|
+
|
|
5960
|
+
if (typeof cell_value === 'undefined' && !cell.style?.font_face) {
|
|
5961
|
+
edit_state = this.edit_state;
|
|
5962
|
+
}
|
|
5963
|
+
|
|
5831
5964
|
// if called from a keypress, we will overwrite whatever is in there so we
|
|
5832
5965
|
// can just leave text as is -- except for handling %, which needs to get
|
|
5833
5966
|
// injected.
|
|
@@ -5855,7 +5988,7 @@ export class Grid extends GridBase {
|
|
|
5855
5988
|
// if so, that should go in the method.
|
|
5856
5989
|
|
|
5857
5990
|
// this.overlay_editor?.Edit(selection, rect.Shift(-1, -1).Expand(1, 1), cell, cell_value, event);
|
|
5858
|
-
this.overlay_editor?.Edit(selection, rect.Expand(-1, -1), cell, cell_value, event);
|
|
5991
|
+
this.overlay_editor?.Edit(selection, rect.Expand(-1, -1), cell, cell_value, event, edit_state);
|
|
5859
5992
|
|
|
5860
5993
|
cell.editing = true;
|
|
5861
5994
|
cell.render_clean = [];
|
|
@@ -7388,7 +7521,7 @@ export class Grid extends GridBase {
|
|
|
7388
7521
|
this.Repaint();
|
|
7389
7522
|
|
|
7390
7523
|
if (result.update_annotations_list?.length) {
|
|
7391
|
-
this.layout.UpdateAnnotation(result.update_annotations_list);
|
|
7524
|
+
this.layout.UpdateAnnotation(result.update_annotations_list, this.theme);
|
|
7392
7525
|
for (const annotation of result.resize_annotations_list || []) {
|
|
7393
7526
|
const view = annotation.view[this.view_index];
|
|
7394
7527
|
if (view?.resize_callback) {
|
|
@@ -7472,7 +7605,7 @@ export class Grid extends GridBase {
|
|
|
7472
7605
|
this.DelayedRender(true, undefined, true);
|
|
7473
7606
|
|
|
7474
7607
|
if (result.update_annotations_list?.length) {
|
|
7475
|
-
this.layout.UpdateAnnotation(result.update_annotations_list);
|
|
7608
|
+
this.layout.UpdateAnnotation(result.update_annotations_list, this.theme);
|
|
7476
7609
|
for (const annotation of result.resize_annotations_list || []) {
|
|
7477
7610
|
const view = annotation.view[this.view_index];
|
|
7478
7611
|
if (view?.resize_callback) {
|
|
@@ -7552,7 +7685,7 @@ export class Grid extends GridBase {
|
|
|
7552
7685
|
this.Repaint(false, true); // repaint full tiles
|
|
7553
7686
|
}
|
|
7554
7687
|
|
|
7555
|
-
this.layout.UpdateAnnotation(this.active_sheet.annotations);
|
|
7688
|
+
this.layout.UpdateAnnotation(this.active_sheet.annotations, this.theme);
|
|
7556
7689
|
this.RenderSelections();
|
|
7557
7690
|
|
|
7558
7691
|
}
|
|
@@ -7618,7 +7751,8 @@ export class Grid extends GridBase {
|
|
|
7618
7751
|
}
|
|
7619
7752
|
}
|
|
7620
7753
|
|
|
7621
|
-
sheet.AutoSizeRow(entry, this.theme
|
|
7754
|
+
// sheet.AutoSizeRow(entry, this.theme, shrink, this.scale);
|
|
7755
|
+
this.AutoSizeRow(sheet, entry, shrink);
|
|
7622
7756
|
}
|
|
7623
7757
|
}
|
|
7624
7758
|
else {
|
|
@@ -7653,7 +7787,7 @@ export class Grid extends GridBase {
|
|
|
7653
7787
|
this.Repaint(false, true); // repaint full tiles
|
|
7654
7788
|
}
|
|
7655
7789
|
|
|
7656
|
-
this.layout.UpdateAnnotation(this.active_sheet.annotations);
|
|
7790
|
+
this.layout.UpdateAnnotation(this.active_sheet.annotations, this.theme);
|
|
7657
7791
|
this.RenderSelections();
|
|
7658
7792
|
|
|
7659
7793
|
}
|
|
@@ -89,7 +89,7 @@ export interface StructureEvent {
|
|
|
89
89
|
export interface AnnotationEvent {
|
|
90
90
|
type: 'annotation';
|
|
91
91
|
annotation?: Annotation;
|
|
92
|
-
event?: 'move'|'resize'|'create'|'delete'|'update';
|
|
92
|
+
event?: 'move'|'resize'|'create'|'delete'|'update'|'select';
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
export interface HyperlinkCellEventData {
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of TREB.
|
|
3
|
+
*
|
|
4
|
+
* TREB is free software: you can redistribute it and/or modify it under the
|
|
5
|
+
* terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
* Foundation, either version 3 of the License, or (at your option) any
|
|
7
|
+
* later version.
|
|
8
|
+
*
|
|
9
|
+
* TREB is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
10
|
+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
11
|
+
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
12
|
+
* details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License along
|
|
15
|
+
* with TREB. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*
|
|
17
|
+
* Copyright 2022-2024 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
* as of 2024 it looks like we can use proper fontmetrics in all browsers,
|
|
24
|
+
* so we'll switch to that. this is a replacement for the old fontmetrics
|
|
25
|
+
* (which read pixles), and any other font measurement utils.
|
|
26
|
+
*
|
|
27
|
+
* as far as I can tell the alphabatic baseline is constant, and reliable,
|
|
28
|
+
* in all browsers. so let's use that. other baselines seem to be slightly
|
|
29
|
+
* different, at least in firefox.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
export interface FontMetrics {
|
|
33
|
+
|
|
34
|
+
/** from textmetrics, this is the font ascent (max, essentially) */
|
|
35
|
+
ascent: number;
|
|
36
|
+
|
|
37
|
+
/** from textmetrics, this is the font descent (max) */
|
|
38
|
+
descent: number;
|
|
39
|
+
|
|
40
|
+
/** total height for the font (line height). just ascent + descent. should we +1 for baseline? */
|
|
41
|
+
height: number;
|
|
42
|
+
|
|
43
|
+
/** width of one paren */
|
|
44
|
+
paren: number;
|
|
45
|
+
|
|
46
|
+
/** width of one hash (#) character */
|
|
47
|
+
hash: number;
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// these two will be engine global, which is what we want
|
|
52
|
+
const cache: Map<string, FontMetrics> = new Map();
|
|
53
|
+
let canvas: HTMLCanvasElement | undefined;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* get font metrics for the given font, which includes a size.
|
|
57
|
+
* precompute the size, we're not doing that anymore.
|
|
58
|
+
*/
|
|
59
|
+
export const Get = (font: string, variants?: string) => {
|
|
60
|
+
|
|
61
|
+
const key = font;
|
|
62
|
+
// console.info({key});
|
|
63
|
+
|
|
64
|
+
let metrics = cache.get(key);
|
|
65
|
+
|
|
66
|
+
if (metrics) {
|
|
67
|
+
return metrics;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
metrics = Measure(font, variants);
|
|
71
|
+
cache.set(key, metrics);
|
|
72
|
+
return metrics;
|
|
73
|
+
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* flush cache. this should be called when you update the theme
|
|
78
|
+
*/
|
|
79
|
+
export const Flush = () => {
|
|
80
|
+
cache.clear();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* do the actual measurement
|
|
85
|
+
*/
|
|
86
|
+
const Measure = (font: string, variants?: string): FontMetrics => {
|
|
87
|
+
|
|
88
|
+
if (!canvas) {
|
|
89
|
+
if (typeof document !== 'undefined') {
|
|
90
|
+
canvas = document.createElement('canvas');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (canvas) {
|
|
95
|
+
if (variants) {
|
|
96
|
+
canvas.style.fontVariant = variants;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
canvas.style.fontVariant = '';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const context = canvas?.getContext('2d', { alpha: false });
|
|
104
|
+
if (!context) {
|
|
105
|
+
throw new Error('invalid context');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
context.textBaseline = 'alphabetic';
|
|
109
|
+
context.textAlign = 'center';
|
|
110
|
+
context.font = font;
|
|
111
|
+
|
|
112
|
+
let metrics = context.measureText('(');
|
|
113
|
+
const paren = metrics.actualBoundingBoxRight + metrics.actualBoundingBoxLeft;
|
|
114
|
+
|
|
115
|
+
metrics = context.measureText('#');
|
|
116
|
+
const hash = metrics.actualBoundingBoxRight + metrics.actualBoundingBoxLeft;
|
|
117
|
+
|
|
118
|
+
metrics = context.measureText('Mljy!');
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
|
|
122
|
+
paren,
|
|
123
|
+
hash,
|
|
124
|
+
|
|
125
|
+
ascent: metrics.fontBoundingBoxAscent,
|
|
126
|
+
descent: metrics.fontBoundingBoxDescent,
|
|
127
|
+
|
|
128
|
+
height: (metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent),
|
|
129
|
+
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
|
|
@@ -209,8 +209,9 @@ export class Parser {
|
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
211
|
* FIXME: why is this a class member? at a minimum it could be static
|
|
212
|
+
* FIXME: why are we doing this with a regex?
|
|
212
213
|
*/
|
|
213
|
-
protected r1c1_regex = /[rR]((?:\[[-+]{0,1}\d+\]|\d
|
|
214
|
+
protected r1c1_regex = /[rR]((?:\[[-+]{0,1}\d+\]|\d*))[cC]((?:\[[-+]{0,1}\d+\]|\d*))$/;
|
|
214
215
|
|
|
215
216
|
/**
|
|
216
217
|
* internal argument separator, as a number. this is set internally on
|
|
@@ -2556,17 +2557,25 @@ export class Parser {
|
|
|
2556
2557
|
r1c1.offset_row = true;
|
|
2557
2558
|
r1c1.row = Number(match[1].substring(1, match[1].length - 1));
|
|
2558
2559
|
}
|
|
2559
|
-
else { // absolute
|
|
2560
|
+
else if (match[1]){ // absolute
|
|
2560
2561
|
r1c1.row = Number(match[1]) - 1; // R1C1 is 1-based
|
|
2561
2562
|
}
|
|
2563
|
+
else {
|
|
2564
|
+
r1c1.offset_row = true;
|
|
2565
|
+
r1c1.row = 0;
|
|
2566
|
+
}
|
|
2562
2567
|
|
|
2563
2568
|
if (match[2][0] === '[') { // relative
|
|
2564
2569
|
r1c1.offset_column = true;
|
|
2565
2570
|
r1c1.column = Number(match[2].substring(1, match[2].length - 1));
|
|
2566
2571
|
}
|
|
2567
|
-
else { // absolute
|
|
2572
|
+
else if (match[2]) { // absolute
|
|
2568
2573
|
r1c1.column = Number(match[2]) - 1; // R1C1 is 1-based
|
|
2569
2574
|
}
|
|
2575
|
+
else {
|
|
2576
|
+
r1c1.offset_column = true;
|
|
2577
|
+
r1c1.column = 0;
|
|
2578
|
+
}
|
|
2570
2579
|
|
|
2571
2580
|
return r1c1;
|
|
2572
2581
|
|
|
@@ -120,7 +120,7 @@ export class Measurement {
|
|
|
120
120
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
/**
|
|
124
124
|
* check if font is loaded, based on the theory that the alternatives
|
|
125
125
|
* will be different sizes. note that this probably doesn't test weights
|
|
126
126
|
* or italics properly, as those can be emulated without the specific face.
|
|
@@ -131,7 +131,7 @@ export class Measurement {
|
|
|
131
131
|
* @param font_face
|
|
132
132
|
* @param italic
|
|
133
133
|
* @param bold
|
|
134
|
-
|
|
134
|
+
*/
|
|
135
135
|
public static FontLoaded(font_face: string, italic = false, weight = 400): boolean {
|
|
136
136
|
const face = `${italic ? 'italic' : ''} ${weight} 20pt ${font_face}`;
|
|
137
137
|
const m1 = this.MeasureText(`${face}, sans-serif`, `check font`);
|
|
@@ -139,7 +139,6 @@ export class Measurement {
|
|
|
139
139
|
const m3 = this.MeasureText(`${face}, monospace`, `check font`);
|
|
140
140
|
return (m1.width === m2.width && m2.width === m3.width);
|
|
141
141
|
}
|
|
142
|
-
*/
|
|
143
142
|
|
|
144
143
|
/**
|
|
145
144
|
* measure width, height of text, accounting for rotation
|