Skip to content

Commit 59dd68b

Browse files
authored
fix(global position): calculate proper left and top co-ordinates when hostWidth and hostHeight in percentage
1 parent 462b48f commit 59dd68b

File tree

2 files changed

+259
-47
lines changed

2 files changed

+259
-47
lines changed

projects/toppy/src/lib/position/global-position.ts

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,64 +9,66 @@ export interface Config {
99
}
1010
export class GlobalPosition extends Position {
1111
private size;
12-
protected _config: Config = { placement: InsidePlacement.CENTER, hostWidth: 500, hostHeight: 500, offset: 0 };
12+
protected _config: Config = { placement: InsidePlacement.CENTER, hostWidth: 100, hostHeight: 100, offset: 0 };
1313
constructor(config: Config) {
1414
super();
1515
this._config = { ...this._config, ...config };
1616
}
1717
getPositions(hostElement?: HTMLElement) {
18-
const host = hostElement.getBoundingClientRect();
18+
const host = hostElement.getBoundingClientRect() as any;
1919
const src = {
2020
width: (window as any).innerWidth,
2121
height: (window as any).innerHeight
2222
};
23-
const props = this[`calculate_${this._config.placement}`](src, host);
24-
return { ...props, width: this._config.hostWidth, height: this._config.hostHeight, position: 'fixed' };
25-
}
2623

27-
private [`calculate_${InsidePlacement.TOP}`](src, host) {
28-
const top = this._config.offset;
29-
const left = (src.width - host.width) / 2;
30-
return { left, top };
31-
}
32-
private [`calculate_${InsidePlacement.BOTTOM}`](src, host) {
33-
const bottom = this._config.offset;
34-
const left = (src.width - host.width) / 2;
35-
return { left, bottom };
36-
}
37-
private [`calculate_${InsidePlacement.LEFT}`](src, host) {
38-
const top = (src.height - host.height) / 2;
39-
const left = this._config.offset;
40-
return { left, top };
41-
}
42-
private [`calculate_${InsidePlacement.RIGHT}`](src, host) {
43-
const top = (src.height - host.height) / 2;
44-
const right = this._config.offset;
45-
return { right, top };
46-
}
47-
private [`calculate_${InsidePlacement.CENTER}`](src, host) {
48-
const top = (src.height - host.height) / 2;
49-
const left = (src.width - host.width) / 2;
50-
return { left, top };
51-
}
52-
private [`calculate_${InsidePlacement.TOP_LEFT}`](src, host) {
53-
const top = this._config.offset;
54-
const left = this._config.offset;
55-
return { left, top };
56-
}
57-
private [`calculate_${InsidePlacement.TOP_RIGHT}`](src, host) {
58-
const top = this._config.offset;
59-
const right = this._config.offset;
60-
return { right, top };
24+
if (typeof this._config.hostHeight === 'number') {
25+
host.height = this._config.hostHeight = Math.abs(this._config.hostHeight);
26+
}
27+
if (typeof this._config.hostWidth === 'number') {
28+
host.width = this._config.hostWidth = Math.abs(this._config.hostWidth);
29+
}
30+
if (typeof this._config.hostWidth === 'string' && this._config.hostWidth.endsWith('%')) {
31+
this._config.hostWidth = this._getPercentageToCssPx(src.width, this._config.hostWidth);
32+
}
33+
if (typeof this._config.hostHeight === 'string' && this._config.hostHeight.endsWith('%')) {
34+
this._config.hostHeight = this._getPercentageToCssPx(src.height, this._config.hostHeight);
35+
}
36+
37+
const props = this._calc(this._config.placement, src, host);
38+
return { ...props, width: this._config.hostWidth, height: this._config.hostHeight, position: 'fixed' };
6139
}
62-
private [`calculate_${InsidePlacement.BOTTOM_LEFT}`](src, host) {
63-
const bottom = this._config.offset;
64-
const left = this._config.offset;
65-
return { left, bottom };
40+
private _getPercentageToCssPx(max, percentage: string) {
41+
let number = Number(percentage.slice(0, -1));
42+
if (number > 100) {
43+
number = 100;
44+
}
45+
return `calc(${max}px - ${100 - number}%)`;
6646
}
67-
private [`calculate_${InsidePlacement.BOTTOM_RIGHT}`](src, host) {
68-
const bottom = this._config.offset;
69-
const right = this._config.offset;
70-
return { right, bottom };
47+
48+
private _calc(placement: InsidePlacement, src, host) {
49+
const [main, sub] = placement.split('');
50+
const p: any = {};
51+
52+
if (main === 't') {
53+
p.top = this._config.offset;
54+
}
55+
if (main === 'b') {
56+
p.bottom = this._config.offset;
57+
}
58+
if ((main === 'l' || main === 'r' || main === 'c') && !sub) {
59+
p.top = (src.height - host.height) / 2;
60+
}
61+
62+
if ((main === 't' || main === 'b' || main === 'c') && !sub) {
63+
p.left = (src.width - host.width) / 2;
64+
}
65+
if ((main === 'l' && !sub) || sub === 'l') {
66+
p.left = this._config.offset;
67+
}
68+
if ((main === 'r' && !sub) || sub === 'r') {
69+
p.right = this._config.offset;
70+
}
71+
72+
return p;
7173
}
7274
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/// <reference types="karma-viewport" />
2+
3+
import { InsidePlacement } from '../../lib/models';
4+
import { GlobalPosition } from '../../lib/position';
5+
6+
describe('== Global position ==', () => {
7+
let targetElement: HTMLElement;
8+
let hostElement: HTMLElement;
9+
let ww;
10+
let wh;
11+
beforeEach(() => {
12+
targetElement = document.createElement('div');
13+
targetElement.setAttribute('class', 'foobar');
14+
const textnode = document.createTextNode('Hello');
15+
targetElement.appendChild(textnode);
16+
document.getElementsByTagName('body')[0].appendChild(targetElement);
17+
18+
hostElement = document.createElement('div');
19+
hostElement.setAttribute('class', 'hostelement');
20+
const textnode2 = document.createTextNode('Im host');
21+
hostElement.appendChild(textnode2);
22+
document.getElementsByTagName('body')[0].appendChild(hostElement);
23+
viewport.set(1000, 480);
24+
25+
ww = window.innerWidth;
26+
wh = window.innerHeight;
27+
});
28+
afterEach(() => {
29+
document.getElementsByTagName('body')[0].removeChild(targetElement);
30+
document.getElementsByTagName('body')[0].removeChild(hostElement);
31+
viewport.set(1000, 480);
32+
});
33+
34+
it('should have target element in document', () => {
35+
expect(document.querySelector('.foobar').textContent).toBe('Hello');
36+
});
37+
it('should get updated config', () => {
38+
const gloPos = new GlobalPosition({});
39+
gloPos.updateConfig({ offset: 2 });
40+
expect((gloPos as any)._config).toEqual({
41+
placement: InsidePlacement.CENTER,
42+
hostWidth: 100,
43+
hostHeight: 100,
44+
offset: 2
45+
});
46+
});
47+
it('should return correct class name', () => {
48+
const gloPos = new GlobalPosition({});
49+
expect(gloPos.getClassName()).toBe('global-position');
50+
});
51+
52+
describe('should return correct position coords of host element', () => {
53+
let srcCoords;
54+
beforeEach(() => {
55+
srcCoords = targetElement.getBoundingClientRect();
56+
});
57+
it('when exact width and height is provided in px', () => {
58+
const gloPos = new GlobalPosition({
59+
hostWidth: 4,
60+
hostHeight: 10,
61+
placement: InsidePlacement.TOP
62+
});
63+
expect(gloPos.getPositions(hostElement)).toEqual({
64+
left: (ww - 4) / 2,
65+
top: 0,
66+
width: 4,
67+
height: 10,
68+
position: 'fixed'
69+
});
70+
});
71+
it('when exact width and height is provided in negative px', () => {
72+
const gloPos = new GlobalPosition({
73+
hostWidth: -4,
74+
hostHeight: -10,
75+
placement: InsidePlacement.TOP
76+
});
77+
expect(gloPos.getPositions(hostElement)).toEqual({
78+
left: (ww - 4) / 2,
79+
top: 0,
80+
width: 4,
81+
height: 10,
82+
position: 'fixed'
83+
});
84+
});
85+
it('when exact width and height is provided in percentage', () => {
86+
const gloPos = new GlobalPosition({
87+
hostWidth: '50%',
88+
hostHeight: '50%',
89+
placement: InsidePlacement.TOP
90+
});
91+
expect(gloPos.getPositions(hostElement)).toEqual({
92+
left: (ww - hostElement.offsetWidth) / 2,
93+
top: 0,
94+
width: `calc(${ww}px - 50%)`,
95+
height: `calc(${wh}px - 50%)`,
96+
position: 'fixed'
97+
});
98+
});
99+
it('when exact width and height is provided in higher percentage', () => {
100+
const gloPos = new GlobalPosition({
101+
hostWidth: '150%',
102+
hostHeight: '150%',
103+
placement: InsidePlacement.TOP
104+
});
105+
expect(gloPos.getPositions(hostElement)).toEqual({
106+
left: (ww - hostElement.offsetWidth) / 2,
107+
top: 0,
108+
width: `calc(${ww}px - 0%)`,
109+
height: `calc(${wh}px - 0%)`,
110+
position: 'fixed'
111+
});
112+
});
113+
it('when no width and height is provided', () => {
114+
const gloPos = new GlobalPosition({ placement: InsidePlacement.TOP_RIGHT });
115+
expect(gloPos.getPositions(hostElement)).toEqual({
116+
right: 0,
117+
top: 0,
118+
width: 100,
119+
height: 100,
120+
position: 'fixed'
121+
});
122+
});
123+
});
124+
describe('should get correction position for', () => {
125+
const targetElCoords = {
126+
width: (window as any).innerWidth,
127+
height: (window as any).innerHeight
128+
};
129+
130+
const hostElCoords = {
131+
width: 4, // actual 967
132+
height: 10 // actual 18
133+
};
134+
getData().forEach(data => {
135+
it(data.name, () => {
136+
const gloPos = new GlobalPosition({
137+
hostWidth: hostElCoords.width,
138+
hostHeight: hostElCoords.height,
139+
placement: data.placement,
140+
offset: 2
141+
});
142+
const pos = (gloPos as any)._calc(data.placement, targetElCoords, hostElCoords);
143+
expect(pos).toEqual(data.expected);
144+
});
145+
});
146+
});
147+
function getData() {
148+
// offset is 2
149+
ww = window.innerWidth;
150+
wh = window.innerHeight;
151+
const tests = [
152+
{
153+
name: 'bottom',
154+
placement: InsidePlacement.BOTTOM,
155+
method: `calculate_${InsidePlacement.BOTTOM}`,
156+
expected: { left: (ww - 4) / 2, bottom: 2 }
157+
},
158+
{
159+
name: 'top',
160+
placement: InsidePlacement.TOP,
161+
method: `calculate_${InsidePlacement.TOP}`,
162+
expected: { left: (ww - 4) / 2, top: 2 }
163+
},
164+
{
165+
name: 'left',
166+
placement: InsidePlacement.LEFT,
167+
method: `calculate_${InsidePlacement.LEFT}`,
168+
expected: { left: 2, top: (wh - 10) / 2 }
169+
},
170+
{
171+
name: 'right',
172+
placement: InsidePlacement.RIGHT,
173+
method: `calculate_${InsidePlacement.RIGHT}`,
174+
expected: { right: 2, top: (wh - 10) / 2 }
175+
},
176+
{
177+
name: 'center',
178+
placement: InsidePlacement.CENTER,
179+
method: `calculate_${InsidePlacement.CENTER}`,
180+
expected: { left: (ww - 4) / 2, top: (wh - 10) / 2 }
181+
},
182+
{
183+
name: 'top left',
184+
placement: InsidePlacement.TOP_LEFT,
185+
method: `calculate_${InsidePlacement.TOP_LEFT}`,
186+
expected: { left: 2, top: 2 }
187+
},
188+
{
189+
name: 'top right',
190+
placement: InsidePlacement.TOP_RIGHT,
191+
method: `calculate_${InsidePlacement.TOP_RIGHT}`,
192+
expected: { right: 2, top: 2 }
193+
},
194+
{
195+
name: 'bottom left',
196+
placement: InsidePlacement.BOTTOM_LEFT,
197+
method: `calculate_${InsidePlacement.BOTTOM_LEFT}`,
198+
expected: { left: 2, bottom: 2 }
199+
},
200+
{
201+
name: 'bottom right',
202+
placement: InsidePlacement.BOTTOM_RIGHT,
203+
method: `calculate_${InsidePlacement.BOTTOM_RIGHT}`,
204+
expected: { right: 2, bottom: 2 }
205+
}
206+
];
207+
208+
return tests;
209+
}
210+
});

0 commit comments

Comments
 (0)