UNPKG

9.85 kBJavaScriptView Raw
1export default {
2 name: "cjsPopover",
3 injections: [],
4 fn: () => ({
5 // restrict: "E",
6 link: function(scope, element, attrs) {
7 var useOverlay = attrs.noOverlay === undefined;
8 var horizontal = attrs.horizontal !== undefined;
9 var menuwidth = parseFloat(attrs.menuwidth) || 280;
10 var menuheight = parseFloat(attrs.menuheight) || 150;
11
12 element.addClass("modal");
13 element.addClass("popover");
14 var removeEventHandlers;
15
16 function clickOutsidePopup(e) {
17 removeEventHandlers();
18 var r = element[0].getBoundingClientRect();
19 var x = e.changedTouches ? e.changedTouches[0].clientX : e.touches ? e.touches[0].clientX : e.clientX;
20 var y = e.changedTouches ? e.changedTouches[0].clientY : e.touches ? e.touches[0].clientY : e.clientY;
21 if (x > r.left && x < r.right && y > r.top && y < r.bottom) return;
22 scope.$apply("hideModal('" + attrs.cjsSidepanel + "')");
23 removeEventHandlers();
24 }
25
26 function closeWithKey(e) {
27 e.preventDefault();
28 clickOutsidePopup(e);
29 }
30
31 removeEventHandlers = function() {
32 window.document.body.removeEventListener(window.useMouse ? 'mousedown' : "touchstart", clickOutsidePopup, true);
33 window.document.body.removeEventListener('keydown', closeWithKey, true);
34 };
35
36 scope.$on('$destroy', removeEventHandlers);
37
38 function ensureOverlay() {
39 var parentPageElement = element.closest(".chondric-page");
40 if (parentPageElement.length === 0) parentPageElement = element.closest(".chondric-section");
41 if (parentPageElement.length === 0) parentPageElement = element.closest(".chondric-viewport");
42 if (useOverlay) {
43 var overlay = angular.element(".modal-overlay", parentPageElement);
44 if (overlay.length === 0) {
45 overlay = angular.element('<div class="modal-overlay"></div>');
46 parentPageElement.append(overlay);
47 }
48 return overlay;
49 }
50 }
51 var lastFocused = null;
52 scope.$watch(attrs.cjsPopover, function(val) {
53
54 var overlay = ensureOverlay();
55
56 if (!val) {
57
58 if (lastFocused && document.activeElement === element[0]) {
59 if (lastFocused.tagName == "BODY") {
60 document.activeElement.blur();
61 } else {
62 lastFocused.focus();
63 }
64 lastFocused = null;
65 }
66
67 if (useOverlay) {
68 overlay.removeClass("active");
69 }
70 element.removeClass("active");
71 removeEventHandlers();
72 } else {
73 if (window.useMouse) {
74 // giving the popup focus doesn't really work on iOS, so leace focus where it is
75 if (document.activeElement && document.activeElement.tagName != "BODY") {
76 lastFocused = document.activeElement;
77 }
78 element.focus();
79 }
80
81 window.document.body.addEventListener(window.useMouse ? 'mousedown' : "touchstart", clickOutsidePopup, true);
82 window.document.body.addEventListener('keydown', closeWithKey, true);
83 menuheight = element.outerHeight() || menuheight;
84 menuwidth = element.outerWidth() || menuwidth;
85
86 var menupos = {};
87 // TODO: should get actual size of the element, but it is display: none at this point.
88
89 // var sw = element[0].offsetParent.offsetWidth;
90 // var sh = element[0].offsetParent.offsetHeight;
91
92 var parentRect = element[0].offsetParent.getBoundingClientRect();
93
94 var br = (element.closest(".chondric-page")[0] || document.body).getBoundingClientRect();
95 var pageRect = {
96 top: Math.max(0, br.top),
97 bottom: Math.min(angular.element(window).height(), br.bottom),
98 left: Math.max(0, br.left),
99 right: Math.min(angular.element(window).width(), br.right)
100 };
101 pageRect.width = pageRect.right - pageRect.left;
102 pageRect.height = pageRect.bottom - pageRect.top;
103
104 var horizontalCutoff = pageRect.left + pageRect.width / 2;
105 var verticalCutoff = pageRect.top + pageRect.height / 2;
106 var idealX = 0;
107 var idealY = 0;
108
109 var cr;
110 if (val.element && val.element[0]) {
111 var button = val.element[0];
112 cr = button.getBoundingClientRect();
113
114 if (horizontal) {
115 // x at left or right of button, y center of button
116 if (cr.right > horizontalCutoff) {
117 idealX = cr.right;
118 } else {
119 idealX = cr.left;
120 }
121 idealY = cr.top + cr.height / 2;
122 } else {
123 // x at center of button, y at left or right of button
124 var w = cr.width;
125 if (!w) w = cr.right - cr.left;
126
127 idealX = cr.left + w / 2;
128 if (cr.top > verticalCutoff) {
129 idealY = cr.top;
130 } else {
131 idealY = cr.bottom;
132 }
133 }
134
135 } else {
136 // the values usually come from mouse events which use document coordinates.
137 // convert to viewport coordinates to match getBoundingClientRect
138 idealX = (val.x || 0) - (window.pageXOffset || 0);
139 idealY = (val.y || 0) - (window.pageYOffset || 0);
140 }
141
142
143 var actualX = idealX;
144 var actualY = idealY;
145 // adjust position to ensure menu remains onscreen
146 if (horizontal) {
147 if (idealY - 10 - menuheight / 2 < pageRect.top) actualY = pageRect.top + menuheight / 2 + 10;
148 if ((idealY + 10 + menuheight / 2) > pageRect.bottom) actualY = pageRect.bottom - menuheight / 2 - 10;
149 } else {
150 if (idealX - 10 - menuwidth / 2 < pageRect.left) actualX = pageRect.left + menuwidth / 2 + 10;
151 if ((idealX + 10 + menuwidth / 2) > pageRect.right) actualX = pageRect.right - menuwidth / 2 - 10;
152 }
153
154 if (horizontal) {
155 if (actualX < horizontalCutoff) {
156 menupos.left = (actualX + 13 - parentRect.left) + "px";
157 menupos.right = "auto";
158 element.addClass("right").removeClass("left");
159 } else {
160 menupos.right = (parentRect.right - actualX + 13) + "px";
161 menupos.left = "auto";
162 element.addClass("left").removeClass("right");
163 }
164 menupos.top = (actualY - menuheight / 2 - parentRect.top) + "px";
165 } else {
166 if (actualY < verticalCutoff || (cr && actualY > cr.top)) {
167 menupos.top = (actualY + 13 - parentRect.top) + "px";
168 menupos.bottom = "auto";
169 element.addClass("down").removeClass("up");
170 } else {
171 menupos.bottom = (parentRect.bottom - actualY + 13) + "px";
172 menupos.top = "auto";
173 element.addClass("up").removeClass("down");
174 }
175 menupos.left = (actualX - menuwidth / 2 - parentRect.left) + "px";
176 }
177
178 var indel = angular.element(".poparrow", element);
179 if (indel.length > 0) {
180 if (horizontal) {
181 var arrowtop = idealY - (actualY - menuheight / 2) - 13;
182 if (arrowtop < 10) arrowtop = 10;
183 if (arrowtop + 26 > menuheight - 10) arrowtop = menuheight - 10 - 26;
184 indel.css("top", arrowtop + "px");
185 } else {
186 var arrowleft = idealX - (actualX - menuwidth / 2) - 13;
187 if (arrowleft < 10) arrowleft = 10;
188 if (arrowleft + 26 > menuwidth - 10) arrowleft = menuwidth - 10 - 26;
189 indel.css("left", arrowleft + "px");
190
191 }
192 }
193 if (useOverlay) {
194 overlay.addClass("active");
195 }
196 element.addClass("active");
197 element.css(menupos);
198 }
199 });
200 }
201 })
202};