Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/overlayLayoutElement.ts
13389 views
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 { getComputedStyle, setParentFlowTo } from './dom.js';
7
import { IDisposable } from '../common/lifecycle.js';
8
import { generateUuid } from '../common/uuid.js';
9
10
/**
11
* If the element already has an `anchor-name` style, return it.
12
* Otherwise generate a fresh `--overlay-anchor-<uuid>` name, assign it, and return it.
13
*/
14
function getOrCreateAnchorName(element: HTMLElement): string {
15
const existing = element.style.getPropertyValue('anchor-name');
16
if (existing) {
17
return existing;
18
}
19
const name = `--overlay-anchor-${generateUuid()}`;
20
element.style.setProperty('anchor-name', name);
21
return name;
22
}
23
24
/**
25
* Positions an element over another element anywhere in the dom using absolute positioning.
26
*
27
* This is useful for cases where a dom node cannot be re-parented without losing its state, such as a iframe.
28
*
29
* Call {@link setAnchorElement} each time the layout is recalculated. When the
30
* same anchor element is passed again the call is a no-op (the browser keeps them in sync).
31
*/
32
export class OverlayLayoutElement implements IDisposable {
33
34
private _currentAnchor?: { readonly element: HTMLElement; readonly name: string };
35
private _clippingAnchor?: { readonly element: HTMLElement; readonly name: string };
36
37
/**
38
* The root element that contains the overlay element.
39
*
40
* This also provides clipping support for the overlay element. Clipping is needed when the anchor is
41
* scrollable and may scroll and be hidden by overflow from its parent container.
42
*/
43
private readonly _root: HTMLElement;
44
45
constructor() {
46
this.content = document.createElement('div');
47
this.content.style.position = 'absolute';
48
this.content.style.overflow = 'hidden';
49
50
this._root = document.createElement('div');
51
this._root.appendChild(this.content);
52
53
this.reapplyLayoutStyles();
54
}
55
56
public reapplyLayoutStyles(): void {
57
this.content.style.position = 'fixed';
58
this.content.style.top = 'anchor(top)';
59
this.content.style.left = 'anchor(left)';
60
this.content.style.width = 'anchor-size(width)';
61
this.content.style.height = 'anchor-size(height)';
62
this.content.style.pointerEvents = 'auto';
63
64
this._root.style.position = 'absolute';
65
this._root.style.pointerEvents = 'none';
66
}
67
68
public dispose(): void {
69
this.root.remove();
70
}
71
72
/**
73
* The outermost element. This is what should be appended to the actual dom hierarchy, typically near to
74
* the document root node.
75
*/
76
public get root(): HTMLElement {
77
return this._root;
78
}
79
80
/**
81
* The actual element that is positioned over the anchor.
82
*/
83
public readonly content: HTMLElement;
84
85
/**
86
* Position the content over `anchorElement`.
87
*
88
* This only needs to be called when the anchor element or the clipping container changes.
89
*/
90
public setAnchorElement(
91
anchorElement: HTMLElement,
92
options?: {
93
readonly clippingContainer?: HTMLElement;
94
},
95
): void {
96
if (this._currentAnchor?.element !== anchorElement) {
97
const name = getOrCreateAnchorName(anchorElement);
98
this.content.style.setProperty('position-anchor', name);
99
setParentFlowTo(this.content, anchorElement);
100
this._currentAnchor = { element: anchorElement, name };
101
}
102
103
this._updateClipping(options?.clippingContainer);
104
this._updateZIndex(anchorElement);
105
}
106
107
/**
108
* Walk up from the anchor element to find the nearest ancestor with an explicit
109
* z-index and place the overlay one level above it. This ensures the overlay sits
110
* above modal layers or other stacking contexts.
111
*/
112
private _updateZIndex(anchorElement: HTMLElement): void {
113
let zIndex = '';
114
for (let el: HTMLElement | null = anchorElement; el; el = el.parentElement) {
115
const computed = getComputedStyle(el).zIndex;
116
if (computed && computed !== 'auto') {
117
zIndex = String(Number(computed) + 1);
118
break;
119
}
120
}
121
this.content.style.zIndex = zIndex;
122
}
123
124
private _updateClipping(clippingContainer: HTMLElement | undefined): void {
125
if (this._clippingAnchor?.element === clippingContainer) {
126
return;
127
}
128
129
this._root.style.removeProperty('position-anchor');
130
131
const ws = this._root.style;
132
if (clippingContainer) {
133
const name = getOrCreateAnchorName(clippingContainer);
134
ws.clipPath = 'content-box';
135
ws.setProperty('position-anchor', name);
136
ws.setProperty('top', 'anchor(top)');
137
ws.setProperty('left', 'anchor(left)');
138
ws.setProperty('width', `anchor-size(width)`);
139
ws.setProperty('height', `anchor-size(height)`);
140
this._clippingAnchor = { element: clippingContainer, name };
141
} else {
142
ws.clipPath = '';
143
ws.setProperty('top', '0');
144
ws.setProperty('left', '0');
145
ws.setProperty('right', '0');
146
ws.setProperty('bottom', '0');
147
this._clippingAnchor = undefined;
148
}
149
}
150
}
151
152