• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 29841725068

21 Jul 2026 02:58PM UTC coverage: 75.761% (-0.03%) from 75.787%
29841725068

Pull #12707

github

web-flow
Merge 4a9fb46b6 into 7f6572b57
Pull Request #12707: #12650: Zoom to filtered layer from the filter widget

36125 of 56873 branches covered (63.52%)

160 of 229 new or added lines in 13 files covered. (69.87%)

2 existing lines in 1 file now uncovered.

44897 of 59261 relevant lines covered (75.76%)

122.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.3
/web/client/utils/MapWidgetUtils.js
1
/*
2
 * Copyright 2026, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
 */
8
export const WIDGET_CARD_SELECTOR = '[data-widget-card]';
3✔
9

10
/**
11
 * Returns true when the element is rendered and visible in the DOM.
12
 * @param {HTMLElement} element
13
 * @returns {boolean}
14
 */
15
export const isElementVisible = (element) => {
3✔
16
    if (!element || typeof element.getBoundingClientRect !== 'function') {
30✔
17
        return false;
6✔
18
    }
19
    const rect = element.getBoundingClientRect();
24✔
20
    if (rect.width <= 0 || rect.height <= 0) {
24✔
21
        return false;
6✔
22
    }
23
    const style = window.getComputedStyle(element);
18✔
24
    if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) {
18!
NEW
25
        return false;
×
26
    }
27
    return true;
18✔
28
};
29

30
/**
31
 * Returns true when two bounding rectangles overlap.
32
 * @param {DOMRect} a
33
 * @param {DOMRect} b
34
 * @returns {boolean}
35
 */
36
export const elementsOverlap = (a, b) =>
3✔
37
    a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;
18✔
38

39
/**
40
 * Collect visible widget cards overlapping the map container, excluding the
41
 * widget card that contains the map itself.
42
 * @param {HTMLElement} mapContainer
43
 * @param {Document} doc
44
 * @returns {HTMLElement[]}
45
 */
46
export const getVisibleOverlappingWidgetElements = (mapContainer, doc = document) => {
3✔
47
    if (!mapContainer || typeof mapContainer.getBoundingClientRect !== 'function') {
39✔
48
        return [];
3✔
49
    }
50
    return Array.from(doc.querySelectorAll(WIDGET_CARD_SELECTOR))
36✔
51
        .filter((element) => !element.contains(mapContainer))
15✔
52
        .filter(isElementVisible);
53
};
54

55
/**
56
 * Compute dynamic padding from visible widgets obstructing the map.
57
 * Returns the best {top, right, bottom, left} padding by finding the largest
58
 * rectangle free of widget cards.
59
 * @param {HTMLElement} mapContainer
60
 * @param {Document} doc
61
 * @returns {{top: number, right: number, bottom: number, left: number}}
62
 */
63
export const getWidgetPadding = (mapContainer, doc = document) => {
3!
64
    const widgets = getVisibleOverlappingWidgetElements(mapContainer, doc);
33✔
65
    if (!widgets.length) {
33✔
66
        return { top: 0, right: 0, bottom: 0, left: 0 };
21✔
67
    }
68
    const mapRect = mapContainer.getBoundingClientRect();
12✔
69

70
    let regions = [{
12✔
71
        left: mapRect.left,
72
        right: mapRect.right,
73
        top: mapRect.top,
74
        bottom: mapRect.bottom,
75
        width: mapRect.width,
76
        height: mapRect.height
77
    }];
78

79
    widgets.forEach(widget => {
12✔
80
        const widgetRect = widget.getBoundingClientRect();
12✔
81
        const nextRegions = [];
12✔
82

83
        regions.forEach(region => {
12✔
84
            if (elementsOverlap(region, widgetRect)) {
12!
85
                if (widgetRect.left > region.left) {
12✔
86
                    nextRegions.push({ ...region, right: widgetRect.left, width: widgetRect.left - region.left });
6✔
87
                }
88
                if (widgetRect.right < region.right) {
12✔
89
                    nextRegions.push({ ...region, left: widgetRect.right, width: region.right - widgetRect.right });
3✔
90
                }
91
                if (widgetRect.top > region.top) {
12!
NEW
92
                    nextRegions.push({ ...region, bottom: widgetRect.top, height: widgetRect.top - region.top });
×
93
                }
94
                if (widgetRect.bottom < region.bottom) {
12!
NEW
95
                    nextRegions.push({ ...region, top: widgetRect.bottom, height: region.bottom - widgetRect.bottom });
×
96
                }
97
            } else {
NEW
98
                nextRegions.push(region);
×
99
            }
100
        });
101

102
        const filteredRegions = [];
12✔
103
        nextRegions.forEach((nextRegion, i) => {
12✔
104
            let isCovered = false;
9✔
105
            for (let j = 0; j < nextRegions.length; j++) {
9✔
106
                if (i !== j) {
9!
NEW
107
                    const otherRegion = nextRegions[j];
×
NEW
108
                    if (
×
109
                        nextRegion.left >= otherRegion.left
×
110
                        && nextRegion.right <= otherRegion.right
111
                        && nextRegion.top >= otherRegion.top
112
                        && nextRegion.bottom <= otherRegion.bottom
113
                    ) {
NEW
114
                        isCovered = true;
×
NEW
115
                        break;
×
116
                    }
117
                }
118
            }
119
            if (!isCovered) {
9!
120
                filteredRegions.push(nextRegion);
9✔
121
            }
122
        });
123
        regions = filteredRegions;
12✔
124
    });
125

126
    let largestRegion = null;
12✔
127
    let maxArea = -1;
12✔
128

129
    regions.forEach(region => {
12✔
130
        const area = region.width * region.height;
9✔
131
        if (area > maxArea) {
9!
132
            maxArea = area;
9✔
133
            largestRegion = region;
9✔
134
        }
135
    });
136

137
    if (!largestRegion || maxArea <= 0) {
12✔
138
        return { top: 0, right: 0, bottom: 0, left: 0 };
3✔
139
    }
140

141
    return {
9✔
142
        top: Math.max(0, Math.round(largestRegion.top - mapRect.top)),
143
        right: Math.max(0, Math.round(mapRect.right - largestRegion.right)),
144
        bottom: Math.max(0, Math.round(mapRect.bottom - largestRegion.bottom)),
145
        left: Math.max(0, Math.round(largestRegion.left - mapRect.left))
146
    };
147
};
148

149
/**
150
 * Resolve padding for map fit/zoom operations.
151
 * Applies layout and widget padding to determine best zoom bounds.
152
 * @param {HTMLElement} mapContainer
153
 * @param {object} layoutPadding padding from map layout selectors (top/bottom)
154
 * @param {Document} doc
155
 * @returns {{top: number, right: number, bottom: number, left: number}}
156
 */
157
export const resolveZoomToExtentPadding = (mapContainer, layoutPadding, doc = document) => {
3✔
158
    const widgetPadding = getWidgetPadding(mapContainer, doc);
21✔
159
    return {
21✔
160
        top: Math.max(layoutPadding?.top || 0, widgetPadding.top),
36✔
161
        bottom: Math.max(layoutPadding?.bottom || 0, widgetPadding.bottom),
36✔
162
        left: Math.max(layoutPadding?.left || 0, widgetPadding.left),
36✔
163
        right: Math.max(layoutPadding?.right || 0, widgetPadding.right)
36✔
164
    };
165
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc