@trebco/treb 29.8.3 → 30.1.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.
- package/dist/treb-spreadsheet-light.mjs +11 -11
- package/dist/treb-spreadsheet.mjs +15 -15
- package/dist/treb.d.ts +11 -1
- package/eslint.config.js +9 -0
- package/package.json +1 -1
- package/treb-base-types/src/area-utils.ts +60 -0
- package/treb-base-types/src/area.ts +11 -0
- package/treb-base-types/src/cell.ts +6 -1
- package/treb-base-types/src/cells.ts +38 -7
- package/treb-base-types/src/index.ts +2 -0
- package/treb-calculator/src/calculator.ts +274 -4
- package/treb-calculator/src/dag/array-vertex.ts +0 -10
- package/treb-calculator/src/dag/graph.ts +118 -77
- package/treb-calculator/src/dag/spreadsheet_vertex.ts +38 -9
- package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +1 -0
- package/treb-calculator/src/expression-calculator.ts +7 -2
- package/treb-calculator/src/function-error.ts +6 -0
- package/treb-charts/src/chart-functions.ts +39 -5
- package/treb-charts/src/chart-types.ts +23 -0
- package/treb-charts/src/chart-utils.ts +165 -2
- package/treb-charts/src/chart.ts +6 -1
- package/treb-charts/src/default-chart-renderer.ts +70 -1
- package/treb-charts/src/index.ts +1 -0
- package/treb-charts/src/renderer.ts +95 -2
- package/treb-charts/style/charts.scss +41 -0
- package/treb-embed/src/embedded-spreadsheet.ts +11 -4
- package/treb-embed/src/options.ts +8 -0
- package/treb-embed/style/dark-theme.scss +4 -0
- package/treb-embed/style/grid.scss +15 -0
- package/treb-embed/style/z-index.scss +3 -0
- package/treb-export/src/import2.ts +9 -0
- package/treb-export/src/workbook2.ts +67 -3
- package/treb-grid/src/editors/editor.ts +12 -5
- package/treb-grid/src/layout/base_layout.ts +41 -0
- package/treb-grid/src/types/grid.ts +72 -28
- package/treb-parser/src/parser-types.ts +3 -0
- package/treb-parser/src/parser.ts +21 -2
- package/treb-utils/src/serialize_html.ts +35 -10
|
@@ -49,7 +49,7 @@ export const ConditionalFormatOperators: Record<string, string> = {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
export enum ChartType {
|
|
52
|
-
Unknown = 0, Column, Bar, Line, Scatter, Donut, Pie, Bubble
|
|
52
|
+
Unknown = 0, Column, Bar, Line, Scatter, Donut, Pie, Bubble, Box
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export interface ChartSeries {
|
|
@@ -345,7 +345,14 @@ export class Workbook {
|
|
|
345
345
|
to: ParseAnchor(anchor_node['xdr:to']),
|
|
346
346
|
};
|
|
347
347
|
|
|
348
|
-
|
|
348
|
+
let chart_reference = XMLUtils.FindAll(anchor_node, `xdr:graphicFrame/a:graphic/a:graphicData/c:chart`)[0];
|
|
349
|
+
|
|
350
|
+
// check for an "alternate content" chart/chartex (wtf ms). we're
|
|
351
|
+
// supporting this for box charts only (atm)
|
|
352
|
+
|
|
353
|
+
if (!chart_reference) {
|
|
354
|
+
chart_reference = XMLUtils.FindAll(anchor_node, `mc:AlternateContent/mc:Choice/xdr:graphicFrame/a:graphic/a:graphicData/cx:chart`)[0];
|
|
355
|
+
}
|
|
349
356
|
|
|
350
357
|
if (chart_reference && chart_reference.a$ && chart_reference.a$['r:id']) {
|
|
351
358
|
const result: AnchoredChartDescription = { type: 'chart', anchor };
|
|
@@ -659,7 +666,64 @@ export class Workbook {
|
|
|
659
666
|
if (node) {
|
|
660
667
|
result.type = ChartType.Bubble;
|
|
661
668
|
result.series = ParseSeries(node, ChartType.Bubble);
|
|
662
|
-
console.info("Bubble series?", result.series);
|
|
669
|
+
// console.info("Bubble series?", result.series);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (!node) {
|
|
674
|
+
|
|
675
|
+
// box plot uses "extended chart" which is totally different... but we
|
|
676
|
+
// might need it again later? for the time being it's just inlined
|
|
677
|
+
|
|
678
|
+
const ex_series = XMLUtils.FindAll(xml, 'cx:chartSpace/cx:chart/cx:plotArea/cx:plotAreaRegion/cx:series');
|
|
679
|
+
if (ex_series?.length) {
|
|
680
|
+
if (ex_series.every(test => test.__layoutId === 'boxWhisker')) {
|
|
681
|
+
result.type = ChartType.Box;
|
|
682
|
+
result.series = [];
|
|
683
|
+
const data = XMLUtils.FindAll(xml, 'cx:chartSpace/cx:chartData/cx:data'); // /cx:data/cx:numDim/cx:f');
|
|
684
|
+
|
|
685
|
+
// console.info({ex_series, data})
|
|
686
|
+
|
|
687
|
+
for (const entry of ex_series) {
|
|
688
|
+
|
|
689
|
+
const series: ChartSeries = {};
|
|
690
|
+
|
|
691
|
+
const id = Number(entry['cx:dataId']?.['__val']);
|
|
692
|
+
for (const data_series of data) {
|
|
693
|
+
if (Number(data_series.__id) === id) {
|
|
694
|
+
series.values = data_series['cx:numDim']?.['cx:f'] || '';
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const label = XMLUtils.FindAll(entry, 'cx:tx/cx:txData');
|
|
700
|
+
if (label) {
|
|
701
|
+
if (label[0]?.['cx:f']) {
|
|
702
|
+
series.title = label[0]['cx:f'];
|
|
703
|
+
}
|
|
704
|
+
else if (label[0]?.['cx:v']) {
|
|
705
|
+
series.title = '"' + label[0]['cx:v'] + '"';
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const title = XMLUtils.FindAll(xml, 'cx:chartSpace/cx:chart/cx:title/cx:tx/cx:txData');
|
|
710
|
+
if (title) {
|
|
711
|
+
if (title[0]?.['cx:f']) {
|
|
712
|
+
result.title = title[0]['cx:f'];
|
|
713
|
+
}
|
|
714
|
+
else if (title[0]?.['cx:v']) {
|
|
715
|
+
result.title = '"' + title[0]['cx:v'] + '"';
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
result.series.push(series);
|
|
720
|
+
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// console.info({result});
|
|
724
|
+
return result;
|
|
725
|
+
|
|
726
|
+
}
|
|
663
727
|
}
|
|
664
728
|
}
|
|
665
729
|
|
|
@@ -726,7 +726,8 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
|
|
|
726
726
|
const list: Set<string> = new Set();
|
|
727
727
|
|
|
728
728
|
// for the result, map of reference to normalized address label
|
|
729
|
-
const map: Map<ExpressionUnit, string> = new Map();
|
|
729
|
+
// const map: Map<ExpressionUnit, string> = new Map();
|
|
730
|
+
const map: Map<string, string> = new Map();
|
|
730
731
|
|
|
731
732
|
for (const entry of reference_list) {
|
|
732
733
|
|
|
@@ -743,12 +744,14 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
|
|
|
743
744
|
}
|
|
744
745
|
|
|
745
746
|
// but keep a map
|
|
746
|
-
map.set(entry, label);
|
|
747
|
+
map.set(entry.label, label);
|
|
747
748
|
|
|
748
749
|
}
|
|
749
750
|
|
|
750
751
|
this.UpdateReferences(descriptor, references);
|
|
751
752
|
|
|
753
|
+
// console.info({map});
|
|
754
|
+
|
|
752
755
|
return map;
|
|
753
756
|
|
|
754
757
|
}
|
|
@@ -851,7 +854,7 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
|
|
|
851
854
|
if (parse_result.expression) {
|
|
852
855
|
|
|
853
856
|
const normalized_labels = this.UpdateDependencies(descriptor, parse_result);
|
|
854
|
-
|
|
857
|
+
|
|
855
858
|
// the parser will drop a leading = character, so be
|
|
856
859
|
// sure to add that back if necessary
|
|
857
860
|
|
|
@@ -925,8 +928,12 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
|
|
|
925
928
|
|
|
926
929
|
switch (unit.type) {
|
|
927
930
|
case 'identifier':
|
|
928
|
-
|
|
931
|
+
// FIXME: canonicalize (optionally)
|
|
932
|
+
label = text.substring(pos, pos + unit.name.length);
|
|
933
|
+
reference = normalized_labels.get(unit.name) || '';
|
|
934
|
+
break;
|
|
929
935
|
|
|
936
|
+
case 'call':
|
|
930
937
|
// FIXME: canonicalize (optionally)
|
|
931
938
|
label = text.substring(pos, pos + unit.name.length);
|
|
932
939
|
break;
|
|
@@ -944,7 +951,7 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
|
|
|
944
951
|
case 'address':
|
|
945
952
|
case 'range':
|
|
946
953
|
case 'structured-reference':
|
|
947
|
-
reference = normalized_labels.get(unit) || '???';
|
|
954
|
+
reference = normalized_labels.get(unit.label) || '???';
|
|
948
955
|
|
|
949
956
|
/*
|
|
950
957
|
{
|
|
@@ -149,6 +149,7 @@ export abstract class BaseLayout {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
protected dropdown_caret!: SVGSVGElement;
|
|
152
|
+
protected spill_border!: SVGSVGElement;
|
|
152
153
|
|
|
153
154
|
/** we have to disable mock selection for IE or it breaks key handling */
|
|
154
155
|
private trident = ((typeof navigator !== 'undefined') &&
|
|
@@ -207,6 +208,9 @@ export abstract class BaseLayout {
|
|
|
207
208
|
this.mask = DOM.Div('treb-mouse-mask');
|
|
208
209
|
this.tooltip = DOM.Div('treb-tooltip');
|
|
209
210
|
|
|
211
|
+
this.spill_border = DOM.SVG('svg', 'treb-spill-border');
|
|
212
|
+
this.spill_border.tabIndex = -1;
|
|
213
|
+
|
|
210
214
|
this.dropdown_caret = DOM.SVG('svg', 'treb-dropdown-caret');
|
|
211
215
|
this.dropdown_caret.setAttribute('viewBox', '0 0 24 24');
|
|
212
216
|
this.dropdown_caret.tabIndex = -1;
|
|
@@ -1099,6 +1103,10 @@ export abstract class BaseLayout {
|
|
|
1099
1103
|
|
|
1100
1104
|
// FIXME: -> instance specific, b/c trident
|
|
1101
1105
|
|
|
1106
|
+
if (!this.spill_border.parentElement) {
|
|
1107
|
+
container.appendChild(this.spill_border);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1102
1110
|
if (!this.dropdown_caret.parentElement) {
|
|
1103
1111
|
container.appendChild(this.dropdown_caret);
|
|
1104
1112
|
}
|
|
@@ -1431,6 +1439,39 @@ export abstract class BaseLayout {
|
|
|
1431
1439
|
this.dropdown_caret_visible = true;
|
|
1432
1440
|
}
|
|
1433
1441
|
|
|
1442
|
+
public ShowSpillBorder(area?: IArea) {
|
|
1443
|
+
this.spill_border.textContent = '';
|
|
1444
|
+
if (area) {
|
|
1445
|
+
const resolved = new Area(area.start, area.end);
|
|
1446
|
+
|
|
1447
|
+
let target_rect = this.OffsetCellAddressToRectangle(resolved.start);
|
|
1448
|
+
|
|
1449
|
+
if (resolved.count > 1) {
|
|
1450
|
+
target_rect = target_rect.Combine(this.OffsetCellAddressToRectangle(resolved.end));
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
target_rect = target_rect.Shift(
|
|
1454
|
+
this.header_size.width, this.header_size.height);
|
|
1455
|
+
|
|
1456
|
+
this.spill_border.style.display = 'block';
|
|
1457
|
+
this.spill_border.style.top = (target_rect.top - 5).toString();
|
|
1458
|
+
this.spill_border.style.left = (target_rect.left - 5).toString();
|
|
1459
|
+
this.spill_border.style.width = (target_rect.width + 10).toString();
|
|
1460
|
+
this.spill_border.style.height = (target_rect.height + 10).toString();
|
|
1461
|
+
|
|
1462
|
+
const rect = this.DOM.SVG('rect', undefined, this.spill_border);
|
|
1463
|
+
rect.setAttribute('x', '4.5');
|
|
1464
|
+
rect.setAttribute('y', '4.5');
|
|
1465
|
+
rect.setAttribute('width', (target_rect.width + 1).toString());
|
|
1466
|
+
rect.setAttribute('height', (target_rect.height + 1).toString());
|
|
1467
|
+
|
|
1468
|
+
}
|
|
1469
|
+
else {
|
|
1470
|
+
this.spill_border.style.display = 'none';
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1434
1475
|
public HideDropdownCaret(): void {
|
|
1435
1476
|
if (this.dropdown_caret_visible) {
|
|
1436
1477
|
// this.dropdown_caret.classList.remove('active');
|
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
IsArea,
|
|
49
49
|
} from 'treb-base-types';
|
|
50
50
|
|
|
51
|
-
import type { ExpressionUnit, UnitAddress } from 'treb-parser';
|
|
51
|
+
import type { ExpressionUnit, RenderOptions, UnitAddress } from 'treb-parser';
|
|
52
52
|
import {
|
|
53
53
|
DecimalMarkType,
|
|
54
54
|
ArgumentSeparatorType,
|
|
@@ -311,6 +311,13 @@ export class Grid extends GridBase {
|
|
|
311
311
|
empty: true,
|
|
312
312
|
};
|
|
313
313
|
|
|
314
|
+
/** reusing type. FIXME? we don't need a target */
|
|
315
|
+
private readonly spill_selection: GridSelection = {
|
|
316
|
+
target: { row: 0, column: 0 },
|
|
317
|
+
area: new Area({ row: 0, column: 0 }),
|
|
318
|
+
empty: true,
|
|
319
|
+
};
|
|
320
|
+
|
|
314
321
|
/**
|
|
315
322
|
* active selection when selecting arguments (while editing)
|
|
316
323
|
*/
|
|
@@ -1353,6 +1360,7 @@ export class Grid extends GridBase {
|
|
|
1353
1360
|
*/
|
|
1354
1361
|
public UpdateLayout(): void {
|
|
1355
1362
|
this.layout.UpdateTiles();
|
|
1363
|
+
this.layout.UpdateContentsSize();
|
|
1356
1364
|
this.render_tiles = this.layout.VisibleTiles();
|
|
1357
1365
|
this.Repaint(true);
|
|
1358
1366
|
}
|
|
@@ -4614,9 +4622,17 @@ export class Grid extends GridBase {
|
|
|
4614
4622
|
}
|
|
4615
4623
|
|
|
4616
4624
|
for (let row = start; row >= 0 && row < target_rows; row += step, offset += step, pattern_increment += pattern) {
|
|
4625
|
+
|
|
4626
|
+
const render_options: Partial<RenderOptions> = {
|
|
4627
|
+
offset: transposed ? {
|
|
4628
|
+
rows: 0, columns: offset,
|
|
4629
|
+
} : {
|
|
4630
|
+
rows: offset, columns: 0,
|
|
4631
|
+
}
|
|
4632
|
+
};
|
|
4633
|
+
|
|
4617
4634
|
if (translate) {
|
|
4618
|
-
data[row][column] = '=' + this.parser.Render(translate,
|
|
4619
|
-
offset: { rows: offset, columns: 0 }});
|
|
4635
|
+
data[row][column] = '=' + this.parser.Render(translate, render_options);
|
|
4620
4636
|
}
|
|
4621
4637
|
else {
|
|
4622
4638
|
const cell = cells[source_row][column];
|
|
@@ -5091,13 +5107,16 @@ export class Grid extends GridBase {
|
|
|
5091
5107
|
|
|
5092
5108
|
const cell = this.active_sheet.CellData(this.primary_selection.target);
|
|
5093
5109
|
|
|
5094
|
-
if (!cell || (!cell.area && !cell.table)) {
|
|
5110
|
+
if (!cell || (!cell.area && !cell.table && !cell.spill)) {
|
|
5095
5111
|
return;
|
|
5096
5112
|
}
|
|
5097
5113
|
|
|
5098
5114
|
if (cell.area) {
|
|
5099
5115
|
this.Select(this.primary_selection, cell.area, cell.area.start);
|
|
5100
5116
|
}
|
|
5117
|
+
if (cell.spill) {
|
|
5118
|
+
this.Select(this.primary_selection, cell.spill, cell.spill.start);
|
|
5119
|
+
}
|
|
5101
5120
|
if (cell.table) {
|
|
5102
5121
|
const area = new Area(cell.table.area.start, cell.table.area.end);
|
|
5103
5122
|
this.Select(this.primary_selection, area, area.start);
|
|
@@ -5115,7 +5134,12 @@ export class Grid extends GridBase {
|
|
|
5115
5134
|
|
|
5116
5135
|
const show_primary_selection = this.hide_selection ? false :
|
|
5117
5136
|
(!this.editing_state) || (this.editing_cell.sheet_id === this.active_sheet.id);
|
|
5118
|
-
|
|
5137
|
+
|
|
5138
|
+
const data = this.primary_selection.empty ? undefined :
|
|
5139
|
+
this.active_sheet.CellData(this.primary_selection.target);
|
|
5140
|
+
|
|
5141
|
+
this.layout.ShowSpillBorder(data?.spill);
|
|
5142
|
+
|
|
5119
5143
|
this.selection_renderer?.RenderSelections(show_primary_selection, rerender);
|
|
5120
5144
|
}
|
|
5121
5145
|
|
|
@@ -5164,7 +5188,7 @@ export class Grid extends GridBase {
|
|
|
5164
5188
|
const cells = this.active_sheet.cells;
|
|
5165
5189
|
|
|
5166
5190
|
let cell = cells.GetCell(selection.target, false);
|
|
5167
|
-
if (!cell || (cell.type === ValueType.undefined && !cell.area)) {
|
|
5191
|
+
if (!cell || (cell.type === ValueType.undefined && !cell.area && !cell.spill)) {
|
|
5168
5192
|
return false;
|
|
5169
5193
|
}
|
|
5170
5194
|
|
|
@@ -5198,20 +5222,20 @@ export class Grid extends GridBase {
|
|
|
5198
5222
|
if (rows) {
|
|
5199
5223
|
for (let column = selection.area.start.column; !has_value && column <= selection.area.end.column; column++) {
|
|
5200
5224
|
cell = cells.GetCell({ row: test.row, column }, false);
|
|
5201
|
-
has_value = has_value || (!!cell && (cell.type !== ValueType.undefined || !!cell.area));
|
|
5225
|
+
has_value = has_value || (!!cell && (cell.type !== ValueType.undefined || !!cell.area || !!cell.spill));
|
|
5202
5226
|
if (!has_value && cell && cell.merge_area) {
|
|
5203
5227
|
cell = cells.GetCell(cell.merge_area.start, false);
|
|
5204
|
-
has_value = has_value || (!!cell && (cell.type !== ValueType.undefined || !!cell.area));
|
|
5228
|
+
has_value = has_value || (!!cell && (cell.type !== ValueType.undefined || !!cell.area || !!cell.spill));
|
|
5205
5229
|
}
|
|
5206
5230
|
}
|
|
5207
5231
|
}
|
|
5208
5232
|
else {
|
|
5209
5233
|
for (let row = selection.area.start.row; !has_value && row <= selection.area.end.row; row++) {
|
|
5210
5234
|
cell = cells.GetCell({ row, column: test.column }, false);
|
|
5211
|
-
has_value = has_value || (!!cell && (cell.type !== ValueType.undefined || !!cell.area));
|
|
5235
|
+
has_value = has_value || (!!cell && (cell.type !== ValueType.undefined || !!cell.area || !!cell.spill));
|
|
5212
5236
|
if (!has_value && cell && cell.merge_area) {
|
|
5213
5237
|
cell = cells.GetCell(cell.merge_area.start, false);
|
|
5214
|
-
has_value = has_value || (!!cell && (cell.type !== ValueType.undefined || !!cell.area));
|
|
5238
|
+
has_value = has_value || (!!cell && (cell.type !== ValueType.undefined || !!cell.area || !!cell.spill));
|
|
5215
5239
|
}
|
|
5216
5240
|
}
|
|
5217
5241
|
}
|
|
@@ -6557,15 +6581,24 @@ export class Grid extends GridBase {
|
|
|
6557
6581
|
*/
|
|
6558
6582
|
private UpdateFormulaBarFormula(override?: string) {
|
|
6559
6583
|
|
|
6560
|
-
|
|
6584
|
+
this.layout.HideDropdownCaret();
|
|
6585
|
+
|
|
6586
|
+
// NOTE: this means we won't set validation carets... that needs
|
|
6587
|
+
// to be handled separately (FIXME)
|
|
6588
|
+
|
|
6589
|
+
// if (!this.formula_bar) { return; }
|
|
6561
6590
|
|
|
6562
6591
|
if (override) {
|
|
6563
|
-
this.formula_bar
|
|
6592
|
+
if (this.formula_bar) {
|
|
6593
|
+
this.formula_bar.formula = override;
|
|
6594
|
+
}
|
|
6564
6595
|
return;
|
|
6565
6596
|
}
|
|
6566
6597
|
|
|
6567
6598
|
if (this.primary_selection.empty) {
|
|
6568
|
-
this.formula_bar
|
|
6599
|
+
if (this.formula_bar) {
|
|
6600
|
+
this.formula_bar.formula = '';
|
|
6601
|
+
}
|
|
6569
6602
|
}
|
|
6570
6603
|
else {
|
|
6571
6604
|
let data = this.active_sheet.CellData(this.primary_selection.target);
|
|
@@ -6581,7 +6614,10 @@ export class Grid extends GridBase {
|
|
|
6581
6614
|
}
|
|
6582
6615
|
}
|
|
6583
6616
|
|
|
6584
|
-
this.formula_bar
|
|
6617
|
+
if (this.formula_bar) {
|
|
6618
|
+
this.formula_bar.editable = !data.style?.locked;
|
|
6619
|
+
}
|
|
6620
|
+
|
|
6585
6621
|
const value = this.NormalizeCellValue(data);
|
|
6586
6622
|
|
|
6587
6623
|
// this isn't necessarily the best place for this, except that
|
|
@@ -6611,17 +6647,19 @@ export class Grid extends GridBase {
|
|
|
6611
6647
|
}
|
|
6612
6648
|
|
|
6613
6649
|
}
|
|
6614
|
-
else {
|
|
6615
|
-
this.layout.HideDropdownCaret();
|
|
6616
|
-
}
|
|
6617
6650
|
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6651
|
+
if (this.formula_bar) {
|
|
6652
|
+
|
|
6653
|
+
// add braces for area
|
|
6654
|
+
if (data.area) {
|
|
6655
|
+
this.formula_bar.formula = '{' + (value || '') + '}';
|
|
6656
|
+
}
|
|
6657
|
+
else {
|
|
6658
|
+
this.formula_bar.formula = (typeof value !== 'undefined') ? value.toString() : ''; // value || ''; // what about zero?
|
|
6659
|
+
}
|
|
6660
|
+
|
|
6624
6661
|
}
|
|
6662
|
+
|
|
6625
6663
|
}
|
|
6626
6664
|
|
|
6627
6665
|
}
|
|
@@ -6869,14 +6907,20 @@ export class Grid extends GridBase {
|
|
|
6869
6907
|
|
|
6870
6908
|
if (view && view.node) {
|
|
6871
6909
|
// this.selected_annotation.node.innerHTML;
|
|
6872
|
-
const node = view.node.firstChild;
|
|
6910
|
+
const node = view.node.firstChild?.firstChild;
|
|
6911
|
+
|
|
6873
6912
|
if (node) {
|
|
6913
|
+
|
|
6914
|
+
// trying to put svg on the clipboard here, which works, but
|
|
6915
|
+
// is basically useless. the underlying method is good, though,
|
|
6916
|
+
// clients could use it for better UX in saving images
|
|
6917
|
+
|
|
6874
6918
|
const html = (SerializeHTML(node as Element) as HTMLElement).outerHTML;
|
|
6875
6919
|
|
|
6876
|
-
//
|
|
6877
|
-
|
|
6878
|
-
event.clipboardData.setData(
|
|
6879
|
-
|
|
6920
|
+
event.clipboardData.setData('text/uri-list', `data:image/svg+xml;base64,` + btoa(html)); // <-- does this work? seems no
|
|
6921
|
+
event.clipboardData.setData('text/html', html); // <-- does this work? (also no)
|
|
6922
|
+
event.clipboardData.setData('text/plain', html);
|
|
6923
|
+
|
|
6880
6924
|
}
|
|
6881
6925
|
}
|
|
6882
6926
|
}
|
|
@@ -208,6 +208,9 @@ export interface UnitAddress extends BaseUnit {
|
|
|
208
208
|
absolute_row?: boolean;
|
|
209
209
|
absolute_column?: boolean;
|
|
210
210
|
|
|
211
|
+
/** spill flag (address ends with #) */
|
|
212
|
+
spill?: boolean;
|
|
213
|
+
|
|
211
214
|
/**
|
|
212
215
|
* this means the row is a relative offset from the current row. this
|
|
213
216
|
* happens if you use R1C1 syntax with square brackets.
|
|
@@ -970,7 +970,8 @@ export class Parser {
|
|
|
970
970
|
(address.absolute_column ? '$' : '') +
|
|
971
971
|
this.ColumnLabel(column) +
|
|
972
972
|
(address.absolute_row ? '$' : '') +
|
|
973
|
-
(row + 1)
|
|
973
|
+
(row + 1) +
|
|
974
|
+
(address.spill ? '#' : '')
|
|
974
975
|
);
|
|
975
976
|
}
|
|
976
977
|
|
|
@@ -2210,6 +2211,9 @@ export class Parser {
|
|
|
2210
2211
|
|
|
2211
2212
|
|| (char === QUESTION_MARK && square_bracket === 0)
|
|
2212
2213
|
|
|
2214
|
+
// moving
|
|
2215
|
+
// || (char === HASH) // FIXME: this should only be allowed at the end...
|
|
2216
|
+
|
|
2213
2217
|
/*
|
|
2214
2218
|
|
|
2215
2219
|
|| (this.flags.r1c1 && (
|
|
@@ -2243,6 +2247,11 @@ export class Parser {
|
|
|
2243
2247
|
else break;
|
|
2244
2248
|
}
|
|
2245
2249
|
|
|
2250
|
+
// hash at end only
|
|
2251
|
+
if (this.data[this.index] === HASH) {
|
|
2252
|
+
token.push(this.data[this.index++]);
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2246
2255
|
const str = token.map((num) => String.fromCharCode(num)).join('');
|
|
2247
2256
|
|
|
2248
2257
|
// special handling: unbalanced single quote (probably sheet name),
|
|
@@ -2523,6 +2532,7 @@ export class Parser {
|
|
|
2523
2532
|
// as names. so this should be a token if r === 0.
|
|
2524
2533
|
|
|
2525
2534
|
const r = this.ConsumeAddressRow(position);
|
|
2535
|
+
|
|
2526
2536
|
if (!r) return null;
|
|
2527
2537
|
position = r.position;
|
|
2528
2538
|
|
|
@@ -2544,6 +2554,7 @@ export class Parser {
|
|
|
2544
2554
|
absolute_column: c.absolute,
|
|
2545
2555
|
position: index,
|
|
2546
2556
|
sheet,
|
|
2557
|
+
spill: r.spill,
|
|
2547
2558
|
};
|
|
2548
2559
|
|
|
2549
2560
|
// if that's not the complete token, then it's invalid
|
|
@@ -2578,6 +2589,8 @@ export class Parser {
|
|
|
2578
2589
|
absolute: boolean;
|
|
2579
2590
|
row: number;
|
|
2580
2591
|
position: number;
|
|
2592
|
+
spill?: boolean; // spill reference
|
|
2593
|
+
|
|
2581
2594
|
}|false {
|
|
2582
2595
|
|
|
2583
2596
|
const absolute = this.data[position] === DOLLAR_SIGN;
|
|
@@ -2607,7 +2620,13 @@ export class Parser {
|
|
|
2607
2620
|
return false;
|
|
2608
2621
|
}
|
|
2609
2622
|
|
|
2610
|
-
|
|
2623
|
+
let spill = false;
|
|
2624
|
+
if (this.data[position] === HASH) {
|
|
2625
|
+
position++;
|
|
2626
|
+
spill = true;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
return { absolute, row: value - 1, position, spill };
|
|
2611
2630
|
}
|
|
2612
2631
|
|
|
2613
2632
|
/**
|
|
@@ -19,25 +19,33 @@
|
|
|
19
19
|
*
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
/*
|
|
22
23
|
interface StringMap {
|
|
23
24
|
[index: string]: string;
|
|
24
25
|
}
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
type StringMap = Map<string, string>;
|
|
29
|
+
|
|
25
30
|
|
|
26
31
|
/**
|
|
27
32
|
* defaults are global, since we assume they never change. created on demand.
|
|
28
33
|
*/
|
|
29
34
|
let default_properties: StringMap|undefined;
|
|
30
35
|
|
|
36
|
+
/**
|
|
37
|
+
* convert CSSStyleDeclaration to map
|
|
38
|
+
*/
|
|
31
39
|
const PropertyMap = (source: CSSStyleDeclaration): StringMap => {
|
|
32
40
|
|
|
33
|
-
const map: StringMap =
|
|
41
|
+
const map: StringMap = new Map();
|
|
34
42
|
|
|
35
43
|
// you can iterate this thing, although apparently ts won't allow
|
|
36
44
|
// it because it's not in the spec? should probably play ball
|
|
37
45
|
|
|
38
46
|
for (let i = 0; i < source.length; i++) {
|
|
39
47
|
const key = source[i];
|
|
40
|
-
map
|
|
48
|
+
map.set(key, source.getPropertyValue(key));
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
return map;
|
|
@@ -49,16 +57,17 @@ const PropertyMap = (source: CSSStyleDeclaration): StringMap => {
|
|
|
49
57
|
*/
|
|
50
58
|
const GetAppliedStyle = (node: Element, computed: CSSStyleDeclaration, defaults: StringMap) => {
|
|
51
59
|
|
|
52
|
-
const applied: StringMap =
|
|
60
|
+
const applied: StringMap = new Map();
|
|
53
61
|
const computed_map = PropertyMap(computed);
|
|
54
62
|
|
|
55
|
-
for (const key of
|
|
56
|
-
if (
|
|
57
|
-
applied
|
|
63
|
+
for (const [key, value] of computed_map.entries()) {
|
|
64
|
+
if (value !== defaults.get(key)) {
|
|
65
|
+
applied.set(key, value);
|
|
58
66
|
}
|
|
59
67
|
}
|
|
60
68
|
|
|
61
|
-
|
|
69
|
+
const arr = Array.from(applied.entries());
|
|
70
|
+
return (arr.map(([key, value]) => `${key}: ${value}`).join('; ') +
|
|
62
71
|
'; ' + (node.getAttribute('style') || '')).trim().replace(/"/g, '\'');
|
|
63
72
|
|
|
64
73
|
};
|
|
@@ -118,7 +127,7 @@ export const SerializeHTML = (node: Element) => {
|
|
|
118
127
|
|
|
119
128
|
if (!default_properties) {
|
|
120
129
|
|
|
121
|
-
const defaults: StringMap =
|
|
130
|
+
const defaults: StringMap = new Map();
|
|
122
131
|
|
|
123
132
|
// regarding document, in this case we're creating an iframe
|
|
124
133
|
// specifically for isolation, and adding it to "document".
|
|
@@ -137,7 +146,7 @@ export const SerializeHTML = (node: Element) => {
|
|
|
137
146
|
const div = frame_document.createElement('div');
|
|
138
147
|
frame_document.body.appendChild(div);
|
|
139
148
|
const computed = getComputedStyle(div);
|
|
140
|
-
Array.prototype.forEach.call(computed,
|
|
149
|
+
Array.prototype.forEach.call(computed, key => defaults.set(key, computed[key]));
|
|
141
150
|
}
|
|
142
151
|
|
|
143
152
|
document.body.removeChild(iframe);
|
|
@@ -145,7 +154,23 @@ export const SerializeHTML = (node: Element) => {
|
|
|
145
154
|
|
|
146
155
|
}
|
|
147
156
|
|
|
148
|
-
|
|
157
|
+
const rendered = RenderNode(node, default_properties);
|
|
158
|
+
if (rendered instanceof Element && rendered.tagName === 'svg') {
|
|
159
|
+
if (!rendered.hasAttribute('version')) {
|
|
160
|
+
rendered.setAttribute('version', '1.1');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!rendered.hasAttribute('xmlns')) {
|
|
164
|
+
rendered.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!rendered.hasAttribute('xmlns:xlink')) {
|
|
168
|
+
rendered.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return rendered;
|
|
149
174
|
|
|
150
175
|
};
|
|
151
176
|
|