@trebco/treb 27.5.3 → 27.9.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.mjs +14 -14
- package/dist/treb.d.ts +37 -23
- package/notes/conditional-fomratring.md +29 -0
- package/package.json +3 -3
- package/treb-base-types/src/area.ts +181 -0
- package/treb-base-types/src/evaluate-options.ts +21 -0
- package/treb-base-types/src/gradient.ts +97 -0
- package/treb-base-types/src/import.ts +2 -1
- package/treb-base-types/src/index.ts +2 -0
- package/treb-calculator/src/calculator.ts +205 -132
- package/treb-calculator/src/dag/calculation_leaf_vertex.ts +97 -0
- package/treb-calculator/src/dag/graph.ts +10 -22
- package/treb-calculator/src/dag/{leaf_vertex.ts → state_leaf_vertex.ts} +3 -3
- package/treb-calculator/src/descriptors.ts +10 -3
- package/treb-calculator/src/expression-calculator.ts +1 -1
- package/treb-calculator/src/function-library.ts +25 -22
- package/treb-calculator/src/functions/base-functions.ts +166 -5
- package/treb-calculator/src/index.ts +6 -6
- package/treb-calculator/src/notifier-types.ts +1 -1
- package/treb-calculator/src/utilities.ts +2 -2
- package/treb-charts/src/util.ts +2 -2
- package/treb-embed/src/embedded-spreadsheet.ts +382 -71
- package/treb-embed/style/formula-bar.scss +2 -0
- package/treb-embed/style/theme-defaults.scss +46 -15
- package/treb-export/src/export-worker/export-worker.ts +0 -13
- package/treb-export/src/export2.ts +187 -2
- package/treb-export/src/import2.ts +169 -4
- package/treb-export/src/workbook-style2.ts +56 -8
- package/treb-export/src/workbook2.ts +10 -1
- package/treb-grid/src/editors/editor.ts +1276 -0
- package/treb-grid/src/editors/external_editor.ts +113 -0
- package/treb-grid/src/editors/formula_bar.ts +450 -474
- package/treb-grid/src/editors/overlay_editor.ts +437 -512
- package/treb-grid/src/index.ts +2 -1
- package/treb-grid/src/layout/base_layout.ts +24 -16
- package/treb-grid/src/render/tile_renderer.ts +2 -1
- package/treb-grid/src/types/conditional_format.ts +168 -0
- package/treb-grid/src/types/data_model.ts +130 -3
- package/treb-grid/src/types/external_editor_config.ts +47 -0
- package/treb-grid/src/types/grid.ts +96 -45
- package/treb-grid/src/types/grid_base.ts +187 -35
- package/treb-grid/src/types/scale-control.ts +1 -1
- package/treb-grid/src/types/sheet.ts +330 -26
- package/treb-grid/src/types/sheet_types.ts +4 -0
- package/treb-grid/src/util/dom_utilities.ts +58 -25
- package/treb-grid/src/editors/formula_editor_base.ts +0 -912
- package/treb-grid/src/types/external_editor.ts +0 -27
- /package/{README-shadow-DOM.md → notes/shadow-DOM.md} +0 -0
|
@@ -1,474 +1,450 @@
|
|
|
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-2023 trebco, llc.
|
|
18
|
-
* info@treb.app
|
|
19
|
-
*
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
import type {
|
|
26
|
-
import type {
|
|
27
|
-
import {
|
|
28
|
-
import
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
export class FormulaBar extends
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
this.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
this.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
this.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
public
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
selection
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
case '
|
|
337
|
-
|
|
338
|
-
event.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
let
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if (
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
event.stopPropagation();
|
|
452
|
-
event.preventDefault();
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
private FormulaKeyUp(event: KeyboardEvent){
|
|
457
|
-
|
|
458
|
-
const ac_result = this.autocomplete.HandleKey('keyup', event);
|
|
459
|
-
if (ac_result.handled) return;
|
|
460
|
-
this.FlushReference();
|
|
461
|
-
|
|
462
|
-
// because there are no input events, we have to try this one -- note
|
|
463
|
-
// we still won't capture pastes, FIXME (add handlers?)
|
|
464
|
-
|
|
465
|
-
//if (this.trident) {
|
|
466
|
-
// this.UpdateSelectState();
|
|
467
|
-
// this.Reconstruct();
|
|
468
|
-
//}
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
|
|
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-2023 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { Area, Cell, Theme } from 'treb-base-types';
|
|
23
|
+
import { Editor, type NodeDescriptor, type FormulaEditorEvent } from './editor';
|
|
24
|
+
import { Parser } from 'treb-parser';
|
|
25
|
+
import type { DataModel, ViewModel } from '../types/data_model';
|
|
26
|
+
import type { GridOptions } from '../types/grid_options';
|
|
27
|
+
import { Autocomplete } from './autocomplete';
|
|
28
|
+
import { DOMUtilities } from '../util/dom_utilities';
|
|
29
|
+
|
|
30
|
+
// --- from formula_bar ---
|
|
31
|
+
|
|
32
|
+
export interface FormulaBarResizeEvent {
|
|
33
|
+
type: 'formula-bar-resize';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface FormulaButtonEvent {
|
|
37
|
+
type: 'formula-button';
|
|
38
|
+
formula?: string;
|
|
39
|
+
cursor_position?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface AddressLabelEvent {
|
|
43
|
+
type: 'address-label-event';
|
|
44
|
+
text?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type FormulaBar2Event
|
|
48
|
+
= FormulaButtonEvent
|
|
49
|
+
| FormulaBarResizeEvent
|
|
50
|
+
| AddressLabelEvent
|
|
51
|
+
;
|
|
52
|
+
|
|
53
|
+
// ---
|
|
54
|
+
|
|
55
|
+
export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/** is the _editor_ currently focused */
|
|
59
|
+
// tslint:disable-next-line:variable-name
|
|
60
|
+
public focused_ = false;
|
|
61
|
+
|
|
62
|
+
/** accessor for focused field */
|
|
63
|
+
public get focused(): boolean { return this.focused_; }
|
|
64
|
+
|
|
65
|
+
/** address label (may also show other things... ?) */
|
|
66
|
+
private address_label_container: HTMLDivElement;
|
|
67
|
+
|
|
68
|
+
/** address label (may also show other things... ?) */
|
|
69
|
+
private address_label: HTMLDivElement;
|
|
70
|
+
|
|
71
|
+
/** the function button (optional?) */
|
|
72
|
+
private button!: HTMLButtonElement;
|
|
73
|
+
|
|
74
|
+
/** */
|
|
75
|
+
private expand_button!: HTMLButtonElement;
|
|
76
|
+
|
|
77
|
+
/* * corner for resizing formula editor * /
|
|
78
|
+
private drag_corner!: HTMLDivElement;
|
|
79
|
+
|
|
80
|
+
/ * * for math * /
|
|
81
|
+
private lines = 1;
|
|
82
|
+
|
|
83
|
+
private last_formula = '';
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
private label_update_timer = 0;
|
|
87
|
+
|
|
88
|
+
/** get formula text */
|
|
89
|
+
public get formula(): string {
|
|
90
|
+
return this.active_editor ? this.active_editor.node.textContent || '' : '';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** set formula text */
|
|
94
|
+
public set formula(text: string) {
|
|
95
|
+
if (this.active_editor) {
|
|
96
|
+
this.active_editor.node.textContent = text;
|
|
97
|
+
this.active_editor.formatted_text = undefined;
|
|
98
|
+
}
|
|
99
|
+
// this.last_formula = text;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** get address label text */
|
|
103
|
+
public get label(): string {
|
|
104
|
+
return this.address_label?.textContent || '';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* set address label text. if the label is too long for the box,
|
|
109
|
+
* add a title attribute for a tooltip.
|
|
110
|
+
*/
|
|
111
|
+
public set label(text: string) {
|
|
112
|
+
if (!text.trim().length) {
|
|
113
|
+
this.address_label.innerHTML = ' ';
|
|
114
|
+
this.address_label.removeAttribute('title');
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
this.address_label.textContent = text;
|
|
118
|
+
|
|
119
|
+
if (!this.label_update_timer) {
|
|
120
|
+
this.label_update_timer = requestAnimationFrame(() => {
|
|
121
|
+
this.label_update_timer = 0;
|
|
122
|
+
|
|
123
|
+
// should this be in a Yield callback? need to check IE11...
|
|
124
|
+
// yes
|
|
125
|
+
|
|
126
|
+
if (this.address_label.scrollWidth > this.address_label.offsetWidth) {
|
|
127
|
+
this.address_label.setAttribute('title', text);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
this.address_label.removeAttribute('title');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** toggle editable property: supports locked cells */
|
|
140
|
+
public set editable(editable: boolean) {
|
|
141
|
+
if (!this.active_editor || !this.container_node) return;
|
|
142
|
+
|
|
143
|
+
if (editable) {
|
|
144
|
+
this.active_editor.node.setAttribute('contenteditable', 'true'); // is that required?
|
|
145
|
+
this.container_node.removeAttribute('locked');
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
this.active_editor.node.removeAttribute('contenteditable');
|
|
149
|
+
this.container_node.setAttribute('locked', '');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
constructor(
|
|
155
|
+
container: HTMLElement,
|
|
156
|
+
// parser: Parser,
|
|
157
|
+
// theme: Theme,
|
|
158
|
+
model: DataModel,
|
|
159
|
+
view: ViewModel,
|
|
160
|
+
private options: GridOptions,
|
|
161
|
+
autocomplete: Autocomplete,
|
|
162
|
+
) {
|
|
163
|
+
|
|
164
|
+
super(model, view, autocomplete);
|
|
165
|
+
|
|
166
|
+
const inner_node = container.querySelector('.treb-formula-bar') as HTMLElement;
|
|
167
|
+
inner_node.removeAttribute('hidden');
|
|
168
|
+
|
|
169
|
+
this.address_label_container = inner_node.querySelector('.treb-address-label') as HTMLDivElement;
|
|
170
|
+
this.address_label = this.address_label_container.firstElementChild as HTMLDivElement;
|
|
171
|
+
|
|
172
|
+
this.InitAddressLabel();
|
|
173
|
+
|
|
174
|
+
if (this.options.insert_function_button) {
|
|
175
|
+
this.button = DOMUtilities.Create('button', 'formula-button', inner_node);
|
|
176
|
+
this.button.addEventListener('click', () => {
|
|
177
|
+
const formula: string = this.active_editor ? this.active_editor.node.textContent || '' : '';
|
|
178
|
+
this.Publish({ type: 'formula-button', formula });
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.container_node = container.querySelector('.treb-editor-container') as HTMLDivElement;
|
|
183
|
+
const target = this.container_node.firstElementChild as HTMLDivElement;
|
|
184
|
+
const descriptor: NodeDescriptor = {
|
|
185
|
+
node: target,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
this.active_editor = descriptor;
|
|
189
|
+
this.nodes = [ descriptor ];
|
|
190
|
+
|
|
191
|
+
// ------------------
|
|
192
|
+
|
|
193
|
+
if (target) {
|
|
194
|
+
this.RegisterListener(descriptor, 'input', (event: Event) => {
|
|
195
|
+
|
|
196
|
+
// we send an extra event when we insert a reference.
|
|
197
|
+
// so filter that out. this might cause problems for other
|
|
198
|
+
// callers -- could we use a different filter?
|
|
199
|
+
|
|
200
|
+
if (event.isTrusted) {
|
|
201
|
+
this.UpdateText(descriptor);
|
|
202
|
+
this.UpdateColors(); // will send a local event
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ------------------
|
|
209
|
+
|
|
210
|
+
//
|
|
211
|
+
// change the default back. this was changed when we were trying to figure
|
|
212
|
+
// out what was happening with IME, but it had nothing to do with spellcheck.
|
|
213
|
+
//
|
|
214
|
+
this.active_editor.node.spellcheck = false; // change the default back
|
|
215
|
+
|
|
216
|
+
this.RegisterListener(descriptor, 'focusin', () => {
|
|
217
|
+
|
|
218
|
+
// this.editor_node.addEventListener('focusin', () => {
|
|
219
|
+
|
|
220
|
+
// can't happen
|
|
221
|
+
if (!this.active_editor) { return; }
|
|
222
|
+
|
|
223
|
+
// console.info('focus in');
|
|
224
|
+
|
|
225
|
+
let text = this.active_editor.node.textContent || '';
|
|
226
|
+
|
|
227
|
+
if (text[0] === '{' && text[text.length - 1] === '}') {
|
|
228
|
+
text = text.substring(1, text.length - 1);
|
|
229
|
+
this.active_editor.node.textContent = text;
|
|
230
|
+
this.active_editor.formatted_text = undefined; // why do we clear this here?
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// not here // this.editor_node.spellcheck = (text[0] !== '='); // true except for functions
|
|
234
|
+
this.autocomplete?.ResetBlock();
|
|
235
|
+
|
|
236
|
+
this.UpdateText(this.active_editor);
|
|
237
|
+
this.UpdateColors();
|
|
238
|
+
|
|
239
|
+
this.Publish([
|
|
240
|
+
{ type: 'start-editing', editor: 'formula-bar' },
|
|
241
|
+
{ type: 'update', text, cell: this.active_cell, dependencies: this.composite_dependencies },
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
this.focused_ = true;
|
|
245
|
+
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
this.RegisterListener(descriptor, 'focusout', (event: FocusEvent) => {
|
|
249
|
+
|
|
250
|
+
if (this.selecting) {
|
|
251
|
+
console.info('focusout, but selecting...');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// console.info('focus out');
|
|
255
|
+
|
|
256
|
+
this.autocomplete?.Hide();
|
|
257
|
+
this.Publish([
|
|
258
|
+
{ type: 'stop-editing' },
|
|
259
|
+
]);
|
|
260
|
+
|
|
261
|
+
this.focused_ = false;
|
|
262
|
+
|
|
263
|
+
if (this.active_editor) {
|
|
264
|
+
this.active_editor.node.spellcheck = false; // for firefox
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
this.RegisterListener(descriptor, 'keydown', this.FormulaKeyDown.bind(this));
|
|
270
|
+
this.RegisterListener(descriptor, 'keyup', this.FormulaKeyUp.bind(this));
|
|
271
|
+
|
|
272
|
+
if (this.options.expand_formula_button) {
|
|
273
|
+
this.expand_button = DOMUtilities.Create('button', 'expand-button', inner_node);
|
|
274
|
+
this.expand_button.addEventListener('click', (event: MouseEvent) => {
|
|
275
|
+
event.stopPropagation();
|
|
276
|
+
event.preventDefault();
|
|
277
|
+
if (this.active_editor) {
|
|
278
|
+
this.active_editor.node.scrollTop = 0;
|
|
279
|
+
}
|
|
280
|
+
if (inner_node.hasAttribute('expanded')) {
|
|
281
|
+
inner_node.removeAttribute('expanded');
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
inner_node.setAttribute('expanded', '');
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public IsElement(element: HTMLElement): boolean {
|
|
292
|
+
return element === this.active_editor?.node;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
public InitAddressLabel() {
|
|
296
|
+
|
|
297
|
+
this.address_label.contentEditable = 'true';
|
|
298
|
+
this.address_label.spellcheck = false;
|
|
299
|
+
|
|
300
|
+
// on focus, select all
|
|
301
|
+
// Q: do we do this in other places? we should consolidate
|
|
302
|
+
// A: I don't think we do just this, usually there's additional logic for % and such
|
|
303
|
+
|
|
304
|
+
this.address_label.addEventListener('focusin', (event) => {
|
|
305
|
+
|
|
306
|
+
// FIXME: close any open editors? (...)
|
|
307
|
+
|
|
308
|
+
// we're now doing this async for all browsers... it's only really
|
|
309
|
+
// necessary for IE11 and safari, but doesn't hurt
|
|
310
|
+
|
|
311
|
+
requestAnimationFrame(() => {
|
|
312
|
+
if ((document.body as any).createTextRange) {
|
|
313
|
+
const range = (document.body as any).createTextRange();
|
|
314
|
+
range.moveToElementText(this.address_label);
|
|
315
|
+
range.select();
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
const selection = window.getSelection();
|
|
319
|
+
const range = document.createRange();
|
|
320
|
+
range.selectNodeContents(this.address_label);
|
|
321
|
+
selection?.removeAllRanges();
|
|
322
|
+
selection?.addRange(range);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
this.address_label.addEventListener('keydown', (event) => {
|
|
329
|
+
|
|
330
|
+
if (event.isComposing) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
switch (event.key) {
|
|
335
|
+
|
|
336
|
+
case 'Enter':
|
|
337
|
+
event.stopPropagation();
|
|
338
|
+
event.preventDefault();
|
|
339
|
+
this.Publish({
|
|
340
|
+
type: 'address-label-event',
|
|
341
|
+
text: this.address_label.textContent || undefined,
|
|
342
|
+
});
|
|
343
|
+
break;
|
|
344
|
+
|
|
345
|
+
case 'Esc':
|
|
346
|
+
case 'Escape':
|
|
347
|
+
event.stopPropagation();
|
|
348
|
+
event.preventDefault();
|
|
349
|
+
this.Publish({ type: 'address-label-event' });
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private GetTextContent(node: Node) {
|
|
357
|
+
|
|
358
|
+
const children = node.childNodes;
|
|
359
|
+
const buffer: string[] = [];
|
|
360
|
+
for (let i = 0; i < children.length; i++) {
|
|
361
|
+
const child = children[i];
|
|
362
|
+
switch (child.nodeType) {
|
|
363
|
+
case Node.ELEMENT_NODE:
|
|
364
|
+
buffer.push(...this.GetTextContent(child));
|
|
365
|
+
if (child instanceof Element && child.tagName === 'DIV') {
|
|
366
|
+
buffer.push('\n');
|
|
367
|
+
}
|
|
368
|
+
break;
|
|
369
|
+
|
|
370
|
+
case Node.TEXT_NODE:
|
|
371
|
+
if (child.nodeValue) { buffer.push(child.nodeValue); }
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return buffer;
|
|
376
|
+
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
private FormulaKeyUp(event: KeyboardEvent){
|
|
382
|
+
|
|
383
|
+
if (event.isComposing) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (this.autocomplete) {
|
|
388
|
+
const ac_result = this.autocomplete.HandleKey('keyup', event);
|
|
389
|
+
if (ac_result.handled) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
// this.FlushReference();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private FormulaKeyDown(event: KeyboardEvent){
|
|
398
|
+
|
|
399
|
+
if (event.isComposing) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (this.autocomplete) {
|
|
404
|
+
const ac_result = this.autocomplete.HandleKey('keydown', event);
|
|
405
|
+
if (ac_result.accept) this.AcceptAutocomplete(ac_result);
|
|
406
|
+
if (ac_result.handled) return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
switch (event.key){
|
|
410
|
+
case 'Enter':
|
|
411
|
+
case 'Tab':
|
|
412
|
+
{
|
|
413
|
+
// this.selecting_ = false;
|
|
414
|
+
const array = (event.key === 'Enter' && event.ctrlKey && event.shiftKey);
|
|
415
|
+
|
|
416
|
+
// I think we use this nontstandard routine so that we preserve
|
|
417
|
+
// newlines? not sure. would like to see the motivation for it.
|
|
418
|
+
|
|
419
|
+
const text = (this.active_editor ?
|
|
420
|
+
this.GetTextContent(this.active_editor.node).join('') : '').trim();
|
|
421
|
+
|
|
422
|
+
this.Publish({
|
|
423
|
+
type: 'commit',
|
|
424
|
+
// selection: this.selection,
|
|
425
|
+
value: text,
|
|
426
|
+
event,
|
|
427
|
+
array,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// this.FlushReference();
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
|
|
434
|
+
case 'Escape':
|
|
435
|
+
case 'Esc':
|
|
436
|
+
// this.selecting_ = false;
|
|
437
|
+
this.Publish({ type: 'discard' });
|
|
438
|
+
// this.FlushReference();
|
|
439
|
+
break;
|
|
440
|
+
|
|
441
|
+
default:
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
event.stopPropagation();
|
|
446
|
+
event.preventDefault();
|
|
447
|
+
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
}
|