@trebco/treb 31.1.3 → 31.3.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-export-worker.mjs +2 -2
- package/dist/treb-spreadsheet.mjs +13 -13
- package/dist/treb.d.ts +2 -2
- package/package.json +1 -1
- package/test/parser.ts +13 -0
- package/treb-calculator/src/complex-math.ts +104 -0
- package/treb-calculator/src/descriptors.ts +6 -0
- package/treb-calculator/src/functions/base-functions.ts +177 -96
- package/treb-calculator/src/functions/statistics-functions.ts +1 -0
- package/treb-calculator/tsconfig.json +0 -4
- package/treb-embed/src/embedded-spreadsheet.ts +6 -3
- package/treb-format/src/format.ts +8 -0
- package/treb-grid/src/editors/editor.ts +2 -2
- package/treb-grid/src/editors/formula_bar.ts +83 -10
- package/treb-grid/src/editors/overlay_editor.ts +11 -0
- package/treb-grid/src/types/grid.ts +56 -5
- package/treb-parser/src/parser-types.ts +4 -0
- package/treb-parser/src/parser.ts +44 -2
|
@@ -46,10 +46,16 @@ export interface AddressLabelEvent {
|
|
|
46
46
|
text?: string;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export interface TollEvent {
|
|
50
|
+
type: 'toll';
|
|
51
|
+
value?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
49
54
|
export type FormulaBar2Event
|
|
50
55
|
= FormulaButtonEvent
|
|
51
56
|
// | FormulaBarResizeEvent
|
|
52
57
|
| AddressLabelEvent
|
|
58
|
+
| TollEvent
|
|
53
59
|
;
|
|
54
60
|
|
|
55
61
|
// ---
|
|
@@ -59,6 +65,8 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
|
|
|
59
65
|
|
|
60
66
|
public committed = false;
|
|
61
67
|
|
|
68
|
+
public tolled?: { text: string, substring: string };
|
|
69
|
+
|
|
62
70
|
/** is the _editor_ currently focused */
|
|
63
71
|
// tslint:disable-next-line:variable-name
|
|
64
72
|
public focused_ = false;
|
|
@@ -221,11 +229,16 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
|
|
|
221
229
|
this.active_editor.node.spellcheck = false; // change the default back
|
|
222
230
|
|
|
223
231
|
this.RegisterListener(descriptor, 'focusin', () => {
|
|
224
|
-
|
|
232
|
+
|
|
233
|
+
const restore = this.tolled !== undefined;
|
|
234
|
+
this.tolled = undefined;
|
|
235
|
+
|
|
225
236
|
// this.editor_node.addEventListener('focusin', () => {
|
|
226
237
|
|
|
227
238
|
// can't happen
|
|
228
|
-
if (!this.active_editor) {
|
|
239
|
+
if (!this.active_editor) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
229
242
|
|
|
230
243
|
// console.info('focus in');
|
|
231
244
|
|
|
@@ -243,6 +256,18 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
|
|
|
243
256
|
this.UpdateText(this.active_editor);
|
|
244
257
|
this.UpdateColors(undefined, true); // toll update event -- we will send in order, below
|
|
245
258
|
|
|
259
|
+
if (restore) {
|
|
260
|
+
|
|
261
|
+
// FIXME: we probably want to hold the caret so we can restore
|
|
262
|
+
// to a particular place. or set the caret explicitly.
|
|
263
|
+
|
|
264
|
+
const node = this.NodeAtIndex(text.length - 1);
|
|
265
|
+
// console.info({text, node});
|
|
266
|
+
if (node) {
|
|
267
|
+
this.SetCaret({node: node as ChildNode, offset: (node.textContent || '').length});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
246
271
|
this.committed = false;
|
|
247
272
|
|
|
248
273
|
this.Publish([
|
|
@@ -254,7 +279,14 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
|
|
|
254
279
|
|
|
255
280
|
});
|
|
256
281
|
|
|
257
|
-
this.RegisterListener(descriptor, 'focusout', () => {
|
|
282
|
+
this.RegisterListener(descriptor, 'focusout', (event: FocusEvent) => {
|
|
283
|
+
|
|
284
|
+
// this is new, to support an external "insert function" dialog.
|
|
285
|
+
// still working out the semantics, but essentially we won't commit
|
|
286
|
+
// and we'll keep pending text. we need some sort of flag to indicate
|
|
287
|
+
// that we're in the editing state.
|
|
288
|
+
|
|
289
|
+
const toll = (event.relatedTarget instanceof HTMLElement && event.relatedTarget.dataset.tollEditor);
|
|
258
290
|
|
|
259
291
|
if (this.selecting) {
|
|
260
292
|
console.info('focusout, but selecting...');
|
|
@@ -265,7 +297,27 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
|
|
|
265
297
|
const text = (this.active_editor ?
|
|
266
298
|
this.GetTextContent(this.active_editor.node).join('') : '').trim();
|
|
267
299
|
|
|
268
|
-
if (
|
|
300
|
+
if (toll) {
|
|
301
|
+
|
|
302
|
+
let substring = text;
|
|
303
|
+
if (this.active_editor?.node) {
|
|
304
|
+
const s2c = this.SubstringToCaret2(this.active_editor.node, true);
|
|
305
|
+
substring = s2c[0];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this.committed = true;
|
|
309
|
+
this.tolled = {
|
|
310
|
+
text,
|
|
311
|
+
substring,
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
this.Publish({
|
|
315
|
+
type: 'toll',
|
|
316
|
+
value: text,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
}
|
|
320
|
+
else if (this.committed) {
|
|
269
321
|
this.Publish([
|
|
270
322
|
{ type: 'stop-editing' },
|
|
271
323
|
]);
|
|
@@ -278,14 +330,10 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
|
|
|
278
330
|
});
|
|
279
331
|
}
|
|
280
332
|
|
|
333
|
+
// huh? we're publishing this twice?
|
|
334
|
+
|
|
281
335
|
this.Publish([
|
|
282
336
|
{ type: 'stop-editing' },
|
|
283
|
-
/*
|
|
284
|
-
{
|
|
285
|
-
type: 'commit',
|
|
286
|
-
value: text,
|
|
287
|
-
}
|
|
288
|
-
*/
|
|
289
337
|
]);
|
|
290
338
|
|
|
291
339
|
this.focused_ = false;
|
|
@@ -336,6 +384,31 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
|
|
|
336
384
|
|
|
337
385
|
}
|
|
338
386
|
|
|
387
|
+
/**
|
|
388
|
+
* we might be overthinking this, we don't necessarily need to restore.
|
|
389
|
+
*
|
|
390
|
+
* this method will focus the editor and set the caret (to the end, atm)
|
|
391
|
+
* in case of a tolled editor. the idea is you call Restore() after your
|
|
392
|
+
* dialog is complete and it's like you are back where you started.
|
|
393
|
+
*
|
|
394
|
+
* alternatively, call `Release()` to clean up any saved state.
|
|
395
|
+
*/
|
|
396
|
+
public Restore() {
|
|
397
|
+
const target = this.container_node?.firstElementChild as HTMLDivElement;
|
|
398
|
+
if (target) {
|
|
399
|
+
target.focus();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* release anything that's been tolled. call this if you toll the editor
|
|
405
|
+
* but don't want to restore it when you are done. I think this will be
|
|
406
|
+
* the default.
|
|
407
|
+
*/
|
|
408
|
+
public Release() {
|
|
409
|
+
this.tolled = undefined;
|
|
410
|
+
}
|
|
411
|
+
|
|
339
412
|
public IsElement(element: HTMLElement): boolean {
|
|
340
413
|
return element === this.active_editor?.node;
|
|
341
414
|
}
|
|
@@ -400,6 +400,17 @@ export class OverlayEditor extends Editor<ResetSelectionEvent> {
|
|
|
400
400
|
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
public GetEditState() {
|
|
404
|
+
const text = this.active_editor?.node?.textContent || '';
|
|
405
|
+
|
|
406
|
+
let substring = '';
|
|
407
|
+
if (this.active_editor?.node) {
|
|
408
|
+
substring = this.SubstringToCaret2(this.active_editor.node, true)[0];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return { text, substring };
|
|
412
|
+
}
|
|
413
|
+
|
|
403
414
|
/**
|
|
404
415
|
* check if we want to handle this key. we have some special cases (tab,
|
|
405
416
|
* enter, escape) where we do take some action but we also let the
|
|
@@ -219,6 +219,9 @@ export class Grid extends GridBase {
|
|
|
219
219
|
/** */
|
|
220
220
|
private editing_annotation?: Annotation;
|
|
221
221
|
|
|
222
|
+
/** */
|
|
223
|
+
private pending_reset_selection = false;
|
|
224
|
+
|
|
222
225
|
/** */
|
|
223
226
|
private view_node?: HTMLElement;
|
|
224
227
|
|
|
@@ -1742,8 +1745,15 @@ export class Grid extends GridBase {
|
|
|
1742
1745
|
if (this.selected_annotation) {
|
|
1743
1746
|
|
|
1744
1747
|
if (click) {
|
|
1745
|
-
|
|
1746
|
-
this.
|
|
1748
|
+
|
|
1749
|
+
if (this.editing_annotation === this.selected_annotation) {
|
|
1750
|
+
this.pending_reset_selection = true;
|
|
1751
|
+
}
|
|
1752
|
+
else {
|
|
1753
|
+
this.selected_annotation = undefined;
|
|
1754
|
+
this.ShowGridSelection();
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1747
1757
|
}
|
|
1748
1758
|
else {
|
|
1749
1759
|
// console.info("reselect annotation...", this.selected_annotation);
|
|
@@ -2964,6 +2974,10 @@ export class Grid extends GridBase {
|
|
|
2964
2974
|
|
|
2965
2975
|
case 'stop-editing':
|
|
2966
2976
|
|
|
2977
|
+
if (this.pending_reset_selection) {
|
|
2978
|
+
this.ShowGridSelection();
|
|
2979
|
+
}
|
|
2980
|
+
this.pending_reset_selection = false;
|
|
2967
2981
|
this.editing_state = EditingState.NotEditing;
|
|
2968
2982
|
break;
|
|
2969
2983
|
|
|
@@ -2986,6 +3000,19 @@ export class Grid extends GridBase {
|
|
|
2986
3000
|
this.editing_selection = { ...this.primary_selection };
|
|
2987
3001
|
break;
|
|
2988
3002
|
|
|
3003
|
+
case 'toll':
|
|
3004
|
+
|
|
3005
|
+
// this is basically "stop editing", with some special
|
|
3006
|
+
// semantics to capture the editing state. it's intended
|
|
3007
|
+
// for external clients, specifically for an "insert function"
|
|
3008
|
+
// dialog.
|
|
3009
|
+
|
|
3010
|
+
this.editing_state = EditingState.NotEditing;
|
|
3011
|
+
this.ClearAdditionalSelections();
|
|
3012
|
+
this.ClearSelection(this.active_selection);
|
|
3013
|
+
this.DelayedRender();
|
|
3014
|
+
break;
|
|
3015
|
+
|
|
2989
3016
|
case 'discard':
|
|
2990
3017
|
|
|
2991
3018
|
this.editing_state = EditingState.NotEditing;
|
|
@@ -3037,10 +3064,14 @@ export class Grid extends GridBase {
|
|
|
3037
3064
|
this.ClearAdditionalSelections();
|
|
3038
3065
|
this.ClearSelection(this.active_selection);
|
|
3039
3066
|
annotation.data.formula = event.value ? this.FixFormula(event.value) : '';
|
|
3040
|
-
|
|
3041
|
-
if (
|
|
3042
|
-
node.
|
|
3067
|
+
|
|
3068
|
+
if (!this.pending_reset_selection) {
|
|
3069
|
+
const node = this.editing_annotation.view[this.view_index]?.node;
|
|
3070
|
+
if (node) {
|
|
3071
|
+
node.focus();
|
|
3072
|
+
}
|
|
3043
3073
|
}
|
|
3074
|
+
|
|
3044
3075
|
this.grid_events.Publish({ type: 'annotation', event: 'update', annotation });
|
|
3045
3076
|
this.editing_annotation = undefined;
|
|
3046
3077
|
this.DelayedRender();
|
|
@@ -4774,6 +4805,26 @@ export class Grid extends GridBase {
|
|
|
4774
4805
|
// || (this.select_argument);
|
|
4775
4806
|
}
|
|
4776
4807
|
|
|
4808
|
+
/**
|
|
4809
|
+
* for external clients. the expected pattern is
|
|
4810
|
+
* - start overlay editor
|
|
4811
|
+
* - click on "insert function"
|
|
4812
|
+
* - do something
|
|
4813
|
+
* - release editor (this function)
|
|
4814
|
+
*/
|
|
4815
|
+
public ReleaseOverlayEditor() {
|
|
4816
|
+
this.editing_state = EditingState.NotEditing;
|
|
4817
|
+
this.DismissEditor();
|
|
4818
|
+
this.DelayedRender();
|
|
4819
|
+
}
|
|
4820
|
+
|
|
4821
|
+
public RestoreOverlayEditor() {
|
|
4822
|
+
|
|
4823
|
+
// ?
|
|
4824
|
+
this.overlay_editor?.FocusEditor();
|
|
4825
|
+
|
|
4826
|
+
}
|
|
4827
|
+
|
|
4777
4828
|
/**
|
|
4778
4829
|
* consolidated event handler for overlay, which both handles grid keys
|
|
4779
4830
|
* and acts as the ICE, depending on state. going to be a little tricky
|
|
@@ -36,6 +36,7 @@ import type {
|
|
|
36
36
|
RenderOptions,
|
|
37
37
|
BaseExpressionUnit,
|
|
38
38
|
} from './parser-types';
|
|
39
|
+
|
|
39
40
|
import {
|
|
40
41
|
ArgumentSeparatorType,
|
|
41
42
|
DecimalMarkType,
|
|
@@ -1137,7 +1138,6 @@ export class Parser {
|
|
|
1137
1138
|
|
|
1138
1139
|
}
|
|
1139
1140
|
|
|
1140
|
-
|
|
1141
1141
|
// so we're moving complex handling to post-reordering, to support
|
|
1142
1142
|
// precedence properly. there's still one thing we have to do here,
|
|
1143
1143
|
// though: handle those cases of naked imaginary values "i". these
|
|
@@ -1233,7 +1233,13 @@ export class Parser {
|
|
|
1233
1233
|
|
|
1234
1234
|
// return this.BinaryToRange(this.ArrangeUnits(stream));
|
|
1235
1235
|
// return this.ArrangeUnits(stream);
|
|
1236
|
+
|
|
1237
|
+
// const arranged = this.ArrangeUnits(stream);
|
|
1238
|
+
// const result = this.BinaryToComplex(arranged);
|
|
1239
|
+
// return result;
|
|
1240
|
+
|
|
1236
1241
|
return this.BinaryToComplex(this.ArrangeUnits(stream));
|
|
1242
|
+
|
|
1237
1243
|
}
|
|
1238
1244
|
|
|
1239
1245
|
/**
|
|
@@ -1808,6 +1814,7 @@ export class Parser {
|
|
|
1808
1814
|
* reorders operations for precendence
|
|
1809
1815
|
*/
|
|
1810
1816
|
protected ArrangeUnits(stream: ExpressionUnit[]): ExpressionUnit {
|
|
1817
|
+
|
|
1811
1818
|
// probably should not happen
|
|
1812
1819
|
if (stream.length === 0) return { type: 'missing', id: this.id_counter++ };
|
|
1813
1820
|
|
|
@@ -1908,7 +1915,41 @@ export class Parser {
|
|
|
1908
1915
|
}
|
|
1909
1916
|
}
|
|
1910
1917
|
|
|
1911
|
-
|
|
1918
|
+
//
|
|
1919
|
+
// why is this 2? are we thinking about combining complex numbers?
|
|
1920
|
+
// or ranges? (those would be binary). or was this for dimensioned
|
|
1921
|
+
// quantities? [actually that makes sense] [A: no, it wasn't that]
|
|
1922
|
+
//
|
|
1923
|
+
// actually what's the case where this is triggered and it's _not_
|
|
1924
|
+
// an error? can we find that?
|
|
1925
|
+
//
|
|
1926
|
+
|
|
1927
|
+
if (stack.length < 2) {
|
|
1928
|
+
|
|
1929
|
+
// we know that `element` is not an operator, because we
|
|
1930
|
+
// would have consumed it
|
|
1931
|
+
|
|
1932
|
+
if (stack.length === 1) {
|
|
1933
|
+
const a = stack[0].type;
|
|
1934
|
+
|
|
1935
|
+
if (a !== 'operator') {
|
|
1936
|
+
|
|
1937
|
+
// console.warn("unexpected element", stack[0], element);
|
|
1938
|
+
|
|
1939
|
+
this.error = `unexpected element [3]: ${element.type}`;
|
|
1940
|
+
this.error_position = (element.type === 'missing' || element.type === 'group' || element.type === 'dimensioned') ? -1 : element.position;
|
|
1941
|
+
this.valid = false;
|
|
1942
|
+
return {
|
|
1943
|
+
type: 'group',
|
|
1944
|
+
id: this.id_counter++,
|
|
1945
|
+
elements: stream,
|
|
1946
|
+
explicit: false,
|
|
1947
|
+
};
|
|
1948
|
+
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1912
1953
|
stack.push(element);
|
|
1913
1954
|
}
|
|
1914
1955
|
else if (stack[stack.length - 1].type === 'operator') {
|
|
@@ -2353,6 +2394,7 @@ export class Parser {
|
|
|
2353
2394
|
name: str,
|
|
2354
2395
|
args,
|
|
2355
2396
|
position,
|
|
2397
|
+
end: this.index, // testing
|
|
2356
2398
|
};
|
|
2357
2399
|
}
|
|
2358
2400
|
|