@trebco/treb 27.5.3 → 27.7.6
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 +17 -3
- package/package.json +3 -3
- package/treb-calculator/src/calculator.ts +15 -104
- package/treb-embed/src/embedded-spreadsheet.ts +30 -30
- package/treb-embed/style/formula-bar.scss +2 -0
- package/treb-embed/style/theme-defaults.scss +46 -15
- 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 +1 -1
- package/treb-grid/src/layout/base_layout.ts +1 -1
- 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 +91 -39
- package/treb-grid/src/types/grid_base.ts +1 -2
- package/treb-grid/src/types/scale-control.ts +1 -1
- 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
|
@@ -1,512 +1,437 @@
|
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (!
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (this.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// this.
|
|
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
|
-
this.edit_node.
|
|
238
|
-
this.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
this
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
this.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
this.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
return OverlayEditorResult.not_handled;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// pass through to autocomplete
|
|
442
|
-
|
|
443
|
-
const ac = this.autocomplete.HandleKey('keydown', event);
|
|
444
|
-
|
|
445
|
-
if (ac.accept){
|
|
446
|
-
this.AcceptAutocomplete(ac);
|
|
447
|
-
}
|
|
448
|
-
if (ac.handled) {
|
|
449
|
-
return OverlayEditorResult.handled;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
switch (event.key) {
|
|
453
|
-
|
|
454
|
-
case 'Enter':
|
|
455
|
-
case 'Tab':
|
|
456
|
-
{
|
|
457
|
-
/*
|
|
458
|
-
// we're going to trap this event, and then re-send it, as we do with
|
|
459
|
-
// the formula bar editor. this is so that the grid can send the data
|
|
460
|
-
// event before the selection event, to better support undo.
|
|
461
|
-
|
|
462
|
-
const value = this.edit_node.textContent || undefined;
|
|
463
|
-
const array = (event.key === 'Enter' && event.ctrlKey && event.shiftKey);
|
|
464
|
-
this.Publish({type: 'commit', value, selection: this.selection, array, event});
|
|
465
|
-
*/
|
|
466
|
-
|
|
467
|
-
this.selecting_ = false;
|
|
468
|
-
|
|
469
|
-
// do this so we don't tab-switch-focus
|
|
470
|
-
// event.stopPropagation();
|
|
471
|
-
// event.preventDefault();
|
|
472
|
-
|
|
473
|
-
return OverlayEditorResult.commit;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
case 'Escape':
|
|
477
|
-
case 'Esc':
|
|
478
|
-
|
|
479
|
-
// this.Publish({type: 'discard'});
|
|
480
|
-
this.selecting_ = false;
|
|
481
|
-
return OverlayEditorResult.discard;
|
|
482
|
-
|
|
483
|
-
case 'ArrowUp':
|
|
484
|
-
case 'ArrowDown':
|
|
485
|
-
case 'ArrowLeft':
|
|
486
|
-
case 'ArrowRight':
|
|
487
|
-
case 'Up':
|
|
488
|
-
case 'Down':
|
|
489
|
-
case 'Left':
|
|
490
|
-
case 'Right':
|
|
491
|
-
return this.selecting_ ? OverlayEditorResult.not_handled : OverlayEditorResult.handled;
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// for all other keys, we consume the key if we're in edit mode; otherwise
|
|
496
|
-
// return false and let the calling routine (in grid) handle the key
|
|
497
|
-
|
|
498
|
-
// return this.editing;
|
|
499
|
-
|
|
500
|
-
return OverlayEditorResult.handled; // always true because we test at the top
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// --- from old ICE ----------------------------------------------------------
|
|
505
|
-
|
|
506
|
-
public UpdateTheme(scale: number): void {
|
|
507
|
-
this.scale = scale;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
|
|
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 { Editor, type NodeDescriptor } from './editor';
|
|
23
|
+
import { Area, Cell, type CellStyle, type CellValue, Rectangle, Style, type Theme, ThemeColor2 } from 'treb-base-types';
|
|
24
|
+
import { DataModel, type ViewModel } from '../types/data_model';
|
|
25
|
+
import { Autocomplete } from './autocomplete';
|
|
26
|
+
import { UA } from '../util/ua';
|
|
27
|
+
import type { GridSelection } from '../types/grid_selection';
|
|
28
|
+
|
|
29
|
+
export type OverlayEditorResult = 'handled' | 'commit' | 'discard';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* but when to send it?
|
|
33
|
+
*/
|
|
34
|
+
export interface ResetSelectionEvent {
|
|
35
|
+
type: 'reset-selection';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class OverlayEditor extends Editor<ResetSelectionEvent> {
|
|
39
|
+
|
|
40
|
+
// --- do we actually need this? ---------------------------------------------
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* selection being edited. note that this is private rather than protected
|
|
44
|
+
* in an effort to prevent subclasses from accidentally using shallow copies
|
|
45
|
+
*/
|
|
46
|
+
private internal_selection: GridSelection = {
|
|
47
|
+
target: { row: 0, column: 0 },
|
|
48
|
+
area: new Area({ row: 0, column: 0 }),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** accessor for selection */
|
|
52
|
+
public get selection(){ return this.internal_selection; }
|
|
53
|
+
|
|
54
|
+
/** set selection, deep copy */
|
|
55
|
+
public set selection(rhs: GridSelection){
|
|
56
|
+
if (!rhs){
|
|
57
|
+
const zero = {row: 0, column: 0};
|
|
58
|
+
this.internal_selection = {target: zero, area: new Area(zero)};
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const target = rhs.target || rhs.area.start;
|
|
62
|
+
this.internal_selection = {
|
|
63
|
+
target: {row: target.row, column: target.column},
|
|
64
|
+
area: new Area(rhs.area.start, rhs.area.end),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* this is a flag used to indicate when we need to reset the selection.
|
|
73
|
+
* the issue has to do with selecting cells via arrow keys; if you do
|
|
74
|
+
* that twice, the second time the selection starts on the cell you
|
|
75
|
+
* selected the first time. so we want to fix that.
|
|
76
|
+
*
|
|
77
|
+
* I guess that used to work with an 'end-selection' event (although it
|
|
78
|
+
* didn't change the sheet) but that doesn't happen anymore because
|
|
79
|
+
* selecting state is determined dynamically now.
|
|
80
|
+
*/
|
|
81
|
+
public reset_selection = false;
|
|
82
|
+
|
|
83
|
+
/** we could use the descriptor reference */
|
|
84
|
+
public edit_node: HTMLElement & ElementContentEditable;
|
|
85
|
+
|
|
86
|
+
/** narrowing from superclass */
|
|
87
|
+
public container_node: HTMLElement;
|
|
88
|
+
|
|
89
|
+
/** special node for ICE */
|
|
90
|
+
public edit_inset: HTMLElement;
|
|
91
|
+
|
|
92
|
+
public scale = 1; // this should go into theme, since it tends to follow it
|
|
93
|
+
|
|
94
|
+
/** shadow property */
|
|
95
|
+
private internal_editing = false;
|
|
96
|
+
|
|
97
|
+
/** accessor */
|
|
98
|
+
public get editing(): boolean {
|
|
99
|
+
return this.internal_editing;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* this is only set one time for each state, so it would be more
|
|
104
|
+
* efficient to inline it unless that's going to change
|
|
105
|
+
*/
|
|
106
|
+
protected set editing(state: boolean) {
|
|
107
|
+
if (this.internal_editing !== state) {
|
|
108
|
+
this.internal_editing = state;
|
|
109
|
+
if (state) {
|
|
110
|
+
this.container_node.style.opacity = '1';
|
|
111
|
+
this.container_node.style.pointerEvents = 'initial';
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.container_node.style.opacity = '0';
|
|
115
|
+
this.container_node.style.pointerEvents = 'none';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
constructor(
|
|
121
|
+
private container: HTMLElement,
|
|
122
|
+
private theme: Theme,
|
|
123
|
+
model: DataModel,
|
|
124
|
+
view: ViewModel,
|
|
125
|
+
autocomplete: Autocomplete) {
|
|
126
|
+
|
|
127
|
+
super(model, view, autocomplete);
|
|
128
|
+
|
|
129
|
+
this.container_node = container.querySelector('.treb-overlay-container') as HTMLElement;
|
|
130
|
+
this.edit_node = this.container_node.querySelector('.treb-overlay-editor') as HTMLElement & ElementContentEditable;
|
|
131
|
+
|
|
132
|
+
if (UA.is_firefox) {
|
|
133
|
+
this.edit_node.classList.add('firefox');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// attempting to cancel "auto" keyboard on ios
|
|
137
|
+
this.edit_node.inputMode = 'none';
|
|
138
|
+
|
|
139
|
+
////
|
|
140
|
+
|
|
141
|
+
const descriptor: NodeDescriptor = { node: this.edit_node };
|
|
142
|
+
this.nodes = [ descriptor ];
|
|
143
|
+
this.active_editor = descriptor;
|
|
144
|
+
|
|
145
|
+
this.RegisterListener(descriptor, 'input', (event: Event) => {
|
|
146
|
+
|
|
147
|
+
if (event instanceof InputEvent && event.isComposing) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!event.isTrusted) {
|
|
152
|
+
this.reset_selection = true; // this is a hack, and unreliable (but works for now)
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (this.reset_selection) {
|
|
157
|
+
this.Publish({
|
|
158
|
+
type: 'reset-selection',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// this is a new thing that popped up in chrome (actually edge).
|
|
163
|
+
// not sure what's happening but this seems to clean it up.
|
|
164
|
+
// we technically could allow a newline here, but... call that a TODO
|
|
165
|
+
|
|
166
|
+
const first_child = this.edit_node.firstChild as HTMLElement;
|
|
167
|
+
if (first_child && first_child.tagName === 'BR') {
|
|
168
|
+
this.edit_node.removeChild(first_child);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!this.editing) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.UpdateText(descriptor);
|
|
176
|
+
this.UpdateColors();
|
|
177
|
+
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
this.RegisterListener(descriptor, 'keyup', (event: KeyboardEvent) => {
|
|
181
|
+
|
|
182
|
+
if (event.isComposing || !this.editing) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// we're not doing anything with the result? (...)
|
|
187
|
+
|
|
188
|
+
if (this.autocomplete && this.autocomplete.HandleKey('keyup', event).handled) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
this.edit_inset = this.container_node.querySelector('.treb-overlay-inset') as HTMLElement;
|
|
195
|
+
// this.container_node = this.container_node ; // wtf is this?
|
|
196
|
+
|
|
197
|
+
this.ClearContents();
|
|
198
|
+
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public UpdateCaption(text = ''): void {
|
|
202
|
+
this.edit_node.setAttribute('aria-label', text);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public Focus(text = ''): void {
|
|
206
|
+
|
|
207
|
+
// we get unexpected scroll behavior if we focus on the overlay editor
|
|
208
|
+
// when it is not already focused, and the grid is scrolled. that's because
|
|
209
|
+
// by default the editor is at (0, 0), so we need to move it before we
|
|
210
|
+
// focus on it (but only in this case).
|
|
211
|
+
|
|
212
|
+
if (this.edit_node !== document.activeElement) {
|
|
213
|
+
|
|
214
|
+
// this was not correct, but should we add those 2 pixels back?
|
|
215
|
+
|
|
216
|
+
// this.edit_container.style.top = `${this.container.scrollTop + 2}px`;
|
|
217
|
+
// this.edit_container.style.left = `${this.container.scrollLeft + 2}px`;
|
|
218
|
+
|
|
219
|
+
this.container_node.style.top = `${this.container.scrollTop + this.view.active_sheet.header_offset.y}px`;
|
|
220
|
+
this.container_node.style.left = `${this.container.scrollLeft + this.view.active_sheet.header_offset.x}px`;
|
|
221
|
+
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.edit_node.focus();
|
|
225
|
+
this.UpdateCaption(text);
|
|
226
|
+
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* TEMP (should be Hide() ?) */
|
|
230
|
+
public CloseEditor(): void {
|
|
231
|
+
this.editing = false;
|
|
232
|
+
this.reset_selection = false;
|
|
233
|
+
|
|
234
|
+
// this (all) should go into the set visible accessor? (...)
|
|
235
|
+
|
|
236
|
+
this.ClearContents();
|
|
237
|
+
this.edit_node.spellcheck = true; // default
|
|
238
|
+
this.autocomplete?.Hide();
|
|
239
|
+
|
|
240
|
+
this.active_cell = undefined;
|
|
241
|
+
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* remove contents, plus add mozilla junk node
|
|
246
|
+
*/
|
|
247
|
+
public ClearContents(): void {
|
|
248
|
+
|
|
249
|
+
// UA doesn't change, so this should be mapped directly
|
|
250
|
+
// (meaning function pointer and no test)
|
|
251
|
+
|
|
252
|
+
// ...maybe overoptimizing
|
|
253
|
+
|
|
254
|
+
if (UA.is_firefox) {
|
|
255
|
+
|
|
256
|
+
// in firefox if the node is empty when you focus on it the
|
|
257
|
+
// cursor shifts up like 1/2 em or something, no idea why
|
|
258
|
+
// (TODO: check bugs)
|
|
259
|
+
|
|
260
|
+
this.edit_node.innerHTML = '<span></span>';
|
|
261
|
+
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
this.edit_node.textContent = '';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* start editing. I'm not sure why we're passing the selection around,
|
|
273
|
+
* but I don't want to take it out until I can answer that question.
|
|
274
|
+
*
|
|
275
|
+
* something to do with keyboard selection? (which needs to be fixed)?
|
|
276
|
+
*/
|
|
277
|
+
public Edit(gridselection: GridSelection, rect: Rectangle, cell: Cell, value?: CellValue, event?: Event): void {
|
|
278
|
+
|
|
279
|
+
this.Publish({
|
|
280
|
+
type: 'start-editing',
|
|
281
|
+
editor: 'ice',
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
this.active_cell = cell;
|
|
285
|
+
this.target_address = {...gridselection.target};
|
|
286
|
+
this.reset_selection = false;
|
|
287
|
+
|
|
288
|
+
const style: CellStyle = cell.style || {};
|
|
289
|
+
|
|
290
|
+
this.edit_node.style.font = Style.Font(style, this.scale);
|
|
291
|
+
this.edit_node.style.color = ThemeColor2(this.theme, style.text, 1);
|
|
292
|
+
this.edit_inset.style.backgroundColor = ThemeColor2(this.theme, style.fill, 0);
|
|
293
|
+
|
|
294
|
+
// NOTE: now that we dropped support for IE11, we can probably
|
|
295
|
+
// remove more than one class at the same time.
|
|
296
|
+
|
|
297
|
+
// (but apparently firefox didn't support multiple classes either,
|
|
298
|
+
// until v[x]? I think that may have been years ago...)
|
|
299
|
+
|
|
300
|
+
switch (style.horizontal_align) {
|
|
301
|
+
case 'right': // Style.HorizontalAlign.Right:
|
|
302
|
+
this.container_node.classList.remove('align-center', 'align-left');
|
|
303
|
+
this.container_node.classList.add('align-right');
|
|
304
|
+
break;
|
|
305
|
+
case 'center': // Style.HorizontalAlign.Center:
|
|
306
|
+
this.container_node.classList.remove('align-right', 'align-left');
|
|
307
|
+
this.container_node.classList.add('align-center');
|
|
308
|
+
break;
|
|
309
|
+
default:
|
|
310
|
+
this.container_node.classList.remove('align-right', 'align-center');
|
|
311
|
+
this.container_node.classList.add('align-left');
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
this.edit_node.style.paddingBottom = `${ Math.max(0, (self.devicePixelRatio||1) - 1)}px`;
|
|
316
|
+
|
|
317
|
+
// console.info('pb', this.edit_node.style.paddingBottom);
|
|
318
|
+
|
|
319
|
+
// TODO: alignment, underline (strike?)
|
|
320
|
+
// bold/italic already work because those are font properties.
|
|
321
|
+
|
|
322
|
+
const value_string = value?.toString() || '';
|
|
323
|
+
|
|
324
|
+
// do this only if there's existing text, in which case we're not
|
|
325
|
+
// typing... or it could be a %, which is OK because the key is a number
|
|
326
|
+
|
|
327
|
+
if (value_string && value_string[0] === '=') {
|
|
328
|
+
this.edit_node.spellcheck = false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
this.autocomplete?.ResetBlock();
|
|
332
|
+
this.selection = gridselection;
|
|
333
|
+
|
|
334
|
+
if (typeof value !== 'undefined') {
|
|
335
|
+
|
|
336
|
+
const percent = value_string[0] !== '=' && value_string[value_string.length - 1] === '%';
|
|
337
|
+
const value_length = value_string.length;
|
|
338
|
+
this.edit_node.textContent = value_string;
|
|
339
|
+
|
|
340
|
+
this.SetCaret({ node: this.edit_node, offset: value_length - (percent ? 1 : 0) })
|
|
341
|
+
|
|
342
|
+
// event moved below
|
|
343
|
+
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
rect.ApplyStyle(this.container_node);
|
|
347
|
+
this.editing = true;
|
|
348
|
+
|
|
349
|
+
Promise.resolve().then(() => {
|
|
350
|
+
|
|
351
|
+
if (this.active_editor) {
|
|
352
|
+
this.active_editor.formatted_text = undefined; // necessary? (...)
|
|
353
|
+
this.UpdateText(this.active_editor);
|
|
354
|
+
this.UpdateColors();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// not sure about these two tests, they're from the old version
|
|
358
|
+
|
|
359
|
+
if (!event && value !== undefined) {
|
|
360
|
+
this.Publish({type: 'update', text: value.toString(), dependencies: this.composite_dependencies});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* check if we want to handle this key. we have some special cases (tab,
|
|
369
|
+
* enter, escape) where we do take some action but we also let the
|
|
370
|
+
* spreadsheet handle the key. for those we have some additional return
|
|
371
|
+
* values.
|
|
372
|
+
*
|
|
373
|
+
* NOTE this is *not* added as an event handler -- it's called by the grid
|
|
374
|
+
*
|
|
375
|
+
* @param event
|
|
376
|
+
* @returns
|
|
377
|
+
*/
|
|
378
|
+
public HandleKeyDown(event: KeyboardEvent): OverlayEditorResult|undefined {
|
|
379
|
+
|
|
380
|
+
// skip keys if we're not editing
|
|
381
|
+
|
|
382
|
+
if (!this.editing) {
|
|
383
|
+
return undefined; // not handled
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// pass through to autocomplete
|
|
387
|
+
|
|
388
|
+
if (this.autocomplete) {
|
|
389
|
+
const ac = this.autocomplete.HandleKey('keydown', event);
|
|
390
|
+
|
|
391
|
+
if (ac.accept){
|
|
392
|
+
this.AcceptAutocomplete(ac);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (ac.handled) {
|
|
396
|
+
return 'handled';
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
switch (event.key) {
|
|
401
|
+
|
|
402
|
+
case 'Enter':
|
|
403
|
+
case 'Tab':
|
|
404
|
+
return 'commit';
|
|
405
|
+
|
|
406
|
+
case 'Escape':
|
|
407
|
+
case 'Esc':
|
|
408
|
+
return 'discard';
|
|
409
|
+
|
|
410
|
+
case 'ArrowUp':
|
|
411
|
+
case 'ArrowDown':
|
|
412
|
+
case 'ArrowLeft':
|
|
413
|
+
case 'ArrowRight':
|
|
414
|
+
case 'Up':
|
|
415
|
+
case 'Down':
|
|
416
|
+
case 'Left':
|
|
417
|
+
case 'Right':
|
|
418
|
+
return this.selecting ? undefined : 'handled';
|
|
419
|
+
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return 'handled'; // we will consume
|
|
423
|
+
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
public UpdateScale(scale: number): void {
|
|
427
|
+
|
|
428
|
+
// we're not changing in place, so this won't affect any open editors...
|
|
429
|
+
// I think there's a case where you change scale without focusing (using
|
|
430
|
+
// the mouse wheel) which might result in incorrect rendering... TODO/FIXME
|
|
431
|
+
|
|
432
|
+
this.scale = scale;
|
|
433
|
+
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
}
|