@xterm/xterm 5.6.0-beta.6 → 5.6.0-beta.61

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 (118) hide show
  1. package/README.md +7 -3
  2. package/css/xterm.css +71 -4
  3. package/lib/xterm.js +1 -1
  4. package/lib/xterm.js.map +1 -1
  5. package/lib/xterm.mjs +53 -0
  6. package/lib/xterm.mjs.map +7 -0
  7. package/package.json +43 -33
  8. package/src/browser/AccessibilityManager.ts +53 -25
  9. package/src/browser/{Terminal.ts → CoreBrowserTerminal.ts} +132 -144
  10. package/src/browser/Linkifier.ts +15 -13
  11. package/src/browser/LocalizableStrings.ts +15 -4
  12. package/src/browser/{Types.d.ts → Types.ts} +67 -15
  13. package/src/browser/Viewport.ts +142 -370
  14. package/src/browser/decorations/BufferDecorationRenderer.ts +14 -9
  15. package/src/browser/decorations/OverviewRulerRenderer.ts +40 -44
  16. package/src/browser/public/Terminal.ts +25 -19
  17. package/src/browser/renderer/dom/DomRenderer.ts +14 -16
  18. package/src/browser/renderer/shared/CharAtlasUtils.ts +4 -0
  19. package/src/browser/renderer/shared/CustomGlyphs.ts +6 -0
  20. package/src/browser/renderer/shared/DevicePixelObserver.ts +1 -2
  21. package/src/browser/renderer/shared/TextureAtlas.ts +3 -3
  22. package/src/browser/renderer/shared/{Types.d.ts → Types.ts} +4 -4
  23. package/src/browser/services/CharSizeService.ts +6 -6
  24. package/src/browser/services/CoreBrowserService.ts +15 -15
  25. package/src/browser/services/LinkProviderService.ts +2 -2
  26. package/src/browser/services/RenderService.ts +20 -20
  27. package/src/browser/services/SelectionService.ts +8 -8
  28. package/src/browser/services/Services.ts +13 -13
  29. package/src/browser/services/ThemeService.ts +17 -56
  30. package/src/browser/shared/Constants.ts +8 -0
  31. package/src/common/CircularList.ts +5 -5
  32. package/src/common/CoreTerminal.ts +35 -41
  33. package/src/common/InputHandler.ts +34 -28
  34. package/src/common/{Types.d.ts → Types.ts} +11 -17
  35. package/src/common/buffer/Buffer.ts +5 -1
  36. package/src/common/buffer/BufferSet.ts +5 -5
  37. package/src/common/buffer/Marker.ts +4 -4
  38. package/src/common/buffer/{Types.d.ts → Types.ts} +2 -2
  39. package/src/common/input/WriteBuffer.ts +3 -3
  40. package/src/common/parser/EscapeSequenceParser.ts +4 -4
  41. package/src/common/public/BufferNamespaceApi.ts +3 -3
  42. package/src/common/services/BufferService.ts +7 -7
  43. package/src/common/services/CoreMouseService.ts +5 -3
  44. package/src/common/services/CoreService.ts +6 -6
  45. package/src/common/services/DecorationService.ts +8 -9
  46. package/src/common/services/LogService.ts +2 -2
  47. package/src/common/services/OptionsService.ts +5 -5
  48. package/src/common/services/Services.ts +24 -17
  49. package/src/common/services/UnicodeService.ts +2 -2
  50. package/src/vs/base/browser/browser.ts +141 -0
  51. package/src/vs/base/browser/canIUse.ts +49 -0
  52. package/src/vs/base/browser/dom.ts +2369 -0
  53. package/src/vs/base/browser/fastDomNode.ts +316 -0
  54. package/src/vs/base/browser/globalPointerMoveMonitor.ts +112 -0
  55. package/src/vs/base/browser/iframe.ts +135 -0
  56. package/src/vs/base/browser/keyboardEvent.ts +213 -0
  57. package/src/vs/base/browser/mouseEvent.ts +229 -0
  58. package/src/vs/base/browser/touch.ts +372 -0
  59. package/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +303 -0
  60. package/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +114 -0
  61. package/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +720 -0
  62. package/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +165 -0
  63. package/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +114 -0
  64. package/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +243 -0
  65. package/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +118 -0
  66. package/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +116 -0
  67. package/src/vs/base/browser/ui/widget.ts +57 -0
  68. package/src/vs/base/browser/window.ts +14 -0
  69. package/src/vs/base/common/arrays.ts +887 -0
  70. package/src/vs/base/common/arraysFind.ts +202 -0
  71. package/src/vs/base/common/assert.ts +71 -0
  72. package/src/vs/base/common/async.ts +1992 -0
  73. package/src/vs/base/common/cancellation.ts +148 -0
  74. package/src/vs/base/common/charCode.ts +450 -0
  75. package/src/vs/base/common/collections.ts +140 -0
  76. package/src/vs/base/common/decorators.ts +130 -0
  77. package/src/vs/base/common/equals.ts +146 -0
  78. package/src/vs/base/common/errors.ts +303 -0
  79. package/src/vs/base/common/event.ts +1778 -0
  80. package/src/vs/base/common/functional.ts +32 -0
  81. package/src/vs/base/common/hash.ts +316 -0
  82. package/src/vs/base/common/iterator.ts +159 -0
  83. package/src/vs/base/common/keyCodes.ts +526 -0
  84. package/src/vs/base/common/keybindings.ts +284 -0
  85. package/src/vs/base/common/lazy.ts +47 -0
  86. package/src/vs/base/common/lifecycle.ts +801 -0
  87. package/src/vs/base/common/linkedList.ts +142 -0
  88. package/src/vs/base/common/map.ts +202 -0
  89. package/src/vs/base/common/numbers.ts +98 -0
  90. package/src/vs/base/common/observable.ts +76 -0
  91. package/src/vs/base/common/observableInternal/api.ts +31 -0
  92. package/src/vs/base/common/observableInternal/autorun.ts +281 -0
  93. package/src/vs/base/common/observableInternal/base.ts +489 -0
  94. package/src/vs/base/common/observableInternal/debugName.ts +145 -0
  95. package/src/vs/base/common/observableInternal/derived.ts +428 -0
  96. package/src/vs/base/common/observableInternal/lazyObservableValue.ts +146 -0
  97. package/src/vs/base/common/observableInternal/logging.ts +328 -0
  98. package/src/vs/base/common/observableInternal/promise.ts +209 -0
  99. package/src/vs/base/common/observableInternal/utils.ts +610 -0
  100. package/src/vs/base/common/platform.ts +281 -0
  101. package/src/vs/base/common/scrollable.ts +522 -0
  102. package/src/vs/base/common/sequence.ts +34 -0
  103. package/src/vs/base/common/stopwatch.ts +43 -0
  104. package/src/vs/base/common/strings.ts +557 -0
  105. package/src/vs/base/common/symbols.ts +9 -0
  106. package/src/vs/base/common/uint.ts +59 -0
  107. package/src/vs/patches/nls.ts +90 -0
  108. package/src/vs/typings/base-common.d.ts +20 -0
  109. package/src/vs/typings/require.d.ts +42 -0
  110. package/src/vs/typings/thenable.d.ts +12 -0
  111. package/src/vs/typings/vscode-globals-nls.d.ts +36 -0
  112. package/src/vs/typings/vscode-globals-product.d.ts +33 -0
  113. package/typings/xterm.d.ts +59 -15
  114. package/src/browser/Lifecycle.ts +0 -33
  115. package/src/common/EventEmitter.ts +0 -78
  116. package/src/common/Lifecycle.ts +0 -108
  117. /package/src/browser/selection/{Types.d.ts → Types.ts} +0 -0
  118. /package/src/common/parser/{Types.d.ts → Types.ts} +0 -0
@@ -0,0 +1,2369 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License. See License.txt in the project root for license information.
4
+ *--------------------------------------------------------------------------------------------*/
5
+
6
+ import * as browser from 'vs/base/browser/browser';
7
+ import { BrowserFeatures } from 'vs/base/browser/canIUse';
8
+ import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
9
+ import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
10
+ import { AbstractIdleValue, IntervalTimer, TimeoutTimer, _runWhenIdle, IdleDeadline } from 'vs/base/common/async';
11
+ import { onUnexpectedError } from 'vs/base/common/errors';
12
+ import * as event from 'vs/base/common/event';
13
+ import { KeyCode } from 'vs/base/common/keyCodes';
14
+ import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
15
+ import * as platform from 'vs/base/common/platform';
16
+ import { hash } from 'vs/base/common/hash';
17
+ import { CodeWindow, ensureCodeWindow, mainWindow } from 'vs/base/browser/window';
18
+ import { isPointWithinTriangle } from 'vs/base/common/numbers';
19
+
20
+ export interface IRegisteredCodeWindow {
21
+ readonly window: CodeWindow;
22
+ readonly disposables: DisposableStore;
23
+ }
24
+
25
+ //# region Multi-Window Support Utilities
26
+
27
+ export const {
28
+ registerWindow,
29
+ getWindow,
30
+ getDocument,
31
+ getWindows,
32
+ getWindowsCount,
33
+ getWindowId,
34
+ getWindowById,
35
+ hasWindow,
36
+ onDidRegisterWindow,
37
+ onWillUnregisterWindow,
38
+ onDidUnregisterWindow
39
+ } = (function () {
40
+ const windows = new Map<number, IRegisteredCodeWindow>();
41
+
42
+ ensureCodeWindow(mainWindow, 1);
43
+ const mainWindowRegistration = { window: mainWindow, disposables: new DisposableStore() };
44
+ windows.set(mainWindow.vscodeWindowId, mainWindowRegistration);
45
+
46
+ const onDidRegisterWindow = new event.Emitter<IRegisteredCodeWindow>();
47
+ const onDidUnregisterWindow = new event.Emitter<CodeWindow>();
48
+ const onWillUnregisterWindow = new event.Emitter<CodeWindow>();
49
+
50
+ function getWindowById(windowId: number): IRegisteredCodeWindow | undefined;
51
+ function getWindowById(windowId: number | undefined, fallbackToMain: true): IRegisteredCodeWindow;
52
+ function getWindowById(windowId: number | undefined, fallbackToMain?: boolean): IRegisteredCodeWindow | undefined {
53
+ const window = typeof windowId === 'number' ? windows.get(windowId) : undefined;
54
+
55
+ return window ?? (fallbackToMain ? mainWindowRegistration : undefined);
56
+ }
57
+
58
+ return {
59
+ onDidRegisterWindow: onDidRegisterWindow.event,
60
+ onWillUnregisterWindow: onWillUnregisterWindow.event,
61
+ onDidUnregisterWindow: onDidUnregisterWindow.event,
62
+ registerWindow(window: CodeWindow): IDisposable {
63
+ if (windows.has(window.vscodeWindowId)) {
64
+ return Disposable.None;
65
+ }
66
+
67
+ const disposables = new DisposableStore();
68
+
69
+ const registeredWindow = {
70
+ window,
71
+ disposables: disposables.add(new DisposableStore())
72
+ };
73
+ windows.set(window.vscodeWindowId, registeredWindow);
74
+
75
+ disposables.add(toDisposable(() => {
76
+ windows.delete(window.vscodeWindowId);
77
+ onDidUnregisterWindow.fire(window);
78
+ }));
79
+
80
+ disposables.add(addDisposableListener(window, EventType.BEFORE_UNLOAD, () => {
81
+ onWillUnregisterWindow.fire(window);
82
+ }));
83
+
84
+ onDidRegisterWindow.fire(registeredWindow);
85
+
86
+ return disposables;
87
+ },
88
+ getWindows(): Iterable<IRegisteredCodeWindow> {
89
+ return windows.values();
90
+ },
91
+ getWindowsCount(): number {
92
+ return windows.size;
93
+ },
94
+ getWindowId(targetWindow: Window): number {
95
+ return (targetWindow as CodeWindow).vscodeWindowId;
96
+ },
97
+ hasWindow(windowId: number): boolean {
98
+ return windows.has(windowId);
99
+ },
100
+ getWindowById,
101
+ getWindow(e: Node | UIEvent | undefined | null): CodeWindow {
102
+ const candidateNode = e as Node | undefined | null;
103
+ if (candidateNode?.ownerDocument?.defaultView) {
104
+ return candidateNode.ownerDocument.defaultView.window as CodeWindow;
105
+ }
106
+
107
+ const candidateEvent = e as UIEvent | undefined | null;
108
+ if (candidateEvent?.view) {
109
+ return candidateEvent.view.window as CodeWindow;
110
+ }
111
+
112
+ return mainWindow;
113
+ },
114
+ getDocument(e: Node | UIEvent | undefined | null): Document {
115
+ const candidateNode = e as Node | undefined | null;
116
+ return getWindow(candidateNode).document;
117
+ }
118
+ };
119
+ })();
120
+
121
+ //#endregion
122
+
123
+ export function clearNode(node: HTMLElement): void {
124
+ while (node.firstChild) {
125
+ node.firstChild.remove();
126
+ }
127
+ }
128
+
129
+
130
+ export function clearNodeRecursively(domNode: ChildNode) {
131
+ while (domNode.firstChild) {
132
+ const element = domNode.firstChild;
133
+ element.remove();
134
+ clearNodeRecursively(element);
135
+ }
136
+ }
137
+
138
+
139
+ class DomListener implements IDisposable {
140
+
141
+ private _handler: (e: any) => void;
142
+ private _node: EventTarget;
143
+ private readonly _type: string;
144
+ private readonly _options: boolean | AddEventListenerOptions;
145
+
146
+ constructor(node: EventTarget, type: string, handler: (e: any) => void, options?: boolean | AddEventListenerOptions) {
147
+ this._node = node;
148
+ this._type = type;
149
+ this._handler = handler;
150
+ this._options = (options || false);
151
+ this._node.addEventListener(this._type, this._handler, this._options);
152
+ }
153
+
154
+ dispose(): void {
155
+ if (!this._handler) {
156
+ // Already disposed
157
+ return;
158
+ }
159
+
160
+ this._node.removeEventListener(this._type, this._handler, this._options);
161
+
162
+ // Prevent leakers from holding on to the dom or handler func
163
+ this._node = null!;
164
+ this._handler = null!;
165
+ }
166
+ }
167
+
168
+ export function addDisposableListener<K extends keyof GlobalEventHandlersEventMap>(node: EventTarget, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCapture?: boolean): IDisposable;
169
+ export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable;
170
+ export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, options: AddEventListenerOptions): IDisposable;
171
+ export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCaptureOrOptions?: boolean | AddEventListenerOptions): IDisposable {
172
+ return new DomListener(node, type, handler, useCaptureOrOptions);
173
+ }
174
+
175
+ export interface IAddStandardDisposableListenerSignature {
176
+ (node: HTMLElement, type: 'click', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable;
177
+ (node: HTMLElement, type: 'mousedown', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable;
178
+ (node: HTMLElement, type: 'keydown', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable;
179
+ (node: HTMLElement, type: 'keypress', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable;
180
+ (node: HTMLElement, type: 'keyup', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable;
181
+ (node: HTMLElement, type: 'pointerdown', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable;
182
+ (node: HTMLElement, type: 'pointermove', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable;
183
+ (node: HTMLElement, type: 'pointerup', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable;
184
+ (node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable;
185
+ }
186
+ function _wrapAsStandardMouseEvent(targetWindow: Window, handler: (e: IMouseEvent) => void): (e: MouseEvent) => void {
187
+ return function (e: MouseEvent) {
188
+ return handler(new StandardMouseEvent(targetWindow, e));
189
+ };
190
+ }
191
+ function _wrapAsStandardKeyboardEvent(handler: (e: IKeyboardEvent) => void): (e: KeyboardEvent) => void {
192
+ return function (e: KeyboardEvent) {
193
+ return handler(new StandardKeyboardEvent(e));
194
+ };
195
+ }
196
+ export const addStandardDisposableListener: IAddStandardDisposableListenerSignature = function addStandardDisposableListener(node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable {
197
+ let wrapHandler = handler;
198
+
199
+ if (type === 'click' || type === 'mousedown' || type === 'contextmenu') {
200
+ wrapHandler = _wrapAsStandardMouseEvent(getWindow(node), handler);
201
+ } else if (type === 'keydown' || type === 'keypress' || type === 'keyup') {
202
+ wrapHandler = _wrapAsStandardKeyboardEvent(handler);
203
+ }
204
+
205
+ return addDisposableListener(node, type, wrapHandler, useCapture);
206
+ };
207
+
208
+ export const addStandardDisposableGenericMouseDownListener = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable {
209
+ const wrapHandler = _wrapAsStandardMouseEvent(getWindow(node), handler);
210
+
211
+ return addDisposableGenericMouseDownListener(node, wrapHandler, useCapture);
212
+ };
213
+
214
+ export const addStandardDisposableGenericMouseUpListener = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable {
215
+ const wrapHandler = _wrapAsStandardMouseEvent(getWindow(node), handler);
216
+
217
+ return addDisposableGenericMouseUpListener(node, wrapHandler, useCapture);
218
+ };
219
+ export function addDisposableGenericMouseDownListener(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
220
+ return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_DOWN : EventType.MOUSE_DOWN, handler, useCapture);
221
+ }
222
+
223
+ export function addDisposableGenericMouseMoveListener(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
224
+ return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_MOVE : EventType.MOUSE_MOVE, handler, useCapture);
225
+ }
226
+
227
+ export function addDisposableGenericMouseUpListener(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
228
+ return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_UP : EventType.MOUSE_UP, handler, useCapture);
229
+ }
230
+
231
+ /**
232
+ * Execute the callback the next time the browser is idle, returning an
233
+ * {@link IDisposable} that will cancel the callback when disposed. This wraps
234
+ * [requestIdleCallback] so it will fallback to [setTimeout] if the environment
235
+ * doesn't support it.
236
+ *
237
+ * @param targetWindow The window for which to run the idle callback
238
+ * @param callback The callback to run when idle, this includes an
239
+ * [IdleDeadline] that provides the time alloted for the idle callback by the
240
+ * browser. Not respecting this deadline will result in a degraded user
241
+ * experience.
242
+ * @param timeout A timeout at which point to queue no longer wait for an idle
243
+ * callback but queue it on the regular event loop (like setTimeout). Typically
244
+ * this should not be used.
245
+ *
246
+ * [IdleDeadline]: https://developer.mozilla.org/en-US/docs/Web/API/IdleDeadline
247
+ * [requestIdleCallback]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
248
+ * [setTimeout]: https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout
249
+ */
250
+ export function runWhenWindowIdle(targetWindow: Window | typeof globalThis, callback: (idle: IdleDeadline) => void, timeout?: number): IDisposable {
251
+ return _runWhenIdle(targetWindow, callback, timeout);
252
+ }
253
+
254
+ /**
255
+ * An implementation of the "idle-until-urgent"-strategy as introduced
256
+ * here: https://philipwalton.com/articles/idle-until-urgent/
257
+ */
258
+ export class WindowIdleValue<T> extends AbstractIdleValue<T> {
259
+ constructor(targetWindow: Window | typeof globalThis, executor: () => T) {
260
+ super(targetWindow, executor);
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Schedule a callback to be run at the next animation frame.
266
+ * This allows multiple parties to register callbacks that should run at the next animation frame.
267
+ * If currently in an animation frame, `runner` will be executed immediately.
268
+ * @return token that can be used to cancel the scheduled runner (only if `runner` was not executed immediately).
269
+ */
270
+ export let runAtThisOrScheduleAtNextAnimationFrame: (targetWindow: Window, runner: () => void, priority?: number) => IDisposable;
271
+ /**
272
+ * Schedule a callback to be run at the next animation frame.
273
+ * This allows multiple parties to register callbacks that should run at the next animation frame.
274
+ * If currently in an animation frame, `runner` will be executed at the next animation frame.
275
+ * @return token that can be used to cancel the scheduled runner.
276
+ */
277
+ export let scheduleAtNextAnimationFrame: (targetWindow: Window, runner: () => void, priority?: number) => IDisposable;
278
+
279
+ export function disposableWindowInterval(targetWindow: Window, handler: () => void | boolean /* stop interval */ | Promise<unknown>, interval: number, iterations?: number): IDisposable {
280
+ let iteration = 0;
281
+ const timer = targetWindow.setInterval(() => {
282
+ iteration++;
283
+ if ((typeof iterations === 'number' && iteration >= iterations) || handler() === true) {
284
+ disposable.dispose();
285
+ }
286
+ }, interval);
287
+ const disposable = toDisposable(() => {
288
+ targetWindow.clearInterval(timer);
289
+ });
290
+ return disposable;
291
+ }
292
+
293
+ export class WindowIntervalTimer extends IntervalTimer {
294
+
295
+ private readonly defaultTarget?: Window & typeof globalThis;
296
+
297
+ /**
298
+ *
299
+ * @param node The optional node from which the target window is determined
300
+ */
301
+ constructor(node?: Node) {
302
+ super();
303
+ this.defaultTarget = node && getWindow(node);
304
+ }
305
+
306
+ override cancelAndSet(runner: () => void, interval: number, targetWindow?: Window & typeof globalThis): void {
307
+ return super.cancelAndSet(runner, interval, targetWindow ?? this.defaultTarget);
308
+ }
309
+ }
310
+
311
+ class AnimationFrameQueueItem implements IDisposable {
312
+
313
+ private _runner: () => void;
314
+ public priority: number;
315
+ private _canceled: boolean;
316
+
317
+ constructor(runner: () => void, priority: number = 0) {
318
+ this._runner = runner;
319
+ this.priority = priority;
320
+ this._canceled = false;
321
+ }
322
+
323
+ dispose(): void {
324
+ this._canceled = true;
325
+ }
326
+
327
+ execute(): void {
328
+ if (this._canceled) {
329
+ return;
330
+ }
331
+
332
+ try {
333
+ this._runner();
334
+ } catch (e) {
335
+ onUnexpectedError(e);
336
+ }
337
+ }
338
+
339
+ // Sort by priority (largest to lowest)
340
+ static sort(a: AnimationFrameQueueItem, b: AnimationFrameQueueItem): number {
341
+ return b.priority - a.priority;
342
+ }
343
+ }
344
+
345
+ (function () {
346
+ /**
347
+ * The runners scheduled at the next animation frame
348
+ */
349
+ const NEXT_QUEUE = new Map<number /* window ID */, AnimationFrameQueueItem[]>();
350
+ /**
351
+ * The runners scheduled at the current animation frame
352
+ */
353
+ const CURRENT_QUEUE = new Map<number /* window ID */, AnimationFrameQueueItem[]>();
354
+ /**
355
+ * A flag to keep track if the native requestAnimationFrame was already called
356
+ */
357
+ const animFrameRequested = new Map<number /* window ID */, boolean>();
358
+ /**
359
+ * A flag to indicate if currently handling a native requestAnimationFrame callback
360
+ */
361
+ const inAnimationFrameRunner = new Map<number /* window ID */, boolean>();
362
+
363
+ const animationFrameRunner = (targetWindowId: number) => {
364
+ animFrameRequested.set(targetWindowId, false);
365
+
366
+ const currentQueue = NEXT_QUEUE.get(targetWindowId) ?? [];
367
+ CURRENT_QUEUE.set(targetWindowId, currentQueue);
368
+ NEXT_QUEUE.set(targetWindowId, []);
369
+
370
+ inAnimationFrameRunner.set(targetWindowId, true);
371
+ while (currentQueue.length > 0) {
372
+ currentQueue.sort(AnimationFrameQueueItem.sort);
373
+ const top = currentQueue.shift()!;
374
+ top.execute();
375
+ }
376
+ inAnimationFrameRunner.set(targetWindowId, false);
377
+ };
378
+
379
+ scheduleAtNextAnimationFrame = (targetWindow: Window, runner: () => void, priority: number = 0) => {
380
+ const targetWindowId = getWindowId(targetWindow);
381
+ const item = new AnimationFrameQueueItem(runner, priority);
382
+
383
+ let nextQueue = NEXT_QUEUE.get(targetWindowId);
384
+ if (!nextQueue) {
385
+ nextQueue = [];
386
+ NEXT_QUEUE.set(targetWindowId, nextQueue);
387
+ }
388
+ nextQueue.push(item);
389
+
390
+ if (!animFrameRequested.get(targetWindowId)) {
391
+ animFrameRequested.set(targetWindowId, true);
392
+ targetWindow.requestAnimationFrame(() => animationFrameRunner(targetWindowId));
393
+ }
394
+
395
+ return item;
396
+ };
397
+
398
+ runAtThisOrScheduleAtNextAnimationFrame = (targetWindow: Window, runner: () => void, priority?: number) => {
399
+ const targetWindowId = getWindowId(targetWindow);
400
+ if (inAnimationFrameRunner.get(targetWindowId)) {
401
+ const item = new AnimationFrameQueueItem(runner, priority);
402
+ let currentQueue = CURRENT_QUEUE.get(targetWindowId);
403
+ if (!currentQueue) {
404
+ currentQueue = [];
405
+ CURRENT_QUEUE.set(targetWindowId, currentQueue);
406
+ }
407
+ currentQueue.push(item);
408
+ return item;
409
+ } else {
410
+ return scheduleAtNextAnimationFrame(targetWindow, runner, priority);
411
+ }
412
+ };
413
+ })();
414
+
415
+ export function measure(targetWindow: Window, callback: () => void): IDisposable {
416
+ return scheduleAtNextAnimationFrame(targetWindow, callback, 10000 /* must be early */);
417
+ }
418
+
419
+ export function modify(targetWindow: Window, callback: () => void): IDisposable {
420
+ return scheduleAtNextAnimationFrame(targetWindow, callback, -10000 /* must be late */);
421
+ }
422
+
423
+ /**
424
+ * Add a throttled listener. `handler` is fired at most every 8.33333ms or with the next animation frame (if browser supports it).
425
+ */
426
+ export interface IEventMerger<R, E> {
427
+ (lastEvent: R | null, currentEvent: E): R;
428
+ }
429
+
430
+ const MINIMUM_TIME_MS = 8;
431
+ const DEFAULT_EVENT_MERGER: IEventMerger<Event, Event> = function (lastEvent: Event | null, currentEvent: Event) {
432
+ return currentEvent;
433
+ };
434
+
435
+ class TimeoutThrottledDomListener<R, E extends Event> extends Disposable {
436
+
437
+ constructor(node: any, type: string, handler: (event: R) => void, eventMerger: IEventMerger<R, E> = <any>DEFAULT_EVENT_MERGER, minimumTimeMs: number = MINIMUM_TIME_MS) {
438
+ super();
439
+
440
+ let lastEvent: R | null = null;
441
+ let lastHandlerTime = 0;
442
+ const timeout = this._register(new TimeoutTimer());
443
+
444
+ const invokeHandler = () => {
445
+ lastHandlerTime = (new Date()).getTime();
446
+ handler(<R>lastEvent);
447
+ lastEvent = null;
448
+ };
449
+
450
+ this._register(addDisposableListener(node, type, (e) => {
451
+
452
+ lastEvent = eventMerger(lastEvent, e);
453
+ const elapsedTime = (new Date()).getTime() - lastHandlerTime;
454
+
455
+ if (elapsedTime >= minimumTimeMs) {
456
+ timeout.cancel();
457
+ invokeHandler();
458
+ } else {
459
+ timeout.setIfNotSet(invokeHandler, minimumTimeMs - elapsedTime);
460
+ }
461
+ }));
462
+ }
463
+ }
464
+
465
+ export function addDisposableThrottledListener<R, E extends Event = Event>(node: any, type: string, handler: (event: R) => void, eventMerger?: IEventMerger<R, E>, minimumTimeMs?: number): IDisposable {
466
+ return new TimeoutThrottledDomListener<R, E>(node, type, handler, eventMerger, minimumTimeMs);
467
+ }
468
+
469
+ export function getComputedStyle(el: HTMLElement): CSSStyleDeclaration {
470
+ return getWindow(el).getComputedStyle(el, null);
471
+ }
472
+
473
+ export function getClientArea(element: HTMLElement, fallback?: HTMLElement): Dimension {
474
+ const elWindow = getWindow(element);
475
+ const elDocument = elWindow.document;
476
+
477
+ // Try with DOM clientWidth / clientHeight
478
+ if (element !== elDocument.body) {
479
+ return new Dimension(element.clientWidth, element.clientHeight);
480
+ }
481
+
482
+ // If visual view port exits and it's on mobile, it should be used instead of window innerWidth / innerHeight, or document.body.clientWidth / document.body.clientHeight
483
+ if (platform.isIOS && elWindow?.visualViewport) {
484
+ return new Dimension(elWindow.visualViewport.width, elWindow.visualViewport.height);
485
+ }
486
+
487
+ // Try innerWidth / innerHeight
488
+ if (elWindow?.innerWidth && elWindow.innerHeight) {
489
+ return new Dimension(elWindow.innerWidth, elWindow.innerHeight);
490
+ }
491
+
492
+ // Try with document.body.clientWidth / document.body.clientHeight
493
+ if (elDocument.body && elDocument.body.clientWidth && elDocument.body.clientHeight) {
494
+ return new Dimension(elDocument.body.clientWidth, elDocument.body.clientHeight);
495
+ }
496
+
497
+ // Try with document.documentElement.clientWidth / document.documentElement.clientHeight
498
+ if (elDocument.documentElement && elDocument.documentElement.clientWidth && elDocument.documentElement.clientHeight) {
499
+ return new Dimension(elDocument.documentElement.clientWidth, elDocument.documentElement.clientHeight);
500
+ }
501
+
502
+ if (fallback) {
503
+ return getClientArea(fallback);
504
+ }
505
+
506
+ throw new Error('Unable to figure out browser width and height');
507
+ }
508
+
509
+ class SizeUtils {
510
+ // Adapted from WinJS
511
+ // Converts a CSS positioning string for the specified element to pixels.
512
+ private static convertToPixels(element: HTMLElement, value: string): number {
513
+ return parseFloat(value) || 0;
514
+ }
515
+
516
+ private static getDimension(element: HTMLElement, cssPropertyName: string, jsPropertyName: string): number {
517
+ const computedStyle = getComputedStyle(element);
518
+ const value = computedStyle ? computedStyle.getPropertyValue(cssPropertyName) : '0';
519
+ return SizeUtils.convertToPixels(element, value);
520
+ }
521
+
522
+ static getBorderLeftWidth(element: HTMLElement): number {
523
+ return SizeUtils.getDimension(element, 'border-left-width', 'borderLeftWidth');
524
+ }
525
+ static getBorderRightWidth(element: HTMLElement): number {
526
+ return SizeUtils.getDimension(element, 'border-right-width', 'borderRightWidth');
527
+ }
528
+ static getBorderTopWidth(element: HTMLElement): number {
529
+ return SizeUtils.getDimension(element, 'border-top-width', 'borderTopWidth');
530
+ }
531
+ static getBorderBottomWidth(element: HTMLElement): number {
532
+ return SizeUtils.getDimension(element, 'border-bottom-width', 'borderBottomWidth');
533
+ }
534
+
535
+ static getPaddingLeft(element: HTMLElement): number {
536
+ return SizeUtils.getDimension(element, 'padding-left', 'paddingLeft');
537
+ }
538
+ static getPaddingRight(element: HTMLElement): number {
539
+ return SizeUtils.getDimension(element, 'padding-right', 'paddingRight');
540
+ }
541
+ static getPaddingTop(element: HTMLElement): number {
542
+ return SizeUtils.getDimension(element, 'padding-top', 'paddingTop');
543
+ }
544
+ static getPaddingBottom(element: HTMLElement): number {
545
+ return SizeUtils.getDimension(element, 'padding-bottom', 'paddingBottom');
546
+ }
547
+
548
+ static getMarginLeft(element: HTMLElement): number {
549
+ return SizeUtils.getDimension(element, 'margin-left', 'marginLeft');
550
+ }
551
+ static getMarginTop(element: HTMLElement): number {
552
+ return SizeUtils.getDimension(element, 'margin-top', 'marginTop');
553
+ }
554
+ static getMarginRight(element: HTMLElement): number {
555
+ return SizeUtils.getDimension(element, 'margin-right', 'marginRight');
556
+ }
557
+ static getMarginBottom(element: HTMLElement): number {
558
+ return SizeUtils.getDimension(element, 'margin-bottom', 'marginBottom');
559
+ }
560
+ }
561
+
562
+ // ----------------------------------------------------------------------------------------
563
+ // Position & Dimension
564
+
565
+ export interface IDimension {
566
+ readonly width: number;
567
+ readonly height: number;
568
+ }
569
+
570
+ export class Dimension implements IDimension {
571
+
572
+ static readonly None = new Dimension(0, 0);
573
+
574
+ constructor(
575
+ readonly width: number,
576
+ readonly height: number,
577
+ ) { }
578
+
579
+ with(width: number = this.width, height: number = this.height): Dimension {
580
+ if (width !== this.width || height !== this.height) {
581
+ return new Dimension(width, height);
582
+ } else {
583
+ return this;
584
+ }
585
+ }
586
+
587
+ static is(obj: unknown): obj is IDimension {
588
+ return typeof obj === 'object' && typeof (<IDimension>obj).height === 'number' && typeof (<IDimension>obj).width === 'number';
589
+ }
590
+
591
+ static lift(obj: IDimension): Dimension {
592
+ if (obj instanceof Dimension) {
593
+ return obj;
594
+ } else {
595
+ return new Dimension(obj.width, obj.height);
596
+ }
597
+ }
598
+
599
+ static equals(a: Dimension | undefined, b: Dimension | undefined): boolean {
600
+ if (a === b) {
601
+ return true;
602
+ }
603
+ if (!a || !b) {
604
+ return false;
605
+ }
606
+ return a.width === b.width && a.height === b.height;
607
+ }
608
+ }
609
+
610
+ export interface IDomPosition {
611
+ readonly left: number;
612
+ readonly top: number;
613
+ }
614
+
615
+ export function getTopLeftOffset(element: HTMLElement): IDomPosition {
616
+ // Adapted from WinJS.Utilities.getPosition
617
+ // and added borders to the mix
618
+
619
+ let offsetParent = element.offsetParent;
620
+ let top = element.offsetTop;
621
+ let left = element.offsetLeft;
622
+
623
+ while (
624
+ (element = <HTMLElement>element.parentNode) !== null
625
+ && element !== element.ownerDocument.body
626
+ && element !== element.ownerDocument.documentElement
627
+ ) {
628
+ top -= element.scrollTop;
629
+ const c = isShadowRoot(element) ? null : getComputedStyle(element);
630
+ if (c) {
631
+ left -= c.direction !== 'rtl' ? element.scrollLeft : -element.scrollLeft;
632
+ }
633
+
634
+ if (element === offsetParent) {
635
+ left += SizeUtils.getBorderLeftWidth(element);
636
+ top += SizeUtils.getBorderTopWidth(element);
637
+ top += element.offsetTop;
638
+ left += element.offsetLeft;
639
+ offsetParent = element.offsetParent;
640
+ }
641
+ }
642
+
643
+ return {
644
+ left: left,
645
+ top: top
646
+ };
647
+ }
648
+
649
+ export interface IDomNodePagePosition {
650
+ left: number;
651
+ top: number;
652
+ width: number;
653
+ height: number;
654
+ }
655
+
656
+ export function size(element: HTMLElement, width: number | null, height: number | null): void {
657
+ if (typeof width === 'number') {
658
+ element.style.width = `${width}px`;
659
+ }
660
+
661
+ if (typeof height === 'number') {
662
+ element.style.height = `${height}px`;
663
+ }
664
+ }
665
+
666
+ export function position(element: HTMLElement, top: number, right?: number, bottom?: number, left?: number, position: string = 'absolute'): void {
667
+ if (typeof top === 'number') {
668
+ element.style.top = `${top}px`;
669
+ }
670
+
671
+ if (typeof right === 'number') {
672
+ element.style.right = `${right}px`;
673
+ }
674
+
675
+ if (typeof bottom === 'number') {
676
+ element.style.bottom = `${bottom}px`;
677
+ }
678
+
679
+ if (typeof left === 'number') {
680
+ element.style.left = `${left}px`;
681
+ }
682
+
683
+ element.style.position = position;
684
+ }
685
+
686
+ /**
687
+ * Returns the position of a dom node relative to the entire page.
688
+ */
689
+ export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePosition {
690
+ const bb = domNode.getBoundingClientRect();
691
+ const window = getWindow(domNode);
692
+ return {
693
+ left: bb.left + window.scrollX,
694
+ top: bb.top + window.scrollY,
695
+ width: bb.width,
696
+ height: bb.height
697
+ };
698
+ }
699
+
700
+ /**
701
+ * Returns the effective zoom on a given element before window zoom level is applied
702
+ */
703
+ export function getDomNodeZoomLevel(domNode: HTMLElement): number {
704
+ let testElement: HTMLElement | null = domNode;
705
+ let zoom = 1.0;
706
+ do {
707
+ const elementZoomLevel = (getComputedStyle(testElement) as any).zoom;
708
+ if (elementZoomLevel !== null && elementZoomLevel !== undefined && elementZoomLevel !== '1') {
709
+ zoom *= elementZoomLevel;
710
+ }
711
+
712
+ testElement = testElement.parentElement;
713
+ } while (testElement !== null && testElement !== testElement.ownerDocument.documentElement);
714
+
715
+ return zoom;
716
+ }
717
+
718
+
719
+ // Adapted from WinJS
720
+ // Gets the width of the element, including margins.
721
+ export function getTotalWidth(element: HTMLElement): number {
722
+ const margin = SizeUtils.getMarginLeft(element) + SizeUtils.getMarginRight(element);
723
+ return element.offsetWidth + margin;
724
+ }
725
+
726
+ export function getContentWidth(element: HTMLElement): number {
727
+ const border = SizeUtils.getBorderLeftWidth(element) + SizeUtils.getBorderRightWidth(element);
728
+ const padding = SizeUtils.getPaddingLeft(element) + SizeUtils.getPaddingRight(element);
729
+ return element.offsetWidth - border - padding;
730
+ }
731
+
732
+ export function getTotalScrollWidth(element: HTMLElement): number {
733
+ const margin = SizeUtils.getMarginLeft(element) + SizeUtils.getMarginRight(element);
734
+ return element.scrollWidth + margin;
735
+ }
736
+
737
+ // Adapted from WinJS
738
+ // Gets the height of the content of the specified element. The content height does not include borders or padding.
739
+ export function getContentHeight(element: HTMLElement): number {
740
+ const border = SizeUtils.getBorderTopWidth(element) + SizeUtils.getBorderBottomWidth(element);
741
+ const padding = SizeUtils.getPaddingTop(element) + SizeUtils.getPaddingBottom(element);
742
+ return element.offsetHeight - border - padding;
743
+ }
744
+
745
+ // Adapted from WinJS
746
+ // Gets the height of the element, including its margins.
747
+ export function getTotalHeight(element: HTMLElement): number {
748
+ const margin = SizeUtils.getMarginTop(element) + SizeUtils.getMarginBottom(element);
749
+ return element.offsetHeight + margin;
750
+ }
751
+
752
+ // Gets the left coordinate of the specified element relative to the specified parent.
753
+ function getRelativeLeft(element: HTMLElement, parent: HTMLElement): number {
754
+ if (element === null) {
755
+ return 0;
756
+ }
757
+
758
+ const elementPosition = getTopLeftOffset(element);
759
+ const parentPosition = getTopLeftOffset(parent);
760
+ return elementPosition.left - parentPosition.left;
761
+ }
762
+
763
+ export function getLargestChildWidth(parent: HTMLElement, children: HTMLElement[]): number {
764
+ const childWidths = children.map((child) => {
765
+ return Math.max(getTotalScrollWidth(child), getTotalWidth(child)) + getRelativeLeft(child, parent) || 0;
766
+ });
767
+ const maxWidth = Math.max(...childWidths);
768
+ return maxWidth;
769
+ }
770
+
771
+ // ----------------------------------------------------------------------------------------
772
+
773
+ export function isAncestor(testChild: Node | null, testAncestor: Node | null): boolean {
774
+ return Boolean(testAncestor?.contains(testChild));
775
+ }
776
+
777
+ const parentFlowToDataKey = 'parentFlowToElementId';
778
+
779
+ /**
780
+ * Set an explicit parent to use for nodes that are not part of the
781
+ * regular dom structure.
782
+ */
783
+ export function setParentFlowTo(fromChildElement: HTMLElement, toParentElement: Element): void {
784
+ fromChildElement.dataset[parentFlowToDataKey] = toParentElement.id;
785
+ }
786
+
787
+ function getParentFlowToElement(node: HTMLElement): HTMLElement | null {
788
+ const flowToParentId = node.dataset[parentFlowToDataKey];
789
+ if (typeof flowToParentId === 'string') {
790
+ return node.ownerDocument.getElementById(flowToParentId);
791
+ }
792
+ return null;
793
+ }
794
+
795
+ /**
796
+ * Check if `testAncestor` is an ancestor of `testChild`, observing the explicit
797
+ * parents set by `setParentFlowTo`.
798
+ */
799
+ export function isAncestorUsingFlowTo(testChild: Node, testAncestor: Node): boolean {
800
+ let node: Node | null = testChild;
801
+ while (node) {
802
+ if (node === testAncestor) {
803
+ return true;
804
+ }
805
+
806
+ if (isHTMLElement(node)) {
807
+ const flowToParentElement = getParentFlowToElement(node);
808
+ if (flowToParentElement) {
809
+ node = flowToParentElement;
810
+ continue;
811
+ }
812
+ }
813
+ node = node.parentNode;
814
+ }
815
+
816
+ return false;
817
+ }
818
+
819
+ export function findParentWithClass(node: HTMLElement, clazz: string, stopAtClazzOrNode?: string | HTMLElement): HTMLElement | null {
820
+ while (node && node.nodeType === node.ELEMENT_NODE) {
821
+ if (node.classList.contains(clazz)) {
822
+ return node;
823
+ }
824
+
825
+ if (stopAtClazzOrNode) {
826
+ if (typeof stopAtClazzOrNode === 'string') {
827
+ if (node.classList.contains(stopAtClazzOrNode)) {
828
+ return null;
829
+ }
830
+ } else {
831
+ if (node === stopAtClazzOrNode) {
832
+ return null;
833
+ }
834
+ }
835
+ }
836
+
837
+ node = <HTMLElement>node.parentNode;
838
+ }
839
+
840
+ return null;
841
+ }
842
+
843
+ export function hasParentWithClass(node: HTMLElement, clazz: string, stopAtClazzOrNode?: string | HTMLElement): boolean {
844
+ return !!findParentWithClass(node, clazz, stopAtClazzOrNode);
845
+ }
846
+
847
+ export function isShadowRoot(node: Node): node is ShadowRoot {
848
+ return (
849
+ node && !!(<ShadowRoot>node).host && !!(<ShadowRoot>node).mode
850
+ );
851
+ }
852
+
853
+ export function isInShadowDOM(domNode: Node): boolean {
854
+ return !!getShadowRoot(domNode);
855
+ }
856
+
857
+ export function getShadowRoot(domNode: Node): ShadowRoot | null {
858
+ while (domNode.parentNode) {
859
+ if (domNode === domNode.ownerDocument?.body) {
860
+ // reached the body
861
+ return null;
862
+ }
863
+ domNode = domNode.parentNode;
864
+ }
865
+ return isShadowRoot(domNode) ? domNode : null;
866
+ }
867
+
868
+ /**
869
+ * Returns the active element across all child windows
870
+ * based on document focus. Falls back to the main
871
+ * window if no window has focus.
872
+ */
873
+ export function getActiveElement(): Element | null {
874
+ let result = getActiveDocument().activeElement;
875
+
876
+ while (result?.shadowRoot) {
877
+ result = result.shadowRoot.activeElement;
878
+ }
879
+
880
+ return result;
881
+ }
882
+
883
+ /**
884
+ * Returns true if the focused window active element matches
885
+ * the provided element. Falls back to the main window if no
886
+ * window has focus.
887
+ */
888
+ export function isActiveElement(element: Element): boolean {
889
+ return getActiveElement() === element;
890
+ }
891
+
892
+ /**
893
+ * Returns true if the focused window active element is contained in
894
+ * `ancestor`. Falls back to the main window if no window has focus.
895
+ */
896
+ export function isAncestorOfActiveElement(ancestor: Element): boolean {
897
+ return isAncestor(getActiveElement(), ancestor);
898
+ }
899
+
900
+ /**
901
+ * Returns whether the element is in the active `document`. The active
902
+ * document has focus or will be the main windows document.
903
+ */
904
+ export function isActiveDocument(element: Element): boolean {
905
+ return element.ownerDocument === getActiveDocument();
906
+ }
907
+
908
+ /**
909
+ * Returns the active document across main and child windows.
910
+ * Prefers the window with focus, otherwise falls back to
911
+ * the main windows document.
912
+ */
913
+ export function getActiveDocument(): Document {
914
+ if (getWindowsCount() <= 1) {
915
+ return mainWindow.document;
916
+ }
917
+
918
+ const documents = Array.from(getWindows()).map(({ window }) => window.document);
919
+ return documents.find(doc => doc.hasFocus()) ?? mainWindow.document;
920
+ }
921
+
922
+ /**
923
+ * Returns the active window across main and child windows.
924
+ * Prefers the window with focus, otherwise falls back to
925
+ * the main window.
926
+ */
927
+ export function getActiveWindow(): CodeWindow {
928
+ const document = getActiveDocument();
929
+ return (document.defaultView?.window ?? mainWindow) as CodeWindow;
930
+ }
931
+
932
+ const globalStylesheets = new Map<HTMLStyleElement /* main stylesheet */, Set<HTMLStyleElement /* aux window clones that track the main stylesheet */>>();
933
+
934
+ export function isGlobalStylesheet(node: Node): boolean {
935
+ return globalStylesheets.has(node as HTMLStyleElement);
936
+ }
937
+
938
+ /**
939
+ * A version of createStyleSheet which has a unified API to initialize/set the style content.
940
+ */
941
+ export function createStyleSheet2(): WrappedStyleElement {
942
+ return new WrappedStyleElement();
943
+ }
944
+
945
+ class WrappedStyleElement {
946
+ private _currentCssStyle = '';
947
+ private _styleSheet: HTMLStyleElement | undefined = undefined;
948
+
949
+ public setStyle(cssStyle: string): void {
950
+ if (cssStyle === this._currentCssStyle) {
951
+ return;
952
+ }
953
+ this._currentCssStyle = cssStyle;
954
+
955
+ if (!this._styleSheet) {
956
+ this._styleSheet = createStyleSheet(mainWindow.document.head, (s) => s.innerText = cssStyle);
957
+ } else {
958
+ this._styleSheet.innerText = cssStyle;
959
+ }
960
+ }
961
+
962
+ public dispose(): void {
963
+ if (this._styleSheet) {
964
+ this._styleSheet.remove();
965
+ this._styleSheet = undefined;
966
+ }
967
+ }
968
+ }
969
+
970
+ export function createStyleSheet(container: HTMLElement = mainWindow.document.head, beforeAppend?: (style: HTMLStyleElement) => void, disposableStore?: DisposableStore): HTMLStyleElement {
971
+ const style = document.createElement('style');
972
+ style.type = 'text/css';
973
+ style.media = 'screen';
974
+ beforeAppend?.(style);
975
+ container.appendChild(style);
976
+
977
+ if (disposableStore) {
978
+ disposableStore.add(toDisposable(() => style.remove()));
979
+ }
980
+
981
+ // With <head> as container, the stylesheet becomes global and is tracked
982
+ // to support auxiliary windows to clone the stylesheet.
983
+ if (container === mainWindow.document.head) {
984
+ const globalStylesheetClones = new Set<HTMLStyleElement>();
985
+ globalStylesheets.set(style, globalStylesheetClones);
986
+
987
+ for (const { window: targetWindow, disposables } of getWindows()) {
988
+ if (targetWindow === mainWindow) {
989
+ continue; // main window is already tracked
990
+ }
991
+
992
+ const cloneDisposable = disposables.add(cloneGlobalStyleSheet(style, globalStylesheetClones, targetWindow));
993
+ disposableStore?.add(cloneDisposable);
994
+ }
995
+ }
996
+
997
+ return style;
998
+ }
999
+
1000
+ export function cloneGlobalStylesheets(targetWindow: Window): IDisposable {
1001
+ const disposables = new DisposableStore();
1002
+
1003
+ for (const [globalStylesheet, clonedGlobalStylesheets] of globalStylesheets) {
1004
+ disposables.add(cloneGlobalStyleSheet(globalStylesheet, clonedGlobalStylesheets, targetWindow));
1005
+ }
1006
+
1007
+ return disposables;
1008
+ }
1009
+
1010
+ function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, globalStylesheetClones: Set<HTMLStyleElement>, targetWindow: Window): IDisposable {
1011
+ const disposables = new DisposableStore();
1012
+
1013
+ const clone = globalStylesheet.cloneNode(true) as HTMLStyleElement;
1014
+ targetWindow.document.head.appendChild(clone);
1015
+ disposables.add(toDisposable(() => clone.remove()));
1016
+
1017
+ for (const rule of getDynamicStyleSheetRules(globalStylesheet)) {
1018
+ clone.sheet?.insertRule(rule.cssText, clone.sheet?.cssRules.length);
1019
+ }
1020
+
1021
+ disposables.add(sharedMutationObserver.observe(globalStylesheet, disposables, { childList: true })(() => {
1022
+ clone.textContent = globalStylesheet.textContent;
1023
+ }));
1024
+
1025
+ globalStylesheetClones.add(clone);
1026
+ disposables.add(toDisposable(() => globalStylesheetClones.delete(clone)));
1027
+
1028
+ return disposables;
1029
+ }
1030
+
1031
+ interface IMutationObserver {
1032
+ users: number;
1033
+ readonly observer: MutationObserver;
1034
+ readonly onDidMutate: event.Event<MutationRecord[]>;
1035
+ }
1036
+
1037
+ export const sharedMutationObserver = new class {
1038
+
1039
+ readonly mutationObservers = new Map<Node, Map<number, IMutationObserver>>();
1040
+
1041
+ observe(target: Node, disposables: DisposableStore, options?: MutationObserverInit): event.Event<MutationRecord[]> {
1042
+ let mutationObserversPerTarget = this.mutationObservers.get(target);
1043
+ if (!mutationObserversPerTarget) {
1044
+ mutationObserversPerTarget = new Map<number, IMutationObserver>();
1045
+ this.mutationObservers.set(target, mutationObserversPerTarget);
1046
+ }
1047
+
1048
+ const optionsHash = hash(options);
1049
+ let mutationObserverPerOptions = mutationObserversPerTarget.get(optionsHash);
1050
+ if (!mutationObserverPerOptions) {
1051
+ const onDidMutate = new event.Emitter<MutationRecord[]>();
1052
+ const observer = new MutationObserver(mutations => onDidMutate.fire(mutations));
1053
+ observer.observe(target, options);
1054
+
1055
+ const resolvedMutationObserverPerOptions = mutationObserverPerOptions = {
1056
+ users: 1,
1057
+ observer,
1058
+ onDidMutate: onDidMutate.event
1059
+ };
1060
+
1061
+ disposables.add(toDisposable(() => {
1062
+ resolvedMutationObserverPerOptions.users -= 1;
1063
+
1064
+ if (resolvedMutationObserverPerOptions.users === 0) {
1065
+ onDidMutate.dispose();
1066
+ observer.disconnect();
1067
+
1068
+ mutationObserversPerTarget?.delete(optionsHash);
1069
+ if (mutationObserversPerTarget?.size === 0) {
1070
+ this.mutationObservers.delete(target);
1071
+ }
1072
+ }
1073
+ }));
1074
+
1075
+ mutationObserversPerTarget.set(optionsHash, mutationObserverPerOptions);
1076
+ } else {
1077
+ mutationObserverPerOptions.users += 1;
1078
+ }
1079
+
1080
+ return mutationObserverPerOptions.onDidMutate;
1081
+ }
1082
+ };
1083
+
1084
+ export function createMetaElement(container: HTMLElement = mainWindow.document.head): HTMLMetaElement {
1085
+ return createHeadElement('meta', container) as HTMLMetaElement;
1086
+ }
1087
+
1088
+ export function createLinkElement(container: HTMLElement = mainWindow.document.head): HTMLLinkElement {
1089
+ return createHeadElement('link', container) as HTMLLinkElement;
1090
+ }
1091
+
1092
+ function createHeadElement(tagName: string, container: HTMLElement = mainWindow.document.head): HTMLElement {
1093
+ const element = document.createElement(tagName);
1094
+ container.appendChild(element);
1095
+ return element;
1096
+ }
1097
+
1098
+ let _sharedStyleSheet: HTMLStyleElement | null = null;
1099
+ function getSharedStyleSheet(): HTMLStyleElement {
1100
+ if (!_sharedStyleSheet) {
1101
+ _sharedStyleSheet = createStyleSheet();
1102
+ }
1103
+ return _sharedStyleSheet;
1104
+ }
1105
+
1106
+ function getDynamicStyleSheetRules(style: HTMLStyleElement) {
1107
+ if (style?.sheet?.rules) {
1108
+ // Chrome, IE
1109
+ return style.sheet.rules;
1110
+ }
1111
+ if (style?.sheet?.cssRules) {
1112
+ // FF
1113
+ return style.sheet.cssRules;
1114
+ }
1115
+ return [];
1116
+ }
1117
+
1118
+ export function createCSSRule(selector: string, cssText: string, style = getSharedStyleSheet()): void {
1119
+ if (!style || !cssText) {
1120
+ return;
1121
+ }
1122
+
1123
+ style.sheet?.insertRule(`${selector} {${cssText}}`, 0);
1124
+
1125
+ // Apply rule also to all cloned global stylesheets
1126
+ for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) {
1127
+ createCSSRule(selector, cssText, clonedGlobalStylesheet);
1128
+ }
1129
+ }
1130
+
1131
+ export function removeCSSRulesContainingSelector(ruleName: string, style = getSharedStyleSheet()): void {
1132
+ if (!style) {
1133
+ return;
1134
+ }
1135
+
1136
+ const rules = getDynamicStyleSheetRules(style);
1137
+ const toDelete: number[] = [];
1138
+ for (let i = 0; i < rules.length; i++) {
1139
+ const rule = rules[i];
1140
+ if (isCSSStyleRule(rule) && rule.selectorText.indexOf(ruleName) !== -1) {
1141
+ toDelete.push(i);
1142
+ }
1143
+ }
1144
+
1145
+ for (let i = toDelete.length - 1; i >= 0; i--) {
1146
+ style.sheet?.deleteRule(toDelete[i]);
1147
+ }
1148
+
1149
+ // Remove rules also from all cloned global stylesheets
1150
+ for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) {
1151
+ removeCSSRulesContainingSelector(ruleName, clonedGlobalStylesheet);
1152
+ }
1153
+ }
1154
+
1155
+ function isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule {
1156
+ return typeof (rule as CSSStyleRule).selectorText === 'string';
1157
+ }
1158
+
1159
+ export function isHTMLElement(e: unknown): e is HTMLElement {
1160
+ // eslint-disable-next-line no-restricted-syntax
1161
+ return e instanceof HTMLElement || e instanceof getWindow(e as Node).HTMLElement;
1162
+ }
1163
+
1164
+ export function isHTMLAnchorElement(e: unknown): e is HTMLAnchorElement {
1165
+ // eslint-disable-next-line no-restricted-syntax
1166
+ return e instanceof HTMLAnchorElement || e instanceof getWindow(e as Node).HTMLAnchorElement;
1167
+ }
1168
+
1169
+ export function isHTMLSpanElement(e: unknown): e is HTMLSpanElement {
1170
+ // eslint-disable-next-line no-restricted-syntax
1171
+ return e instanceof HTMLSpanElement || e instanceof getWindow(e as Node).HTMLSpanElement;
1172
+ }
1173
+
1174
+ export function isHTMLTextAreaElement(e: unknown): e is HTMLTextAreaElement {
1175
+ // eslint-disable-next-line no-restricted-syntax
1176
+ return e instanceof HTMLTextAreaElement || e instanceof getWindow(e as Node).HTMLTextAreaElement;
1177
+ }
1178
+
1179
+ export function isHTMLInputElement(e: unknown): e is HTMLInputElement {
1180
+ // eslint-disable-next-line no-restricted-syntax
1181
+ return e instanceof HTMLInputElement || e instanceof getWindow(e as Node).HTMLInputElement;
1182
+ }
1183
+
1184
+ export function isHTMLButtonElement(e: unknown): e is HTMLButtonElement {
1185
+ // eslint-disable-next-line no-restricted-syntax
1186
+ return e instanceof HTMLButtonElement || e instanceof getWindow(e as Node).HTMLButtonElement;
1187
+ }
1188
+
1189
+ export function isHTMLDivElement(e: unknown): e is HTMLDivElement {
1190
+ // eslint-disable-next-line no-restricted-syntax
1191
+ return e instanceof HTMLDivElement || e instanceof getWindow(e as Node).HTMLDivElement;
1192
+ }
1193
+
1194
+ export function isSVGElement(e: unknown): e is SVGElement {
1195
+ // eslint-disable-next-line no-restricted-syntax
1196
+ return e instanceof SVGElement || e instanceof getWindow(e as Node).SVGElement;
1197
+ }
1198
+
1199
+ export function isMouseEvent(e: unknown): e is MouseEvent {
1200
+ // eslint-disable-next-line no-restricted-syntax
1201
+ return e instanceof MouseEvent || e instanceof getWindow(e as UIEvent).MouseEvent;
1202
+ }
1203
+
1204
+ export function isKeyboardEvent(e: unknown): e is KeyboardEvent {
1205
+ // eslint-disable-next-line no-restricted-syntax
1206
+ return e instanceof KeyboardEvent || e instanceof getWindow(e as UIEvent).KeyboardEvent;
1207
+ }
1208
+
1209
+ export function isPointerEvent(e: unknown): e is PointerEvent {
1210
+ // eslint-disable-next-line no-restricted-syntax
1211
+ return e instanceof PointerEvent || e instanceof getWindow(e as UIEvent).PointerEvent;
1212
+ }
1213
+
1214
+ export function isDragEvent(e: unknown): e is DragEvent {
1215
+ // eslint-disable-next-line no-restricted-syntax
1216
+ return e instanceof DragEvent || e instanceof getWindow(e as UIEvent).DragEvent;
1217
+ }
1218
+
1219
+ export const EventType = {
1220
+ // Mouse
1221
+ CLICK: 'click',
1222
+ AUXCLICK: 'auxclick',
1223
+ DBLCLICK: 'dblclick',
1224
+ MOUSE_UP: 'mouseup',
1225
+ MOUSE_DOWN: 'mousedown',
1226
+ MOUSE_OVER: 'mouseover',
1227
+ MOUSE_MOVE: 'mousemove',
1228
+ MOUSE_OUT: 'mouseout',
1229
+ MOUSE_ENTER: 'mouseenter',
1230
+ MOUSE_LEAVE: 'mouseleave',
1231
+ MOUSE_WHEEL: 'wheel',
1232
+ POINTER_UP: 'pointerup',
1233
+ POINTER_DOWN: 'pointerdown',
1234
+ POINTER_MOVE: 'pointermove',
1235
+ POINTER_LEAVE: 'pointerleave',
1236
+ CONTEXT_MENU: 'contextmenu',
1237
+ WHEEL: 'wheel',
1238
+ // Keyboard
1239
+ KEY_DOWN: 'keydown',
1240
+ KEY_PRESS: 'keypress',
1241
+ KEY_UP: 'keyup',
1242
+ // HTML Document
1243
+ LOAD: 'load',
1244
+ BEFORE_UNLOAD: 'beforeunload',
1245
+ UNLOAD: 'unload',
1246
+ PAGE_SHOW: 'pageshow',
1247
+ PAGE_HIDE: 'pagehide',
1248
+ PASTE: 'paste',
1249
+ ABORT: 'abort',
1250
+ ERROR: 'error',
1251
+ RESIZE: 'resize',
1252
+ SCROLL: 'scroll',
1253
+ FULLSCREEN_CHANGE: 'fullscreenchange',
1254
+ WK_FULLSCREEN_CHANGE: 'webkitfullscreenchange',
1255
+ // Form
1256
+ SELECT: 'select',
1257
+ CHANGE: 'change',
1258
+ SUBMIT: 'submit',
1259
+ RESET: 'reset',
1260
+ FOCUS: 'focus',
1261
+ FOCUS_IN: 'focusin',
1262
+ FOCUS_OUT: 'focusout',
1263
+ BLUR: 'blur',
1264
+ INPUT: 'input',
1265
+ // Local Storage
1266
+ STORAGE: 'storage',
1267
+ // Drag
1268
+ DRAG_START: 'dragstart',
1269
+ DRAG: 'drag',
1270
+ DRAG_ENTER: 'dragenter',
1271
+ DRAG_LEAVE: 'dragleave',
1272
+ DRAG_OVER: 'dragover',
1273
+ DROP: 'drop',
1274
+ DRAG_END: 'dragend',
1275
+ // Animation
1276
+ ANIMATION_START: browser.isWebKit ? 'webkitAnimationStart' : 'animationstart',
1277
+ ANIMATION_END: browser.isWebKit ? 'webkitAnimationEnd' : 'animationend',
1278
+ ANIMATION_ITERATION: browser.isWebKit ? 'webkitAnimationIteration' : 'animationiteration'
1279
+ } as const;
1280
+
1281
+ export interface EventLike {
1282
+ preventDefault(): void;
1283
+ stopPropagation(): void;
1284
+ }
1285
+
1286
+ export function isEventLike(obj: unknown): obj is EventLike {
1287
+ const candidate = obj as EventLike | undefined;
1288
+
1289
+ return !!(candidate && typeof candidate.preventDefault === 'function' && typeof candidate.stopPropagation === 'function');
1290
+ }
1291
+
1292
+ export const EventHelper = {
1293
+ stop: <T extends EventLike>(e: T, cancelBubble?: boolean): T => {
1294
+ e.preventDefault();
1295
+ if (cancelBubble) {
1296
+ e.stopPropagation();
1297
+ }
1298
+ return e;
1299
+ }
1300
+ };
1301
+
1302
+ export interface IFocusTracker extends Disposable {
1303
+ readonly onDidFocus: event.Event<void>;
1304
+ readonly onDidBlur: event.Event<void>;
1305
+ refreshState(): void;
1306
+ }
1307
+
1308
+ export function saveParentsScrollTop(node: Element): number[] {
1309
+ const r: number[] = [];
1310
+ for (let i = 0; node && node.nodeType === node.ELEMENT_NODE; i++) {
1311
+ r[i] = node.scrollTop;
1312
+ node = <Element>node.parentNode;
1313
+ }
1314
+ return r;
1315
+ }
1316
+
1317
+ export function restoreParentsScrollTop(node: Element, state: number[]): void {
1318
+ for (let i = 0; node && node.nodeType === node.ELEMENT_NODE; i++) {
1319
+ if (node.scrollTop !== state[i]) {
1320
+ node.scrollTop = state[i];
1321
+ }
1322
+ node = <Element>node.parentNode;
1323
+ }
1324
+ }
1325
+
1326
+ class FocusTracker extends Disposable implements IFocusTracker {
1327
+
1328
+ private readonly _onDidFocus = this._register(new event.Emitter<void>());
1329
+ readonly onDidFocus = this._onDidFocus.event;
1330
+
1331
+ private readonly _onDidBlur = this._register(new event.Emitter<void>());
1332
+ readonly onDidBlur = this._onDidBlur.event;
1333
+
1334
+ private _refreshStateHandler: () => void;
1335
+
1336
+ private static hasFocusWithin(element: HTMLElement | Window): boolean {
1337
+ if (isHTMLElement(element)) {
1338
+ const shadowRoot = getShadowRoot(element);
1339
+ const activeElement = (shadowRoot ? shadowRoot.activeElement : element.ownerDocument.activeElement);
1340
+ return isAncestor(activeElement, element);
1341
+ } else {
1342
+ const window = element;
1343
+ return isAncestor(window.document.activeElement, window.document);
1344
+ }
1345
+ }
1346
+
1347
+ constructor(element: HTMLElement | Window) {
1348
+ super();
1349
+ let hasFocus = FocusTracker.hasFocusWithin(element);
1350
+ let loosingFocus = false;
1351
+
1352
+ const onFocus = () => {
1353
+ loosingFocus = false;
1354
+ if (!hasFocus) {
1355
+ hasFocus = true;
1356
+ this._onDidFocus.fire();
1357
+ }
1358
+ };
1359
+
1360
+ const onBlur = () => {
1361
+ if (hasFocus) {
1362
+ loosingFocus = true;
1363
+ (isHTMLElement(element) ? getWindow(element) : element).setTimeout(() => {
1364
+ if (loosingFocus) {
1365
+ loosingFocus = false;
1366
+ hasFocus = false;
1367
+ this._onDidBlur.fire();
1368
+ }
1369
+ }, 0);
1370
+ }
1371
+ };
1372
+
1373
+ this._refreshStateHandler = () => {
1374
+ const currentNodeHasFocus = FocusTracker.hasFocusWithin(<HTMLElement>element);
1375
+ if (currentNodeHasFocus !== hasFocus) {
1376
+ if (hasFocus) {
1377
+ onBlur();
1378
+ } else {
1379
+ onFocus();
1380
+ }
1381
+ }
1382
+ };
1383
+
1384
+ this._register(addDisposableListener(element, EventType.FOCUS, onFocus, true));
1385
+ this._register(addDisposableListener(element, EventType.BLUR, onBlur, true));
1386
+ if (isHTMLElement(element)) {
1387
+ this._register(addDisposableListener(element, EventType.FOCUS_IN, () => this._refreshStateHandler()));
1388
+ this._register(addDisposableListener(element, EventType.FOCUS_OUT, () => this._refreshStateHandler()));
1389
+ }
1390
+
1391
+ }
1392
+
1393
+ refreshState() {
1394
+ this._refreshStateHandler();
1395
+ }
1396
+ }
1397
+
1398
+ /**
1399
+ * Creates a new `IFocusTracker` instance that tracks focus changes on the given `element` and its descendants.
1400
+ *
1401
+ * @param element The `HTMLElement` or `Window` to track focus changes on.
1402
+ * @returns An `IFocusTracker` instance.
1403
+ */
1404
+ export function trackFocus(element: HTMLElement | Window): IFocusTracker {
1405
+ return new FocusTracker(element);
1406
+ }
1407
+
1408
+ export function after<T extends Node>(sibling: HTMLElement, child: T): T {
1409
+ sibling.after(child);
1410
+ return child;
1411
+ }
1412
+
1413
+ export function append<T extends Node>(parent: HTMLElement, child: T): T;
1414
+ export function append<T extends Node>(parent: HTMLElement, ...children: (T | string)[]): void;
1415
+ export function append<T extends Node>(parent: HTMLElement, ...children: (T | string)[]): T | void {
1416
+ parent.append(...children);
1417
+ if (children.length === 1 && typeof children[0] !== 'string') {
1418
+ return <T>children[0];
1419
+ }
1420
+ }
1421
+
1422
+ export function prepend<T extends Node>(parent: HTMLElement, child: T): T {
1423
+ parent.insertBefore(child, parent.firstChild);
1424
+ return child;
1425
+ }
1426
+
1427
+ /**
1428
+ * Removes all children from `parent` and appends `children`
1429
+ */
1430
+ export function reset(parent: HTMLElement, ...children: Array<Node | string>): void {
1431
+ parent.innerText = '';
1432
+ append(parent, ...children);
1433
+ }
1434
+
1435
+ const SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/;
1436
+
1437
+ export enum Namespace {
1438
+ HTML = 'http://www.w3.org/1999/xhtml',
1439
+ SVG = 'http://www.w3.org/2000/svg'
1440
+ }
1441
+
1442
+ function _$<T extends Element>(namespace: Namespace, description: string, attrs?: { [key: string]: any }, ...children: Array<Node | string>): T {
1443
+ const match = SELECTOR_REGEX.exec(description);
1444
+
1445
+ if (!match) {
1446
+ throw new Error('Bad use of emmet');
1447
+ }
1448
+
1449
+ const tagName = match[1] || 'div';
1450
+ let result: T;
1451
+
1452
+ if (namespace !== Namespace.HTML) {
1453
+ result = document.createElementNS(namespace as string, tagName) as T;
1454
+ } else {
1455
+ result = document.createElement(tagName) as unknown as T;
1456
+ }
1457
+
1458
+ if (match[3]) {
1459
+ result.id = match[3];
1460
+ }
1461
+ if (match[4]) {
1462
+ result.className = match[4].replace(/\./g, ' ').trim();
1463
+ }
1464
+
1465
+ if (attrs) {
1466
+ Object.entries(attrs).forEach(([name, value]) => {
1467
+ if (typeof value === 'undefined') {
1468
+ return;
1469
+ }
1470
+
1471
+ if (/^on\w+$/.test(name)) {
1472
+ (<any>result)[name] = value;
1473
+ } else if (name === 'selected') {
1474
+ if (value) {
1475
+ result.setAttribute(name, 'true');
1476
+ }
1477
+
1478
+ } else {
1479
+ result.setAttribute(name, value);
1480
+ }
1481
+ });
1482
+ }
1483
+
1484
+ result.append(...children);
1485
+
1486
+ return result as T;
1487
+ }
1488
+
1489
+ export function $<T extends HTMLElement>(description: string, attrs?: { [key: string]: any }, ...children: Array<Node | string>): T {
1490
+ return _$(Namespace.HTML, description, attrs, ...children);
1491
+ }
1492
+
1493
+ $.SVG = function <T extends SVGElement>(description: string, attrs?: { [key: string]: any }, ...children: Array<Node | string>): T {
1494
+ return _$(Namespace.SVG, description, attrs, ...children);
1495
+ };
1496
+
1497
+ export function join(nodes: Node[], separator: Node | string): Node[] {
1498
+ const result: Node[] = [];
1499
+
1500
+ nodes.forEach((node, index) => {
1501
+ if (index > 0) {
1502
+ if (separator instanceof Node) {
1503
+ result.push(separator.cloneNode());
1504
+ } else {
1505
+ result.push(document.createTextNode(separator));
1506
+ }
1507
+ }
1508
+
1509
+ result.push(node);
1510
+ });
1511
+
1512
+ return result;
1513
+ }
1514
+
1515
+ export function setVisibility(visible: boolean, ...elements: HTMLElement[]): void {
1516
+ if (visible) {
1517
+ show(...elements);
1518
+ } else {
1519
+ hide(...elements);
1520
+ }
1521
+ }
1522
+
1523
+ export function show(...elements: HTMLElement[]): void {
1524
+ for (const element of elements) {
1525
+ element.style.display = '';
1526
+ element.removeAttribute('aria-hidden');
1527
+ }
1528
+ }
1529
+
1530
+ export function hide(...elements: HTMLElement[]): void {
1531
+ for (const element of elements) {
1532
+ element.style.display = 'none';
1533
+ element.setAttribute('aria-hidden', 'true');
1534
+ }
1535
+ }
1536
+
1537
+ function findParentWithAttribute(node: Node | null, attribute: string): HTMLElement | null {
1538
+ while (node && node.nodeType === node.ELEMENT_NODE) {
1539
+ if (isHTMLElement(node) && node.hasAttribute(attribute)) {
1540
+ return node;
1541
+ }
1542
+
1543
+ node = node.parentNode;
1544
+ }
1545
+
1546
+ return null;
1547
+ }
1548
+
1549
+ export function removeTabIndexAndUpdateFocus(node: HTMLElement): void {
1550
+ if (!node || !node.hasAttribute('tabIndex')) {
1551
+ return;
1552
+ }
1553
+
1554
+ // If we are the currently focused element and tabIndex is removed,
1555
+ // standard DOM behavior is to move focus to the <body> element. We
1556
+ // typically never want that, rather put focus to the closest element
1557
+ // in the hierarchy of the parent DOM nodes.
1558
+ if (node.ownerDocument.activeElement === node) {
1559
+ const parentFocusable = findParentWithAttribute(node.parentElement, 'tabIndex');
1560
+ parentFocusable?.focus();
1561
+ }
1562
+
1563
+ node.removeAttribute('tabindex');
1564
+ }
1565
+
1566
+ export function finalHandler<T extends Event>(fn: (event: T) => any): (event: T) => any {
1567
+ return e => {
1568
+ e.preventDefault();
1569
+ e.stopPropagation();
1570
+ fn(e);
1571
+ };
1572
+ }
1573
+
1574
+ export function domContentLoaded(targetWindow: Window): Promise<void> {
1575
+ return new Promise<void>(resolve => {
1576
+ const readyState = targetWindow.document.readyState;
1577
+ if (readyState === 'complete' || (targetWindow.document && targetWindow.document.body !== null)) {
1578
+ resolve(undefined);
1579
+ } else {
1580
+ const listener = () => {
1581
+ targetWindow.window.removeEventListener('DOMContentLoaded', listener, false);
1582
+ resolve();
1583
+ };
1584
+
1585
+ targetWindow.window.addEventListener('DOMContentLoaded', listener, false);
1586
+ }
1587
+ });
1588
+ }
1589
+
1590
+ /**
1591
+ * Find a value usable for a dom node size such that the likelihood that it would be
1592
+ * displayed with constant screen pixels size is as high as possible.
1593
+ *
1594
+ * e.g. We would desire for the cursors to be 2px (CSS px) wide. Under a devicePixelRatio
1595
+ * of 1.25, the cursor will be 2.5 screen pixels wide. Depending on how the dom node aligns/"snaps"
1596
+ * with the screen pixels, it will sometimes be rendered with 2 screen pixels, and sometimes with 3 screen pixels.
1597
+ */
1598
+ export function computeScreenAwareSize(window: Window, cssPx: number): number {
1599
+ const screenPx = window.devicePixelRatio * cssPx;
1600
+ return Math.max(1, Math.floor(screenPx)) / window.devicePixelRatio;
1601
+ }
1602
+
1603
+ /**
1604
+ * Open safely a new window. This is the best way to do so, but you cannot tell
1605
+ * if the window was opened or if it was blocked by the browser's popup blocker.
1606
+ * If you want to tell if the browser blocked the new window, use {@link windowOpenWithSuccess}.
1607
+ *
1608
+ * See https://github.com/microsoft/monaco-editor/issues/601
1609
+ * To protect against malicious code in the linked site, particularly phishing attempts,
1610
+ * the window.opener should be set to null to prevent the linked site from having access
1611
+ * to change the location of the current page.
1612
+ * See https://mathiasbynens.github.io/rel-noopener/
1613
+ */
1614
+ export function windowOpenNoOpener(url: string): void {
1615
+ // By using 'noopener' in the `windowFeatures` argument, the newly created window will
1616
+ // not be able to use `window.opener` to reach back to the current page.
1617
+ // See https://stackoverflow.com/a/46958731
1618
+ // See https://developer.mozilla.org/en-US/docs/Web/API/Window/open#noopener
1619
+ // However, this also doesn't allow us to realize if the browser blocked
1620
+ // the creation of the window.
1621
+ mainWindow.open(url, '_blank', 'noopener');
1622
+ }
1623
+
1624
+ /**
1625
+ * Open a new window in a popup. This is the best way to do so, but you cannot tell
1626
+ * if the window was opened or if it was blocked by the browser's popup blocker.
1627
+ * If you want to tell if the browser blocked the new window, use {@link windowOpenWithSuccess}.
1628
+ *
1629
+ * Note: this does not set {@link window.opener} to null. This is to allow the opened popup to
1630
+ * be able to use {@link window.close} to close itself. Because of this, you should only use
1631
+ * this function on urls that you trust.
1632
+ *
1633
+ * In otherwords, you should almost always use {@link windowOpenNoOpener} instead of this function.
1634
+ */
1635
+ const popupWidth = 780, popupHeight = 640;
1636
+ export function windowOpenPopup(url: string): void {
1637
+ const left = Math.floor(mainWindow.screenLeft + mainWindow.innerWidth / 2 - popupWidth / 2);
1638
+ const top = Math.floor(mainWindow.screenTop + mainWindow.innerHeight / 2 - popupHeight / 2);
1639
+ mainWindow.open(
1640
+ url,
1641
+ '_blank',
1642
+ `width=${popupWidth},height=${popupHeight},top=${top},left=${left}`
1643
+ );
1644
+ }
1645
+
1646
+ /**
1647
+ * Attempts to open a window and returns whether it succeeded. This technique is
1648
+ * not appropriate in certain contexts, like for example when the JS context is
1649
+ * executing inside a sandboxed iframe. If it is not necessary to know if the
1650
+ * browser blocked the new window, use {@link windowOpenNoOpener}.
1651
+ *
1652
+ * See https://github.com/microsoft/monaco-editor/issues/601
1653
+ * See https://github.com/microsoft/monaco-editor/issues/2474
1654
+ * See https://mathiasbynens.github.io/rel-noopener/
1655
+ *
1656
+ * @param url the url to open
1657
+ * @param noOpener whether or not to set the {@link window.opener} to null. You should leave the default
1658
+ * (true) unless you trust the url that is being opened.
1659
+ * @returns boolean indicating if the {@link window.open} call succeeded
1660
+ */
1661
+ export function windowOpenWithSuccess(url: string, noOpener = true): boolean {
1662
+ const newTab = mainWindow.open();
1663
+ if (newTab) {
1664
+ if (noOpener) {
1665
+ // see `windowOpenNoOpener` for details on why this is important
1666
+ (newTab as any).opener = null;
1667
+ }
1668
+ newTab.location.href = url;
1669
+ return true;
1670
+ }
1671
+ return false;
1672
+ }
1673
+
1674
+ export function animate(targetWindow: Window, fn: () => void): IDisposable {
1675
+ const step = () => {
1676
+ fn();
1677
+ stepDisposable = scheduleAtNextAnimationFrame(targetWindow, step);
1678
+ };
1679
+
1680
+ let stepDisposable = scheduleAtNextAnimationFrame(targetWindow, step);
1681
+ return toDisposable(() => stepDisposable.dispose());
1682
+ }
1683
+
1684
+ export function asCSSPropertyValue(value: string) {
1685
+ return `'${value.replace(/'/g, '%27')}'`;
1686
+ }
1687
+
1688
+ export function asCssValueWithDefault(cssPropertyValue: string | undefined, dflt: string): string {
1689
+ if (cssPropertyValue !== undefined) {
1690
+ const variableMatch = cssPropertyValue.match(/^\s*var\((.+)\)$/);
1691
+ if (variableMatch) {
1692
+ const varArguments = variableMatch[1].split(',', 2);
1693
+ if (varArguments.length === 2) {
1694
+ dflt = asCssValueWithDefault(varArguments[1].trim(), dflt);
1695
+ }
1696
+ return `var(${varArguments[0]}, ${dflt})`;
1697
+ }
1698
+ return cssPropertyValue;
1699
+ }
1700
+ return dflt;
1701
+ }
1702
+
1703
+ export enum DetectedFullscreenMode {
1704
+
1705
+ /**
1706
+ * The document is fullscreen, e.g. because an element
1707
+ * in the document requested to be fullscreen.
1708
+ */
1709
+ DOCUMENT = 1,
1710
+
1711
+ /**
1712
+ * The browser is fullscreen, e.g. because the user enabled
1713
+ * native window fullscreen for it.
1714
+ */
1715
+ BROWSER
1716
+ }
1717
+
1718
+ export interface IDetectedFullscreen {
1719
+
1720
+ /**
1721
+ * Figure out if the document is fullscreen or the browser.
1722
+ */
1723
+ mode: DetectedFullscreenMode;
1724
+
1725
+ /**
1726
+ * Whether we know for sure that we are in fullscreen mode or
1727
+ * it is a guess.
1728
+ */
1729
+ guess: boolean;
1730
+ }
1731
+
1732
+ export function detectFullscreen(targetWindow: Window): IDetectedFullscreen | null {
1733
+
1734
+ // Browser fullscreen: use DOM APIs to detect
1735
+ if (targetWindow.document.fullscreenElement || (<any>targetWindow.document).webkitFullscreenElement || (<any>targetWindow.document).webkitIsFullScreen) {
1736
+ return { mode: DetectedFullscreenMode.DOCUMENT, guess: false };
1737
+ }
1738
+
1739
+ // There is no standard way to figure out if the browser
1740
+ // is using native fullscreen. Via checking on screen
1741
+ // height and comparing that to window height, we can guess
1742
+ // it though.
1743
+
1744
+ if (targetWindow.innerHeight === targetWindow.screen.height) {
1745
+ // if the height of the window matches the screen height, we can
1746
+ // safely assume that the browser is fullscreen because no browser
1747
+ // chrome is taking height away (e.g. like toolbars).
1748
+ return { mode: DetectedFullscreenMode.BROWSER, guess: false };
1749
+ }
1750
+
1751
+ if (platform.isMacintosh || platform.isLinux) {
1752
+ // macOS and Linux do not properly report `innerHeight`, only Windows does
1753
+ if (targetWindow.outerHeight === targetWindow.screen.height && targetWindow.outerWidth === targetWindow.screen.width) {
1754
+ // if the height of the browser matches the screen height, we can
1755
+ // only guess that we are in fullscreen. It is also possible that
1756
+ // the user has turned off taskbars in the OS and the browser is
1757
+ // simply able to span the entire size of the screen.
1758
+ return { mode: DetectedFullscreenMode.BROWSER, guess: true };
1759
+ }
1760
+ }
1761
+
1762
+ // Not in fullscreen
1763
+ return null;
1764
+ }
1765
+
1766
+ /**
1767
+ * Convert a Unicode string to a string in which each 16-bit unit occupies only one byte
1768
+ *
1769
+ * From https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa
1770
+ */
1771
+ function toBinary(str: string): string {
1772
+ const codeUnits = new Uint16Array(str.length);
1773
+ for (let i = 0; i < codeUnits.length; i++) {
1774
+ codeUnits[i] = str.charCodeAt(i);
1775
+ }
1776
+ let binary = '';
1777
+ const uint8array = new Uint8Array(codeUnits.buffer);
1778
+ for (let i = 0; i < uint8array.length; i++) {
1779
+ binary += String.fromCharCode(uint8array[i]);
1780
+ }
1781
+ return binary;
1782
+ }
1783
+
1784
+ /**
1785
+ * Version of the global `btoa` function that handles multi-byte characters instead
1786
+ * of throwing an exception.
1787
+ */
1788
+ export function multibyteAwareBtoa(str: string): string {
1789
+ return btoa(toBinary(str));
1790
+ }
1791
+
1792
+ type ModifierKey = 'alt' | 'ctrl' | 'shift' | 'meta';
1793
+
1794
+ export interface IModifierKeyStatus {
1795
+ altKey: boolean;
1796
+ shiftKey: boolean;
1797
+ ctrlKey: boolean;
1798
+ metaKey: boolean;
1799
+ lastKeyPressed?: ModifierKey;
1800
+ lastKeyReleased?: ModifierKey;
1801
+ event?: KeyboardEvent;
1802
+ }
1803
+
1804
+ export class ModifierKeyEmitter extends event.Emitter<IModifierKeyStatus> {
1805
+
1806
+ private readonly _subscriptions = new DisposableStore();
1807
+ private _keyStatus: IModifierKeyStatus;
1808
+ private static instance: ModifierKeyEmitter;
1809
+
1810
+ private constructor() {
1811
+ super();
1812
+
1813
+ this._keyStatus = {
1814
+ altKey: false,
1815
+ shiftKey: false,
1816
+ ctrlKey: false,
1817
+ metaKey: false
1818
+ };
1819
+
1820
+ this._subscriptions.add(event.Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => this.registerListeners(window, disposables), { window: mainWindow, disposables: this._subscriptions }));
1821
+ }
1822
+
1823
+ private registerListeners(window: Window, disposables: DisposableStore): void {
1824
+ disposables.add(addDisposableListener(window, 'keydown', e => {
1825
+ if (e.defaultPrevented) {
1826
+ return;
1827
+ }
1828
+
1829
+ const event = new StandardKeyboardEvent(e);
1830
+ // If Alt-key keydown event is repeated, ignore it #112347
1831
+ // Only known to be necessary for Alt-Key at the moment #115810
1832
+ if (event.keyCode === KeyCode.Alt && e.repeat) {
1833
+ return;
1834
+ }
1835
+
1836
+ if (e.altKey && !this._keyStatus.altKey) {
1837
+ this._keyStatus.lastKeyPressed = 'alt';
1838
+ } else if (e.ctrlKey && !this._keyStatus.ctrlKey) {
1839
+ this._keyStatus.lastKeyPressed = 'ctrl';
1840
+ } else if (e.metaKey && !this._keyStatus.metaKey) {
1841
+ this._keyStatus.lastKeyPressed = 'meta';
1842
+ } else if (e.shiftKey && !this._keyStatus.shiftKey) {
1843
+ this._keyStatus.lastKeyPressed = 'shift';
1844
+ } else if (event.keyCode !== KeyCode.Alt) {
1845
+ this._keyStatus.lastKeyPressed = undefined;
1846
+ } else {
1847
+ return;
1848
+ }
1849
+
1850
+ this._keyStatus.altKey = e.altKey;
1851
+ this._keyStatus.ctrlKey = e.ctrlKey;
1852
+ this._keyStatus.metaKey = e.metaKey;
1853
+ this._keyStatus.shiftKey = e.shiftKey;
1854
+
1855
+ if (this._keyStatus.lastKeyPressed) {
1856
+ this._keyStatus.event = e;
1857
+ this.fire(this._keyStatus);
1858
+ }
1859
+ }, true));
1860
+
1861
+ disposables.add(addDisposableListener(window, 'keyup', e => {
1862
+ if (e.defaultPrevented) {
1863
+ return;
1864
+ }
1865
+
1866
+ if (!e.altKey && this._keyStatus.altKey) {
1867
+ this._keyStatus.lastKeyReleased = 'alt';
1868
+ } else if (!e.ctrlKey && this._keyStatus.ctrlKey) {
1869
+ this._keyStatus.lastKeyReleased = 'ctrl';
1870
+ } else if (!e.metaKey && this._keyStatus.metaKey) {
1871
+ this._keyStatus.lastKeyReleased = 'meta';
1872
+ } else if (!e.shiftKey && this._keyStatus.shiftKey) {
1873
+ this._keyStatus.lastKeyReleased = 'shift';
1874
+ } else {
1875
+ this._keyStatus.lastKeyReleased = undefined;
1876
+ }
1877
+
1878
+ if (this._keyStatus.lastKeyPressed !== this._keyStatus.lastKeyReleased) {
1879
+ this._keyStatus.lastKeyPressed = undefined;
1880
+ }
1881
+
1882
+ this._keyStatus.altKey = e.altKey;
1883
+ this._keyStatus.ctrlKey = e.ctrlKey;
1884
+ this._keyStatus.metaKey = e.metaKey;
1885
+ this._keyStatus.shiftKey = e.shiftKey;
1886
+
1887
+ if (this._keyStatus.lastKeyReleased) {
1888
+ this._keyStatus.event = e;
1889
+ this.fire(this._keyStatus);
1890
+ }
1891
+ }, true));
1892
+
1893
+ disposables.add(addDisposableListener(window.document.body, 'mousedown', () => {
1894
+ this._keyStatus.lastKeyPressed = undefined;
1895
+ }, true));
1896
+
1897
+ disposables.add(addDisposableListener(window.document.body, 'mouseup', () => {
1898
+ this._keyStatus.lastKeyPressed = undefined;
1899
+ }, true));
1900
+
1901
+ disposables.add(addDisposableListener(window.document.body, 'mousemove', e => {
1902
+ if (e.buttons) {
1903
+ this._keyStatus.lastKeyPressed = undefined;
1904
+ }
1905
+ }, true));
1906
+
1907
+ disposables.add(addDisposableListener(window, 'blur', () => {
1908
+ this.resetKeyStatus();
1909
+ }));
1910
+ }
1911
+
1912
+ get keyStatus(): IModifierKeyStatus {
1913
+ return this._keyStatus;
1914
+ }
1915
+
1916
+ get isModifierPressed(): boolean {
1917
+ return this._keyStatus.altKey || this._keyStatus.ctrlKey || this._keyStatus.metaKey || this._keyStatus.shiftKey;
1918
+ }
1919
+
1920
+ /**
1921
+ * Allows to explicitly reset the key status based on more knowledge (#109062)
1922
+ */
1923
+ resetKeyStatus(): void {
1924
+ this.doResetKeyStatus();
1925
+ this.fire(this._keyStatus);
1926
+ }
1927
+
1928
+ private doResetKeyStatus(): void {
1929
+ this._keyStatus = {
1930
+ altKey: false,
1931
+ shiftKey: false,
1932
+ ctrlKey: false,
1933
+ metaKey: false
1934
+ };
1935
+ }
1936
+
1937
+ static getInstance() {
1938
+ if (!ModifierKeyEmitter.instance) {
1939
+ ModifierKeyEmitter.instance = new ModifierKeyEmitter();
1940
+ }
1941
+
1942
+ return ModifierKeyEmitter.instance;
1943
+ }
1944
+
1945
+ override dispose() {
1946
+ super.dispose();
1947
+ this._subscriptions.dispose();
1948
+ }
1949
+ }
1950
+
1951
+ export function getCookieValue(name: string): string | undefined {
1952
+ const match = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); // See https://stackoverflow.com/a/25490531
1953
+
1954
+ return match ? match.pop() : undefined;
1955
+ }
1956
+
1957
+ export interface IDragAndDropObserverCallbacks {
1958
+ readonly onDragEnter?: (e: DragEvent) => void;
1959
+ readonly onDragLeave?: (e: DragEvent) => void;
1960
+ readonly onDrop?: (e: DragEvent) => void;
1961
+ readonly onDragEnd?: (e: DragEvent) => void;
1962
+ readonly onDragStart?: (e: DragEvent) => void;
1963
+ readonly onDrag?: (e: DragEvent) => void;
1964
+ readonly onDragOver?: (e: DragEvent, dragDuration: number) => void;
1965
+ }
1966
+
1967
+ export class DragAndDropObserver extends Disposable {
1968
+
1969
+ // A helper to fix issues with repeated DRAG_ENTER / DRAG_LEAVE
1970
+ // calls see https://github.com/microsoft/vscode/issues/14470
1971
+ // when the element has child elements where the events are fired
1972
+ // repeadedly.
1973
+ private counter: number = 0;
1974
+
1975
+ // Allows to measure the duration of the drag operation.
1976
+ private dragStartTime = 0;
1977
+
1978
+ constructor(private readonly element: HTMLElement, private readonly callbacks: IDragAndDropObserverCallbacks) {
1979
+ super();
1980
+
1981
+ this.registerListeners();
1982
+ }
1983
+
1984
+ private registerListeners(): void {
1985
+ if (this.callbacks.onDragStart) {
1986
+ this._register(addDisposableListener(this.element, EventType.DRAG_START, (e: DragEvent) => {
1987
+ this.callbacks.onDragStart?.(e);
1988
+ }));
1989
+ }
1990
+
1991
+ if (this.callbacks.onDrag) {
1992
+ this._register(addDisposableListener(this.element, EventType.DRAG, (e: DragEvent) => {
1993
+ this.callbacks.onDrag?.(e);
1994
+ }));
1995
+ }
1996
+
1997
+ this._register(addDisposableListener(this.element, EventType.DRAG_ENTER, (e: DragEvent) => {
1998
+ this.counter++;
1999
+ this.dragStartTime = e.timeStamp;
2000
+
2001
+ this.callbacks.onDragEnter?.(e);
2002
+ }));
2003
+
2004
+ this._register(addDisposableListener(this.element, EventType.DRAG_OVER, (e: DragEvent) => {
2005
+ e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
2006
+
2007
+ this.callbacks.onDragOver?.(e, e.timeStamp - this.dragStartTime);
2008
+ }));
2009
+
2010
+ this._register(addDisposableListener(this.element, EventType.DRAG_LEAVE, (e: DragEvent) => {
2011
+ this.counter--;
2012
+
2013
+ if (this.counter === 0) {
2014
+ this.dragStartTime = 0;
2015
+
2016
+ this.callbacks.onDragLeave?.(e);
2017
+ }
2018
+ }));
2019
+
2020
+ this._register(addDisposableListener(this.element, EventType.DRAG_END, (e: DragEvent) => {
2021
+ this.counter = 0;
2022
+ this.dragStartTime = 0;
2023
+
2024
+ this.callbacks.onDragEnd?.(e);
2025
+ }));
2026
+
2027
+ this._register(addDisposableListener(this.element, EventType.DROP, (e: DragEvent) => {
2028
+ this.counter = 0;
2029
+ this.dragStartTime = 0;
2030
+
2031
+ this.callbacks.onDrop?.(e);
2032
+ }));
2033
+ }
2034
+ }
2035
+
2036
+ type HTMLElementAttributeKeys<T> = Partial<{ [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? HTMLElementAttributeKeys<T[K]> : T[K] }>;
2037
+ type ElementAttributes<T> = HTMLElementAttributeKeys<T> & Record<string, any>;
2038
+ type RemoveHTMLElement<T> = T extends HTMLElement ? never : T;
2039
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
2040
+ type ArrayToObj<T extends readonly any[]> = UnionToIntersection<RemoveHTMLElement<T[number]>>;
2041
+ type HHTMLElementTagNameMap = HTMLElementTagNameMap & { '': HTMLDivElement };
2042
+
2043
+ type TagToElement<T> = T extends `${infer TStart}#${string}`
2044
+ ? TStart extends keyof HHTMLElementTagNameMap
2045
+ ? HHTMLElementTagNameMap[TStart]
2046
+ : HTMLElement
2047
+ : T extends `${infer TStart}.${string}`
2048
+ ? TStart extends keyof HHTMLElementTagNameMap
2049
+ ? HHTMLElementTagNameMap[TStart]
2050
+ : HTMLElement
2051
+ : T extends keyof HTMLElementTagNameMap
2052
+ ? HTMLElementTagNameMap[T]
2053
+ : HTMLElement;
2054
+
2055
+ type TagToElementAndId<TTag> = TTag extends `${infer TTag}@${infer TId}`
2056
+ ? { element: TagToElement<TTag>; id: TId }
2057
+ : { element: TagToElement<TTag>; id: 'root' };
2058
+
2059
+ type TagToRecord<TTag> = TagToElementAndId<TTag> extends { element: infer TElement; id: infer TId }
2060
+ ? Record<(TId extends string ? TId : never) | 'root', TElement>
2061
+ : never;
2062
+
2063
+ type Child = HTMLElement | string | Record<string, HTMLElement>;
2064
+
2065
+ const H_REGEX = /(?<tag>[\w\-]+)?(?:#(?<id>[\w\-]+))?(?<class>(?:\.(?:[\w\-]+))*)(?:@(?<name>(?:[\w\_])+))?/;
2066
+
2067
+ /**
2068
+ * A helper function to create nested dom nodes.
2069
+ *
2070
+ *
2071
+ * ```ts
2072
+ * const elements = h('div.code-view', [
2073
+ * h('div.title@title'),
2074
+ * h('div.container', [
2075
+ * h('div.gutter@gutterDiv'),
2076
+ * h('div@editor'),
2077
+ * ]),
2078
+ * ]);
2079
+ * const editor = createEditor(elements.editor);
2080
+ * ```
2081
+ */
2082
+ export function h<TTag extends string>
2083
+ (tag: TTag):
2084
+ TagToRecord<TTag> extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
2085
+
2086
+ export function h<TTag extends string, T extends Child[]>
2087
+ (tag: TTag, children: [...T]):
2088
+ (ArrayToObj<T> & TagToRecord<TTag>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
2089
+
2090
+ export function h<TTag extends string>
2091
+ (tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>):
2092
+ TagToRecord<TTag> extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
2093
+
2094
+ export function h<TTag extends string, T extends Child[]>
2095
+ (tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>, children: [...T]):
2096
+ (ArrayToObj<T> & TagToRecord<TTag>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
2097
+
2098
+ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partial<ElementAttributes<HTMLElement>> | Record<string, any>, children?: any[]] | [children: any[]]): Record<string, HTMLElement> {
2099
+ let attributes: { $?: string } & Partial<ElementAttributes<HTMLElement>>;
2100
+ let children: (Record<string, HTMLElement> | HTMLElement)[] | undefined;
2101
+
2102
+ if (Array.isArray(args[0])) {
2103
+ attributes = {};
2104
+ children = args[0];
2105
+ } else {
2106
+ attributes = args[0] as any || {};
2107
+ children = args[1];
2108
+ }
2109
+
2110
+ const match = H_REGEX.exec(tag);
2111
+
2112
+ if (!match || !match.groups) {
2113
+ throw new Error('Bad use of h');
2114
+ }
2115
+
2116
+ const tagName = match.groups['tag'] || 'div';
2117
+ const el = document.createElement(tagName);
2118
+
2119
+ if (match.groups['id']) {
2120
+ el.id = match.groups['id'];
2121
+ }
2122
+
2123
+ const classNames = [];
2124
+ if (match.groups['class']) {
2125
+ for (const className of match.groups['class'].split('.')) {
2126
+ if (className !== '') {
2127
+ classNames.push(className);
2128
+ }
2129
+ }
2130
+ }
2131
+ if (attributes.className !== undefined) {
2132
+ for (const className of attributes.className.split('.')) {
2133
+ if (className !== '') {
2134
+ classNames.push(className);
2135
+ }
2136
+ }
2137
+ }
2138
+ if (classNames.length > 0) {
2139
+ el.className = classNames.join(' ');
2140
+ }
2141
+
2142
+ const result: Record<string, HTMLElement> = {};
2143
+
2144
+ if (match.groups['name']) {
2145
+ result[match.groups['name']] = el;
2146
+ }
2147
+
2148
+ if (children) {
2149
+ for (const c of children) {
2150
+ if (isHTMLElement(c)) {
2151
+ el.appendChild(c);
2152
+ } else if (typeof c === 'string') {
2153
+ el.append(c);
2154
+ } else if ('root' in c) {
2155
+ Object.assign(result, c);
2156
+ el.appendChild(c.root);
2157
+ }
2158
+ }
2159
+ }
2160
+
2161
+ for (const [key, value] of Object.entries(attributes)) {
2162
+ if (key === 'className') {
2163
+ continue;
2164
+ } else if (key === 'style') {
2165
+ for (const [cssKey, cssValue] of Object.entries(value)) {
2166
+ el.style.setProperty(
2167
+ camelCaseToHyphenCase(cssKey),
2168
+ typeof cssValue === 'number' ? cssValue + 'px' : '' + cssValue
2169
+ );
2170
+ }
2171
+ } else if (key === 'tabIndex') {
2172
+ el.tabIndex = value;
2173
+ } else {
2174
+ el.setAttribute(camelCaseToHyphenCase(key), value.toString());
2175
+ }
2176
+ }
2177
+
2178
+ result['root'] = el;
2179
+
2180
+ return result;
2181
+ }
2182
+
2183
+ export function svgElem<TTag extends string>
2184
+ (tag: TTag):
2185
+ TagToRecord<TTag> extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
2186
+
2187
+ export function svgElem<TTag extends string, T extends Child[]>
2188
+ (tag: TTag, children: [...T]):
2189
+ (ArrayToObj<T> & TagToRecord<TTag>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
2190
+
2191
+ export function svgElem<TTag extends string>
2192
+ (tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>):
2193
+ TagToRecord<TTag> extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
2194
+
2195
+ export function svgElem<TTag extends string, T extends Child[]>
2196
+ (tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>, children: [...T]):
2197
+ (ArrayToObj<T> & TagToRecord<TTag>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
2198
+
2199
+ export function svgElem(tag: string, ...args: [] | [attributes: { $: string } & Partial<ElementAttributes<HTMLElement>> | Record<string, any>, children?: any[]] | [children: any[]]): Record<string, HTMLElement> {
2200
+ let attributes: { $?: string } & Partial<ElementAttributes<HTMLElement>>;
2201
+ let children: (Record<string, HTMLElement> | HTMLElement)[] | undefined;
2202
+
2203
+ if (Array.isArray(args[0])) {
2204
+ attributes = {};
2205
+ children = args[0];
2206
+ } else {
2207
+ attributes = args[0] as any || {};
2208
+ children = args[1];
2209
+ }
2210
+
2211
+ const match = H_REGEX.exec(tag);
2212
+
2213
+ if (!match || !match.groups) {
2214
+ throw new Error('Bad use of h');
2215
+ }
2216
+
2217
+ const tagName = match.groups['tag'] || 'div';
2218
+ const el = document.createElementNS('http://www.w3.org/2000/svg', tagName) as any as HTMLElement;
2219
+
2220
+ if (match.groups['id']) {
2221
+ el.id = match.groups['id'];
2222
+ }
2223
+
2224
+ const classNames = [];
2225
+ if (match.groups['class']) {
2226
+ for (const className of match.groups['class'].split('.')) {
2227
+ if (className !== '') {
2228
+ classNames.push(className);
2229
+ }
2230
+ }
2231
+ }
2232
+ if (attributes.className !== undefined) {
2233
+ for (const className of attributes.className.split('.')) {
2234
+ if (className !== '') {
2235
+ classNames.push(className);
2236
+ }
2237
+ }
2238
+ }
2239
+ if (classNames.length > 0) {
2240
+ el.className = classNames.join(' ');
2241
+ }
2242
+
2243
+ const result: Record<string, HTMLElement> = {};
2244
+
2245
+ if (match.groups['name']) {
2246
+ result[match.groups['name']] = el;
2247
+ }
2248
+
2249
+ if (children) {
2250
+ for (const c of children) {
2251
+ if (isHTMLElement(c)) {
2252
+ el.appendChild(c);
2253
+ } else if (typeof c === 'string') {
2254
+ el.append(c);
2255
+ } else if ('root' in c) {
2256
+ Object.assign(result, c);
2257
+ el.appendChild(c.root);
2258
+ }
2259
+ }
2260
+ }
2261
+
2262
+ for (const [key, value] of Object.entries(attributes)) {
2263
+ if (key === 'className') {
2264
+ continue;
2265
+ } else if (key === 'style') {
2266
+ for (const [cssKey, cssValue] of Object.entries(value)) {
2267
+ el.style.setProperty(
2268
+ camelCaseToHyphenCase(cssKey),
2269
+ typeof cssValue === 'number' ? cssValue + 'px' : '' + cssValue
2270
+ );
2271
+ }
2272
+ } else if (key === 'tabIndex') {
2273
+ el.tabIndex = value;
2274
+ } else {
2275
+ el.setAttribute(camelCaseToHyphenCase(key), value.toString());
2276
+ }
2277
+ }
2278
+
2279
+ result['root'] = el;
2280
+
2281
+ return result;
2282
+ }
2283
+
2284
+ function camelCaseToHyphenCase(str: string) {
2285
+ return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
2286
+ }
2287
+
2288
+ export function copyAttributes(from: Element, to: Element, filter?: string[]): void {
2289
+ for (const { name, value } of from.attributes) {
2290
+ if (!filter || filter.includes(name)) {
2291
+ to.setAttribute(name, value);
2292
+ }
2293
+ }
2294
+ }
2295
+
2296
+ function copyAttribute(from: Element, to: Element, name: string): void {
2297
+ const value = from.getAttribute(name);
2298
+ if (value) {
2299
+ to.setAttribute(name, value);
2300
+ } else {
2301
+ to.removeAttribute(name);
2302
+ }
2303
+ }
2304
+
2305
+ export function trackAttributes(from: Element, to: Element, filter?: string[]): IDisposable {
2306
+ copyAttributes(from, to, filter);
2307
+
2308
+ const disposables = new DisposableStore();
2309
+
2310
+ disposables.add(sharedMutationObserver.observe(from, disposables, { attributes: true, attributeFilter: filter })(mutations => {
2311
+ for (const mutation of mutations) {
2312
+ if (mutation.type === 'attributes' && mutation.attributeName) {
2313
+ copyAttribute(from, to, mutation.attributeName);
2314
+ }
2315
+ }
2316
+ }));
2317
+
2318
+ return disposables;
2319
+ }
2320
+
2321
+ /**
2322
+ * Helper for calculating the "safe triangle" occluded by hovers to avoid early dismissal.
2323
+ * @see https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles/ for example
2324
+ */
2325
+ export class SafeTriangle {
2326
+ // 4 triangles, 2 points (x, y) stored for each
2327
+ private triangles: number[] = [];
2328
+
2329
+ constructor(
2330
+ private readonly originX: number,
2331
+ private readonly originY: number,
2332
+ target: HTMLElement
2333
+ ) {
2334
+ const { top, left, right, bottom } = target.getBoundingClientRect();
2335
+ const t = this.triangles;
2336
+ let i = 0;
2337
+
2338
+ t[i++] = left;
2339
+ t[i++] = top;
2340
+ t[i++] = right;
2341
+ t[i++] = top;
2342
+
2343
+ t[i++] = left;
2344
+ t[i++] = top;
2345
+ t[i++] = left;
2346
+ t[i++] = bottom;
2347
+
2348
+ t[i++] = right;
2349
+ t[i++] = top;
2350
+ t[i++] = right;
2351
+ t[i++] = bottom;
2352
+
2353
+ t[i++] = left;
2354
+ t[i++] = bottom;
2355
+ t[i++] = right;
2356
+ t[i++] = bottom;
2357
+ }
2358
+
2359
+ public contains(x: number, y: number) {
2360
+ const { triangles, originX, originY } = this;
2361
+ for (let i = 0; i < 4; i++) {
2362
+ if (isPointWithinTriangle(x, y, originX, originY, triangles[2 * i], triangles[2 * i + 1], triangles[2 * i + 2], triangles[2 * i + 3])) {
2363
+ return true;
2364
+ }
2365
+ }
2366
+
2367
+ return false;
2368
+ }
2369
+ }