Skip to content

Commit 379cc08

Browse files
authored
Merge pull request #363 from cal-smith/dialog
fix(dialog): improve positioning when dealing with margins and un-positioned bodys
2 parents a581766 + 83a12b6 commit 379cc08

File tree

4 files changed

+54
-13
lines changed

4 files changed

+54
-13
lines changed

.storybook/preview.scss

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
@import "~carbon-components/src/globals/scss/styles";
22

3-
43
body {
5-
padding: 20px;
6-
// for tooltips/popovers ... can be on any chld element before the popover
7-
// but body is most conveient
8-
position: relative;
4+
margin: 20px;
95
}

src/dialog/dialog.component.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,6 @@ export class Dialog implements OnInit, AfterViewInit, OnDestroy {
207207

208208
/**
209209
* Uses the position service to position the `Dialog` in screen space
210-
* @memberof Dialog
211210
*/
212211
placeDialog(): void {
213212
// helper to find the position based on the current/given environment
@@ -226,7 +225,7 @@ export class Dialog implements OnInit, AfterViewInit, OnDestroy {
226225
let el = this.dialog.nativeElement;
227226
let dialogPlacement = this.placement;
228227

229-
// split always retuns an array, so we can just use the auto position logic
228+
// split always returns an array, so we can just use the auto position logic
230229
// for single positions too
231230
const placements = this.dialogConfig.placement.split(",");
232231
const weightedPlacements = placements.map(placement => {
@@ -249,7 +248,7 @@ export class Dialog implements OnInit, AfterViewInit, OnDestroy {
249248
};
250249
});
251250

252-
// sort the placments from best to worst
251+
// sort the placements from best to worst
253252
weightedPlacements.sort((a, b) => b.weight - a.weight);
254253
// pick the best!
255254
dialogPlacement = weightedPlacements[0].placement;
@@ -265,7 +264,6 @@ export class Dialog implements OnInit, AfterViewInit, OnDestroy {
265264
/**
266265
* Sets up a KeyboardEvent to close `Dialog` with Escape key.
267266
* @param {KeyboardEvent} event
268-
* @memberof Dialog
269267
*/
270268
@HostListener("keydown", ["$event"])
271269
escapeClose(event: KeyboardEvent) {

src/dialog/overflow-menu/overflow-menu.stories.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { storiesOf, moduleMetadata } from "@storybook/angular";
22
import { withKnobs, boolean, number } from "@storybook/addon-knobs";
33

44
import { DialogModule } from "../../";
5+
import { PlaceholderModule } from "../../placeholder/placeholder.module";
56

67
let options;
78

@@ -17,7 +18,8 @@ storiesOf("Overflow Menu", module)
1718
.addDecorator(
1819
moduleMetadata({
1920
imports: [
20-
DialogModule
21+
DialogModule,
22+
PlaceholderModule
2123
]
2224
})
2325
)
@@ -36,6 +38,7 @@ storiesOf("Overflow Menu", module)
3638
<ibm-overflow-menu-option disabled="true" (selected)="selected($event)">Disabled</ibm-overflow-menu-option>
3739
<ibm-overflow-menu-option type="danger" (selected)="selected($event)">Danger option</ibm-overflow-menu-option>
3840
</ibm-overflow-menu>
41+
<ibm-placeholder></ibm-placeholder>
3942
`,
4043
props: {
4144
click: () => console.log("click"),

src/utils/position.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function calculatePosition(referenceOffset: Offset, reference: HTMLElement, targ
7878

7979
export namespace position {
8080
export function getRelativeOffset(target: HTMLElement): Offset {
81-
// start with the inital element offsets
81+
// start with the initial element offsets
8282
let offsets = {
8383
left: target.offsetLeft,
8484
top: target.offsetTop
@@ -93,9 +93,53 @@ export namespace position {
9393
}
9494

9595
export function getAbsoluteOffset(target: HTMLElement): Offset {
96+
let positionedElement;
97+
let currentNode = target;
98+
let margins = {
99+
top: 0,
100+
left: 0
101+
};
102+
103+
// searches either for a parent `positionedElement` or for
104+
// containing elements with additional margins
105+
// once we have a `positionedElement` we can stop searching
106+
// since we use offsetParent we end up skipping most elements
107+
while (currentNode.offsetParent && !positionedElement) {
108+
const computed = getComputedStyle(currentNode.offsetParent);
109+
if (computed.position !== "static") {
110+
positionedElement = currentNode.offsetParent;
111+
}
112+
113+
// find static elements with additional margins
114+
// since they tend to throw off our positioning
115+
// (usually this is just the body)
116+
if (
117+
computed.position === "static" &&
118+
computed.marginLeft &&
119+
computed.marginTop
120+
) {
121+
if (parseInt(computed.marginTop, 10)) {
122+
margins.top += parseInt(computed.marginTop, 10);
123+
}
124+
if (parseInt(computed.marginLeft, 10)) {
125+
margins.left += parseInt(computed.marginLeft, 10);
126+
}
127+
}
128+
129+
currentNode = currentNode.offsetParent as HTMLElement;
130+
}
131+
132+
// if we don't find any `relativeElement` on our walk
133+
// default to the body
134+
if (!positionedElement) {
135+
positionedElement = document.body;
136+
}
137+
138+
const targetRect = target.getBoundingClientRect();
139+
const relativeRect = positionedElement.getBoundingClientRect();
96140
return {
97-
top: target.getBoundingClientRect().top,
98-
left: target.getBoundingClientRect().left - document.body.getBoundingClientRect().left
141+
top: targetRect.top - relativeRect.top + margins.top,
142+
left: targetRect.left - relativeRect.left + margins.left
99143
};
100144
}
101145

0 commit comments

Comments
 (0)