@trebco/treb 27.12.2 → 28.2.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.
Files changed (44) hide show
  1. package/README.md +6 -0
  2. package/dist/treb-spreadsheet-light.mjs +16 -0
  3. package/dist/treb-spreadsheet.mjs +13 -11
  4. package/dist/treb.d.ts +33 -5
  5. package/esbuild-custom-element.mjs +3 -1
  6. package/package.json +8 -6
  7. package/treb-base-types/src/dom-utilities.ts +157 -19
  8. package/treb-base-types/src/import.ts +1 -0
  9. package/treb-base-types/src/theme.ts +5 -4
  10. package/treb-charts/src/renderer.ts +4 -58
  11. package/treb-embed/markup/layout.html +4 -0
  12. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +131 -87
  13. package/treb-embed/src/embedded-spreadsheet.ts +153 -140
  14. package/treb-embed/src/options.ts +9 -0
  15. package/treb-embed/src/spinner.ts +5 -3
  16. package/treb-embed/src/toolbar-message.ts +5 -0
  17. package/treb-embed/style/layout.scss +65 -1
  18. package/treb-export/src/export-worker/export-worker.ts +7 -12
  19. package/treb-export/src/export2.ts +57 -33
  20. package/treb-export/src/import2.ts +61 -21
  21. package/treb-export/src/workbook2.ts +69 -24
  22. package/treb-export/src/zip-wrapper.ts +96 -0
  23. package/treb-grid/src/editors/autocomplete.ts +24 -13
  24. package/treb-grid/src/editors/editor.ts +43 -139
  25. package/treb-grid/src/editors/external_editor.ts +1 -1
  26. package/treb-grid/src/editors/formula_bar.ts +24 -24
  27. package/treb-grid/src/editors/overlay_editor.ts +1 -1
  28. package/treb-grid/src/layout/base_layout.ts +34 -25
  29. package/treb-grid/src/layout/grid_layout.ts +20 -20
  30. package/treb-grid/src/render/selection-renderer.ts +3 -3
  31. package/treb-grid/src/render/svg_header_overlay.ts +6 -4
  32. package/treb-grid/src/render/svg_selection_block.ts +10 -7
  33. package/treb-grid/src/types/annotation.ts +2 -2
  34. package/treb-grid/src/types/grid.ts +80 -81
  35. package/treb-grid/src/types/scale-control.ts +69 -81
  36. package/treb-grid/src/types/sheet.ts +3 -52
  37. package/treb-grid/src/types/tab_bar.ts +27 -13
  38. package/treb-grid/src/util/fontmetrics2.ts +24 -21
  39. package/treb-utils/src/event_source.ts +23 -23
  40. package/treb-utils/src/index.ts +2 -2
  41. package/treb-utils/src/measurement.ts +24 -24
  42. package/treb-utils/src/serialize_html.ts +25 -21
  43. package/treb-utils/src/dispatch.ts +0 -57
  44. package/treb-utils/src/resizable.ts +0 -159
@@ -0,0 +1,96 @@
1
+
2
+ import UZip from 'uzip';
3
+ import Base64JS from 'base64-js';
4
+
5
+ export class ZipWrapper {
6
+
7
+ public records: UZip.UZIPFiles;
8
+ public text: Map<string, string> = new Map();
9
+
10
+ public constructor(buffer: ArrayBuffer) {
11
+ this.records = UZip.parse(buffer);
12
+ }
13
+
14
+ /**
15
+ * check if entry exists
16
+ */
17
+ public Has(path: string) {
18
+ return this.text.has(path) || !!this.records?.[path];
19
+ }
20
+
21
+ /**
22
+ * nondestructive
23
+ */
24
+ public ArrayBuffer() {
25
+
26
+ const records: Record<string, Uint8Array> = {};
27
+ if (this.records) {
28
+ for (const [key, value] of Object.entries(this.records)) {
29
+ records[key] = new Uint8Array(value);
30
+ }
31
+ }
32
+
33
+ const encoder = new TextEncoder();
34
+ for (const [key, value] of this.text.entries()) {
35
+ records[key] = encoder.encode(value);
36
+ }
37
+
38
+ return UZip.encode(records);
39
+
40
+ }
41
+
42
+ /**
43
+ * set a binary file. set this directly in the records table,
44
+ * instead of the text table.
45
+ */
46
+ public SetBinary(path: string, data: string, encoding?: 'base64'|'binary') {
47
+
48
+ if (encoding === 'base64') {
49
+ const bytes = Base64JS.toByteArray(data);
50
+ this.records[path] = bytes;
51
+ }
52
+ else {
53
+ throw new Error('unsupported encoding: ' + encoding);
54
+ }
55
+
56
+ }
57
+
58
+ public Set(path: string, text: string) {
59
+
60
+ this.text.set(path, text);
61
+ if (!!this.records[path]) {
62
+ delete this.records[path];
63
+ }
64
+ }
65
+
66
+ public GetBinary(path: string) {
67
+ const data = this.records[path];
68
+ if (data) {
69
+ return new Uint8Array(data);
70
+ }
71
+ throw new Error('path not in records: ' + path);
72
+ }
73
+
74
+ public Get(path: string) {
75
+
76
+ let text = this.text.get(path);
77
+
78
+ if (text) {
79
+ return text;
80
+ }
81
+
82
+ const data = this.records[path];
83
+ if (data) {
84
+ text = new TextDecoder().decode(data);
85
+ this.text.set(path, text);
86
+ delete this.records[path];
87
+ return text;
88
+ }
89
+
90
+ console.info(this);
91
+
92
+ throw new Error('path not in zip file: ' + path);
93
+
94
+ }
95
+
96
+ }
@@ -19,7 +19,7 @@
19
19
  *
20
20
  */
21
21
 
22
- import { DOMUtilities } from 'treb-base-types';
22
+ import { DOMContext } from 'treb-base-types';
23
23
  import type { Theme, Rectangle } from 'treb-base-types';
24
24
  import type { AutocompleteExecResult, DescriptorType } from './autocomplete_matcher';
25
25
 
@@ -72,26 +72,37 @@ export class Autocomplete {
72
72
 
73
73
  private active_element?: HTMLElement;
74
74
 
75
+ private DOM: DOMContext;
76
+
75
77
  constructor(private options: AutocompleteOptions = {}){
76
78
 
79
+ this.DOM = DOMContext.GetInstance(options.container?.ownerDocument);
80
+ if (!this.DOM.doc) {
81
+ throw new Error('invalid context');
82
+ }
83
+
77
84
  // this.scope = 'AC' + Math.round(Math.random() * Math.pow(10, 10)).toString(16);
78
85
 
79
- this.completion_list = DOMUtilities.Div(
86
+ this.completion_list = this.DOM.Div(
80
87
  'treb-cell-editor-ac-list treb-autocomplete',
81
- options.container || document.body,
82
- ); // this.scope);
88
+ options.container || this.DOM.doc.body,
89
+ {
90
+ events: {
91
+ mousedown: (event) => this.ListMouseDown(event),
92
+ mousemove: (event) => this.ListMouseMove(event)
93
+ }
94
+ }
95
+ );
83
96
 
84
- this.completion_list.addEventListener('mousedown', (event) => this.ListMouseDown(event));
97
+ // this.completion_list.addEventListener('mousedown', (event) => this.ListMouseDown(event));
85
98
 
86
99
  // FIXME: should we add/remove listener based on visibility? (...)
87
100
 
88
- this.completion_list.addEventListener('mousemove', (event) => this.ListMouseMove(event));
89
-
90
- this.tooltip = DOMUtilities.Div('treb-cell-editor-ac-tooltip treb-autocomplete-tooltip',
91
- options.container || document.body,
92
- ); // this.scope);
101
+ // this.completion_list.addEventListener('mousemove', (event) => this.ListMouseMove(event));
93
102
 
94
- // this.UpdateTheme();
103
+ this.tooltip = this.DOM.Div(
104
+ 'treb-cell-editor-ac-tooltip treb-autocomplete-tooltip',
105
+ options.container || this.DOM.doc.body);
95
106
 
96
107
  }
97
108
 
@@ -360,8 +371,8 @@ export class Autocomplete {
360
371
  // compiler thinks this is possibly undefined, but vs code does
361
372
  // not -- I thought vs code used the same tsc we use to compile?
362
373
 
363
- if (document.documentElement) {
364
- const viewport_height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
374
+ if (this.DOM.doc?.documentElement) {
375
+ const viewport_height = Math.max(this.DOM.doc.documentElement.clientHeight, this.DOM.view?.innerHeight || 0);
365
376
  if (viewport_height - position.bottom < 200 ){
366
377
  layout_top = true;
367
378
  }
@@ -32,9 +32,15 @@
32
32
  *
33
33
  * subclasses or callers can handle those.
34
34
  *
35
+ * ---
36
+ *
37
+ * NOTE: external editors might run in a different realm (in the js meaning
38
+ * of that term). so we don't necessarily want to use the spreadsheet's context
39
+ * for everything. this is going to be extremely confusing.
40
+ *
35
41
  */
36
42
 
37
- import { Area, type IArea, type ICellAddress, IsCellAddress, Localization, type Theme, Rectangle, type Cell, DOMUtilities } from 'treb-base-types';
43
+ import { Area, type IArea, type ICellAddress, IsCellAddress, Localization, type Theme, Rectangle, type Cell, DOMContext } from 'treb-base-types';
38
44
  import type { ExpressionUnit, ParseResult, UnitAddress, UnitRange } from 'treb-parser';
39
45
  import { Parser, QuotedSheetNameRegex } from 'treb-parser';
40
46
  import type { DataModel, ViewModel } from '../types/data_model';
@@ -221,15 +227,16 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
221
227
  // handles that but this will return false. the test is "is the
222
228
  // cursor in or at the end of a reference?"
223
229
 
224
- if (this.active_editor && this.active_editor.node === document.activeElement) {
230
+ if (this.active_editor && this.active_editor.node === this.active_editor.node.ownerDocument.activeElement) {
225
231
 
226
- const selection = window.getSelection();
232
+ const view = this.active_editor.node.ownerDocument.defaultView as (Window & typeof globalThis);
233
+ const selection = view.getSelection();
227
234
  const count = selection?.rangeCount;
228
235
 
229
236
  if (count) {
230
237
 
231
238
  const range = selection?.getRangeAt(0);
232
- const element = range?.endContainer instanceof HTMLElement ? range.endContainer :
239
+ const element = range?.endContainer instanceof view.HTMLElement ? range.endContainer :
233
240
  range.endContainer?.parentElement;
234
241
 
235
242
  // this is a reference, assume we're selecting (we will replace)
@@ -249,7 +256,7 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
249
256
 
250
257
  // start, not end
251
258
 
252
- if (range?.startContainer instanceof Text) {
259
+ if (range?.startContainer instanceof view.Text) {
253
260
  const str = (range.startContainer.textContent?.substring(0, range.startOffset) || '').trim();
254
261
  if (str.length && Editor.FormulaChars.includes(str[str.length - 1])) {
255
262
  return true;
@@ -315,8 +322,9 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
315
322
  }
316
323
 
317
324
  protected SelectAll(node: HTMLElement) {
318
- const selection = window.getSelection();
319
- const range = document.createRange();
325
+ const view = node.ownerDocument.defaultView as (Window & typeof globalThis);
326
+ const selection = view.getSelection();
327
+ const range = node.ownerDocument.createRange();
320
328
  range.selectNode(node);
321
329
  selection?.removeAllRanges();
322
330
  selection?.addRange(range);
@@ -326,12 +334,15 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
326
334
  start: { node: ChildNode, offset: number },
327
335
  end?: { node: ChildNode, offset: number }) {
328
336
 
329
- const selection = window.getSelection();
330
- const range = document.createRange();
337
+ const doc = start.node.ownerDocument;
338
+ const view = doc?.defaultView as (Window & typeof globalThis);
339
+
340
+ const selection = view.getSelection();
341
+ const range = doc?.createRange();
331
342
 
332
343
  const FirstTextNode = (node: ChildNode) => {
333
344
  let target: Node = node;
334
- while (target && !(target instanceof Text) && !!target.firstChild) {
345
+ while (target && !(target instanceof view.Text) && !!target.firstChild) {
335
346
  target = target.firstChild;
336
347
  }
337
348
  return target;
@@ -369,7 +380,9 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
369
380
  return;
370
381
  }
371
382
 
372
- const selection = window.getSelection();
383
+ const view = this.active_editor.node.ownerDocument.defaultView as (Window & typeof globalThis);
384
+
385
+ const selection = view.getSelection();
373
386
  if (!selection) {
374
387
  throw new Error('error getting selection');
375
388
  }
@@ -401,7 +414,7 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
401
414
  // might include _more_ than an existing reference, and we want to
402
415
  // replace the entire range selection.
403
416
 
404
- if (range.startContainer instanceof Text) {
417
+ if (range.startContainer instanceof view.Text) {
405
418
 
406
419
  // first case: range selected
407
420
  if (!range.collapsed && range.startOffset < range.endOffset) {
@@ -436,7 +449,7 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
436
449
  // check if we're in a reference node; if so, replace
437
450
 
438
451
  const parent = range.startContainer.parentElement;
439
- if (parent instanceof HTMLElement && parent.dataset.reference) {
452
+ if (parent instanceof view.HTMLElement && parent.dataset.reference) {
440
453
 
441
454
  // console.info('case 2');
442
455
 
@@ -508,7 +521,7 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
508
521
  // the text. we'll want to create a node, and we'll want to set the
509
522
  // cursor at the end.
510
523
 
511
- if (range.startContainer instanceof HTMLElement) {
524
+ if (range.startContainer instanceof view.HTMLElement) {
512
525
  range.startContainer.textContent = reference;
513
526
  this.SetCaret({
514
527
  node: range.startContainer,
@@ -556,6 +569,8 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
556
569
  */
557
570
  protected UpdateColors(force_event = false) {
558
571
 
572
+ const view = this.active_editor?.node.ownerDocument.defaultView as (Window & typeof globalThis);
573
+
559
574
  // create a map of canonical label -> area
560
575
 
561
576
  const map: Map<string, Area> = new Map();
@@ -582,7 +597,7 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
582
597
 
583
598
  for (const entry of this.nodes) {
584
599
  for (const node of Array.from(entry.node.childNodes)) {
585
- if (node instanceof HTMLElement && node.dataset.reference) {
600
+ if (node instanceof view.HTMLElement && node.dataset.reference) {
586
601
  const index = indexes.get(node.dataset.reference);
587
602
  node.dataset.highlightIndex = (typeof index === 'number') ? (index % 5 + 1).toString() : '?';
588
603
  }
@@ -765,6 +780,8 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
765
780
  const node = descriptor.node;
766
781
  const text = node.textContent || '';
767
782
 
783
+ const DOM = DOMContext.GetInstance(node.ownerDocument);
784
+
768
785
  // set this flag so we can use it in `get selected()`
769
786
 
770
787
  this.text_formula = text[0] === '=';
@@ -843,11 +860,11 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
843
860
  let text_index = 0;
844
861
  let last_text_node: Text|undefined;
845
862
 
846
- const fragment = document.createDocumentFragment();
863
+ const fragment = DOM.Fragment();
847
864
 
848
865
  const AddNode = (text: string, type = 'text', reference = '', force_selection = false) => {
849
866
 
850
- const text_node = document.createTextNode(text);
867
+ const text_node = DOM.Text(text);
851
868
 
852
869
  if (force_selection || ((caret_start > text_index || (caret_start === 0 && text_index === 0)) && caret_start <= text_index + text.length)) {
853
870
  selection_start = {
@@ -865,7 +882,7 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
865
882
 
866
883
  if (type !== 'text') {
867
884
 
868
- const span = DOMUtilities.Create('span', type);
885
+ const span = DOM.Create('span', type);
869
886
 
870
887
  if (reference) {
871
888
  span.dataset.reference = reference;
@@ -1101,10 +1118,13 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
1101
1118
 
1102
1119
  const result: [string, string] = ['', ''];
1103
1120
 
1104
- if (node !== document.activeElement || node !== this.active_editor?.node) {
1121
+ if (node !== node.ownerDocument.activeElement || node !== this.active_editor?.node) {
1105
1122
  return result;
1106
1123
  }
1107
-
1124
+
1125
+ const doc = node.ownerDocument;
1126
+ const view = doc.defaultView as (Window & typeof globalThis);
1127
+
1108
1128
  // is there a way to do this without recursing? (...)
1109
1129
  // how about string concat instead of array join, it's faster in
1110
1130
  // chrome (!)
@@ -1125,7 +1145,7 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
1125
1145
  // not sure what the offsets are even referring to, since we get
1126
1146
  // offsets > the number of child nodes. is this a bug in firefox?
1127
1147
 
1128
- if (element === range.startContainer && element === range.endContainer && !(element instanceof Text)) {
1148
+ if (element === range.startContainer && element === range.endContainer && !(element instanceof view.Text)) {
1129
1149
 
1130
1150
  /*
1131
1151
  if (range.startOffset !== 0 || range.endOffset !== 0) {
@@ -1155,7 +1175,7 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
1155
1175
  return;
1156
1176
  }
1157
1177
 
1158
- if (element instanceof Text) {
1178
+ if (element instanceof view.Text) {
1159
1179
  const text = element.textContent || '';
1160
1180
  if (!complete[0]) {
1161
1181
  result[0] += text;
@@ -1174,7 +1194,7 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
1174
1194
  }
1175
1195
  };
1176
1196
 
1177
- const selection = window.getSelection();
1197
+ const selection = view.getSelection();
1178
1198
  if (selection?.rangeCount ?? 0 > 0) {
1179
1199
  const range = selection?.getRangeAt(0);
1180
1200
  if (range) {
@@ -1185,120 +1205,4 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
1185
1205
  return result;
1186
1206
  }
1187
1207
 
1188
- /* *
1189
- * get text up to the selection. optionally use the start of the
1190
- * selection. new version does not require cloning, we can drop the
1191
- * measurement node.
1192
- *
1193
- * @param node
1194
- * @param start
1195
- * @returns
1196
- * /
1197
- protected SubstringToCaret(node: HTMLElement, start = false): string {
1198
-
1199
- if (node !== document.activeElement) {
1200
- return '';
1201
- }
1202
-
1203
- // is there a way to do this without recursing? (...)
1204
- // how about string concat instead of array join, it's faster in chrome (!)
1205
-
1206
- const Consume = (element: Node, target: Node, offset: number, parts: string[]): boolean => {
1207
- if (element === target) {
1208
- parts.push((element.textContent || '').substring(0, offset));
1209
- return false;
1210
- }
1211
- else if (element instanceof Text) {
1212
- parts.push(element.textContent || '');
1213
- return true;
1214
- }
1215
- else if (element.hasChildNodes()) {
1216
- for (const child of element.childNodes) {
1217
- const result = Consume(child, target, offset, parts);
1218
- if (!result) {
1219
- return false;
1220
- }
1221
- }
1222
- return true;
1223
- }
1224
- return false;
1225
- };
1226
-
1227
- const selection = window.getSelection();
1228
- if (selection?.rangeCount ?? 0 > 0) {
1229
-
1230
- const range = selection?.getRangeAt(0);
1231
- if (range) {
1232
- let [target, offset] = start ?
1233
- [range.startContainer, range.startOffset] :
1234
- [range.endContainer, range.endOffset];
1235
-
1236
- const parts: string[] = [];
1237
- Consume(node, target, offset, parts);
1238
- return parts.join('');
1239
-
1240
- }
1241
-
1242
- }
1243
-
1244
- return '';
1245
-
1246
- }
1247
- */
1248
-
1249
- /* *
1250
- * we've been carrying this function around for a while. is this
1251
- * still the best way to do this in 2023? (...)
1252
- *
1253
- * get text substring to caret position, irrespective of node structure
1254
- *
1255
- * @param start - use the start of the selection instead of the end
1256
- * /
1257
- protected SubstringToCaret(node: HTMLElement, start = false): string {
1258
-
1259
- if (node !== this.editor_node || node !== document.activeElement) {
1260
- return '';
1261
- }
1262
-
1263
- // we could probably shortcut if text is empty
1264
-
1265
- const selection = window.getSelection();
1266
- if (!selection) {
1267
- throw new Error('error getting selection');
1268
- }
1269
-
1270
- if (selection.rangeCount === 0) {
1271
- // console.warn('range count is 0');
1272
- return '';
1273
- }
1274
-
1275
- const range = selection.getRangeAt(0);
1276
- const preCaretRange = range.cloneRange();
1277
-
1278
- preCaretRange.selectNodeContents(node);
1279
- if (start) {
1280
- preCaretRange.setEnd(range.startContainer, range.startOffset);
1281
- }
1282
- else {
1283
- preCaretRange.setEnd(range.endContainer, range.endOffset);
1284
- }
1285
-
1286
- this.measurement_node.textContent = '';
1287
- this.measurement_node.appendChild(preCaretRange.cloneContents());
1288
-
1289
- const result = this.measurement_node.textContent || '';
1290
- const result2 = this.SubstringToCaret2(node, start);
1291
-
1292
- // console.info('X', result === result2, {result, result2});
1293
- if (result !== result2) {
1294
- throw new Error('mismatch');
1295
- }
1296
-
1297
- return result;
1298
-
1299
- // return this.measurement_node.textContent || '';
1300
-
1301
- }
1302
- */
1303
-
1304
1208
  }
@@ -70,7 +70,7 @@ export class ExternalEditor extends Editor {
70
70
 
71
71
  // if it's already focused, set as active
72
72
 
73
- if (document.activeElement === descriptor.node) {
73
+ if (descriptor.node.ownerDocument.activeElement === descriptor.node) {
74
74
  this.active_editor = descriptor;
75
75
  }
76
76
 
@@ -25,7 +25,7 @@ import { Parser } from 'treb-parser';
25
25
  import type { DataModel, ViewModel } from '../types/data_model';
26
26
  import type { GridOptions } from '../types/grid_options';
27
27
  import { Autocomplete } from './autocomplete';
28
- import { DOMUtilities } from 'treb-base-types';
28
+ import { DOMContext } from 'treb-base-types';
29
29
 
30
30
  // --- from formula_bar ---
31
31
 
@@ -162,6 +162,7 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
162
162
  ) {
163
163
 
164
164
  super(model, view, autocomplete);
165
+ const DOM = DOMContext.GetInstance(container.ownerDocument);
165
166
 
166
167
  const inner_node = container.querySelector('.treb-formula-bar') as HTMLElement;
167
168
  inner_node.removeAttribute('hidden');
@@ -172,7 +173,7 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
172
173
  this.InitAddressLabel();
173
174
 
174
175
  if (this.options.insert_function_button) {
175
- this.button = DOMUtilities.Create('button', 'formula-button', inner_node);
176
+ this.button = DOM.Create('button', 'formula-button', inner_node);
176
177
  this.button.addEventListener('click', () => {
177
178
  const formula: string = this.active_editor ? this.active_editor.node.textContent || '' : '';
178
179
  this.Publish({ type: 'formula-button', formula });
@@ -270,20 +271,24 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
270
271
  this.RegisterListener(descriptor, 'keyup', this.FormulaKeyUp.bind(this));
271
272
 
272
273
  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
- }
274
+ this.expand_button = DOM.Create('button', 'expand-button', inner_node, {
275
+ events: {
276
+ click: (event: MouseEvent) => {
277
+ event.stopPropagation();
278
+ event.preventDefault();
279
+ if (this.active_editor) {
280
+ this.active_editor.node.scrollTop = 0;
281
+ }
282
+ if (inner_node.hasAttribute('expanded')) {
283
+ inner_node.removeAttribute('expanded');
284
+ }
285
+ else {
286
+ inner_node.setAttribute('expanded', '');
287
+ }
288
+ },
289
+ },
286
290
  });
291
+
287
292
  }
288
293
 
289
294
  }
@@ -303,24 +308,19 @@ export class FormulaBar extends Editor<FormulaBar2Event|FormulaEditorEvent> {
303
308
 
304
309
  this.address_label.addEventListener('focusin', (event) => {
305
310
 
311
+ const doc = this.address_label.ownerDocument;
312
+
306
313
  // FIXME: close any open editors? (...)
307
314
 
308
315
  // we're now doing this async for all browsers... it's only really
309
316
  // necessary for IE11 and safari, but doesn't hurt
310
317
 
311
318
  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();
319
+ const selection = (doc.defaultView as (Window & typeof globalThis)).getSelection();
320
+ const range = doc.createRange();
320
321
  range.selectNodeContents(this.address_label);
321
322
  selection?.removeAllRanges();
322
323
  selection?.addRange(range);
323
- }
324
324
  });
325
325
 
326
326
  });
@@ -209,7 +209,7 @@ export class OverlayEditor extends Editor<ResetSelectionEvent> {
209
209
  // by default the editor is at (0, 0), so we need to move it before we
210
210
  // focus on it (but only in this case).
211
211
 
212
- if (this.edit_node !== document.activeElement) {
212
+ if (this.edit_node !== this.edit_node.ownerDocument.activeElement) {
213
213
 
214
214
  // this was not correct, but should we add those 2 pixels back?
215
215