@trebco/treb 31.1.3 → 31.3.3

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.
@@ -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,28 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
59
65
 
60
66
  public committed = false;
61
67
 
68
+ public tolled?: { text: string, substring: string };
69
+
70
+ /**
71
+ * if we're showing a spill array and it's not the first cell, show
72
+ * the formula using a shadow style (and it's not editable).
73
+ */
74
+ public shadow_ = false;
75
+
76
+ public get shadow() { return this.shadow_; }
77
+
78
+ public set shadow(shadow: boolean) {
79
+ this.shadow_ = shadow;
80
+ if (this.active_editor?.node) {
81
+ if (shadow) {
82
+ this.active_editor.node.classList.add('treb-editor-shadow');
83
+ }
84
+ else {
85
+ this.active_editor.node.classList.remove('treb-editor-shadow');
86
+ }
87
+ }
88
+ }
89
+
62
90
  /** is the _editor_ currently focused */
63
91
  // tslint:disable-next-line:variable-name
64
92
  public focused_ = false;
@@ -221,16 +249,27 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
221
249
  this.active_editor.node.spellcheck = false; // change the default back
222
250
 
223
251
  this.RegisterListener(descriptor, 'focusin', () => {
224
-
252
+
253
+ const restore = this.tolled !== undefined;
254
+ this.tolled = undefined;
255
+
225
256
  // this.editor_node.addEventListener('focusin', () => {
226
257
 
227
258
  // can't happen
228
- if (!this.active_editor) { return; }
259
+ if (!this.active_editor) {
260
+ return;
261
+ }
229
262
 
230
263
  // console.info('focus in');
231
264
 
232
265
  let text = this.active_editor.node.textContent || '';
233
266
 
267
+ if (this.shadow) {
268
+ this.shadow = false;
269
+ text = '';
270
+ this.active_editor.node.textContent = text;
271
+ this.active_editor.formatted_text = undefined; // why do we clear this here?
272
+ }
234
273
  if (text[0] === '{' && text[text.length - 1] === '}') {
235
274
  text = text.substring(1, text.length - 1);
236
275
  this.active_editor.node.textContent = text;
@@ -243,6 +282,18 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
243
282
  this.UpdateText(this.active_editor);
244
283
  this.UpdateColors(undefined, true); // toll update event -- we will send in order, below
245
284
 
285
+ if (restore) {
286
+
287
+ // FIXME: we probably want to hold the caret so we can restore
288
+ // to a particular place. or set the caret explicitly.
289
+
290
+ const node = this.NodeAtIndex(text.length - 1);
291
+ // console.info({text, node});
292
+ if (node) {
293
+ this.SetCaret({node: node as ChildNode, offset: (node.textContent || '').length});
294
+ }
295
+ }
296
+
246
297
  this.committed = false;
247
298
 
248
299
  this.Publish([
@@ -254,7 +305,14 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
254
305
 
255
306
  });
256
307
 
257
- this.RegisterListener(descriptor, 'focusout', () => {
308
+ this.RegisterListener(descriptor, 'focusout', (event: FocusEvent) => {
309
+
310
+ // this is new, to support an external "insert function" dialog.
311
+ // still working out the semantics, but essentially we won't commit
312
+ // and we'll keep pending text. we need some sort of flag to indicate
313
+ // that we're in the editing state.
314
+
315
+ const toll = (event.relatedTarget instanceof HTMLElement && event.relatedTarget.dataset.tollEditor);
258
316
 
259
317
  if (this.selecting) {
260
318
  console.info('focusout, but selecting...');
@@ -265,7 +323,27 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
265
323
  const text = (this.active_editor ?
266
324
  this.GetTextContent(this.active_editor.node).join('') : '').trim();
267
325
 
268
- if (this.committed) {
326
+ if (toll) {
327
+
328
+ let substring = text;
329
+ if (this.active_editor?.node) {
330
+ const s2c = this.SubstringToCaret2(this.active_editor.node, true);
331
+ substring = s2c[0];
332
+ }
333
+
334
+ this.committed = true;
335
+ this.tolled = {
336
+ text,
337
+ substring,
338
+ };
339
+
340
+ this.Publish({
341
+ type: 'toll',
342
+ value: text,
343
+ });
344
+
345
+ }
346
+ else if (this.committed) {
269
347
  this.Publish([
270
348
  { type: 'stop-editing' },
271
349
  ]);
@@ -278,14 +356,10 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
278
356
  });
279
357
  }
280
358
 
359
+ // huh? we're publishing this twice?
360
+
281
361
  this.Publish([
282
362
  { type: 'stop-editing' },
283
- /*
284
- {
285
- type: 'commit',
286
- value: text,
287
- }
288
- */
289
363
  ]);
290
364
 
291
365
  this.focused_ = false;
@@ -336,6 +410,31 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
336
410
 
337
411
  }
338
412
 
413
+ /**
414
+ * we might be overthinking this, we don't necessarily need to restore.
415
+ *
416
+ * this method will focus the editor and set the caret (to the end, atm)
417
+ * in case of a tolled editor. the idea is you call Restore() after your
418
+ * dialog is complete and it's like you are back where you started.
419
+ *
420
+ * alternatively, call `Release()` to clean up any saved state.
421
+ */
422
+ public Restore() {
423
+ const target = this.container_node?.firstElementChild as HTMLDivElement;
424
+ if (target) {
425
+ target.focus();
426
+ }
427
+ }
428
+
429
+ /**
430
+ * release anything that's been tolled. call this if you toll the editor
431
+ * but don't want to restore it when you are done. I think this will be
432
+ * the default.
433
+ */
434
+ public Release() {
435
+ this.tolled = undefined;
436
+ }
437
+
339
438
  public IsElement(element: HTMLElement): boolean {
340
439
  return element === this.active_editor?.node;
341
440
  }
@@ -482,6 +581,7 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
482
581
  case 'Escape':
483
582
  case 'Esc':
484
583
  // this.selecting_ = false;
584
+ this.committed = true;
485
585
  this.Publish({ type: 'discard' });
486
586
  // this.FlushReference();
487
587
  break;
@@ -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
- this.selected_annotation = undefined;
1746
- this.ShowGridSelection();
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
- const node = this.editing_annotation.view[this.view_index]?.node;
3041
- if (node) {
3042
- node.focus();
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
@@ -6641,6 +6692,7 @@ export class Grid extends GridBase {
6641
6692
 
6642
6693
  if (override) {
6643
6694
  if (this.formula_bar) {
6695
+ this.formula_bar.shadow = false;
6644
6696
  this.formula_bar.formula = override;
6645
6697
  }
6646
6698
  return;
@@ -6648,6 +6700,7 @@ export class Grid extends GridBase {
6648
6700
 
6649
6701
  if (this.primary_selection.empty) {
6650
6702
  if (this.formula_bar) {
6703
+ this.formula_bar.shadow = false;
6651
6704
  this.formula_bar.formula = '';
6652
6705
  }
6653
6706
  }
@@ -6657,11 +6710,14 @@ export class Grid extends GridBase {
6657
6710
  // optimally we would do this check prior to this call, but
6658
6711
  // it's the uncommon case... not sure how important that is
6659
6712
 
6660
- const head = data.merge_area || data.area;
6713
+ const head = data.merge_area || data.area || data.spill;
6714
+ let shadow = false;
6715
+
6661
6716
  if (head) {
6662
6717
  if (head.start.column !== this.primary_selection.target.column
6663
6718
  || head.start.row !== this.primary_selection.target.row) {
6664
6719
  data = this.active_sheet.CellData(head.start);
6720
+ if (data.spill) { shadow = true; }
6665
6721
  }
6666
6722
  }
6667
6723
 
@@ -6701,12 +6757,13 @@ export class Grid extends GridBase {
6701
6757
 
6702
6758
  if (this.formula_bar) {
6703
6759
 
6704
- // add braces for area
6760
+ this.formula_bar.shadow = shadow;
6761
+
6705
6762
  if (data.area) {
6706
6763
  this.formula_bar.formula = '{' + (value || '') + '}';
6707
6764
  }
6708
6765
  else {
6709
- this.formula_bar.formula = (typeof value !== 'undefined') ? value.toString() : ''; // value || ''; // what about zero?
6766
+ this.formula_bar.formula = (value ?? '').toString();
6710
6767
  }
6711
6768
 
6712
6769
  }
@@ -161,6 +161,10 @@ export interface UnitCall extends BaseUnit {
161
161
  name: string;
162
162
  position: number;
163
163
  args: ExpressionUnit[];
164
+
165
+ /** testing */
166
+ end?: number;
167
+
164
168
  }
165
169
 
166
170
  /**
@@ -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
- if (stack.length < 2) {
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