@defra/interactive-map 0.0.46-alpha → 0.0.46-fmp-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/docs/api/map-style-config.md +7 -0
- package/docs/assets/images/select-feature.jpg +0 -0
- package/govuk-prototype-kit.config.json +1 -0
- package/package.json +1 -1
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-es/src/api/editFeature.js +12 -5
- package/plugins/beta/draw-es/src/api/newPolygon.js +7 -2
- package/plugins/beta/draw-es/src/api/removeSafeZone.js +30 -0
- package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +5 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapEngine.js +10 -5
- package/plugins/draw/src/adapters/openlayers/snap/snapEngine.test.js +1 -0
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/src/openlayersProvider.js +1 -1
- package/providers/beta/openlayers/src/openlayersProvider.test.js +22 -0
- package/providers/beta/openlayers/src/utils/highlightFeatures.js +23 -11
- package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +2 -0
- package/providers/beta/openlayers/src/utils/hoverCursor.js +6 -5
- package/providers/beta/openlayers/src/utils/hoverCursor.test.js +2 -1
- package/providers/beta/openlayers/src/utils/queryFeatures.js +9 -6
- package/providers/beta/openlayers/src/utils/queryFeatures.test.js +6 -3
- package/providers/beta/openlayers/src/utils/spatial.js +6 -15
- package/providers/beta/openlayers/src/utils/spatial.test.js +3 -9
- package/providers/beta/openlayers/src/utils/tileLayers.js +5 -0
- package/providers/beta/openlayers/src/utils/tileLayers.test.js +1 -1
- package/src/App/components/Attributions/Attributions.jsx +3 -1
- package/src/App/components/Attributions/Attributions.module.scss +7 -3
- package/src/App/components/Attributions/Attributions.test.jsx +8 -0
- package/src/App/hooks/useLayoutMeasurements.js +94 -29
- package/src/App/hooks/useLayoutMeasurements.test.js +55 -10
- package/src/App/layout/Layout.jsx +7 -3
- package/src/App/layout/layout.module.scss +30 -1
- package/src/types.js +5 -0
|
@@ -10,13 +10,23 @@ jest.mock('../store/appContext.js')
|
|
|
10
10
|
jest.mock('../store/mapContext.js')
|
|
11
11
|
jest.mock('../../utils/getSafeZoneInset.js')
|
|
12
12
|
|
|
13
|
-
const el = (props = {}) => {
|
|
13
|
+
const el = ({ rect, ...props } = {}) => {
|
|
14
14
|
const e = document.createElement('div')
|
|
15
15
|
e.style.setProperty = jest.fn()
|
|
16
16
|
Object.entries(props).forEach(([k, v]) => Object.defineProperty(e, k, { value: v, configurable: true }))
|
|
17
|
+
if (rect) {
|
|
18
|
+
e.getBoundingClientRect = () => ({ top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0, ...rect })
|
|
19
|
+
}
|
|
17
20
|
return e
|
|
18
21
|
}
|
|
19
22
|
|
|
23
|
+
// For firstElementChild-based reads (logo column width, attribution text natural width).
|
|
24
|
+
const appendChild = (parent, props = {}) => {
|
|
25
|
+
const child = el(props)
|
|
26
|
+
parent.appendChild(child)
|
|
27
|
+
return child
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
const refs = (o = {}) => ({
|
|
21
31
|
appContainerRef: { current: o.appContainer || el() },
|
|
22
32
|
mainRef: { current: o.main === null ? null : el({ offsetHeight: 500, ...o.main }) },
|
|
@@ -105,19 +115,19 @@ describe('useLayoutMeasurements', () => {
|
|
|
105
115
|
|
|
106
116
|
test.each([
|
|
107
117
|
[
|
|
108
|
-
'no actions (height 0) —
|
|
118
|
+
'no actions (height 0) — clears the bottom row',
|
|
109
119
|
{ main: { offsetHeight: 500 }, bottom: { offsetTop: 400 }, actions: { offsetTop: 450, offsetHeight: 0 } },
|
|
110
|
-
'
|
|
120
|
+
'108px' // clearsBottomRow = 500 - 400 + dividerGap (8) = 108
|
|
111
121
|
],
|
|
112
122
|
[
|
|
113
|
-
'actions floating above
|
|
123
|
+
'actions floating above the row (tablet/desktop) — uses actionsOffset + dividerGap',
|
|
114
124
|
{ main: { offsetHeight: 500 }, bottom: { offsetTop: 440, offsetHeight: 20 }, actions: { offsetTop: 408, offsetHeight: 60 } },
|
|
115
|
-
'100px' // actionsOffset = 500 - 408 = 92, 92 + 8 = 100 >
|
|
125
|
+
'100px' // actionsOffset = 500 - 408 = 92, 92 + 8 = 100 > clearsBottomRow (68)
|
|
116
126
|
],
|
|
117
127
|
[
|
|
118
|
-
'
|
|
128
|
+
'row clearance already larger than actionsOffset (mobile in-flow) — row clearance wins',
|
|
119
129
|
{ main: { offsetHeight: 500 }, bottom: { offsetTop: 350 }, actions: { offsetTop: 430, offsetHeight: 40 } },
|
|
120
|
-
'
|
|
130
|
+
'158px' // clearsBottomRow = 500 - 350 + 8 = 158 > actionsOffset (70) + dividerGap (8) = 78
|
|
121
131
|
]
|
|
122
132
|
])('calculates --hint-bottom for %s', (_, refOverrides, expected) => {
|
|
123
133
|
const { layoutRefs } = setup({ refs: refOverrides })
|
|
@@ -129,8 +139,8 @@ describe('useLayoutMeasurements', () => {
|
|
|
129
139
|
const { layoutRefs } = setup()
|
|
130
140
|
layoutRefs.actionsRef.current = null
|
|
131
141
|
renderHook(() => useLayoutMeasurements())
|
|
132
|
-
// actionsHeight = 0, falls back to
|
|
133
|
-
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--hint-bottom', '
|
|
142
|
+
// actionsHeight = 0, falls back to clearsBottomRow = 500 - 400 + dividerGap (8) = 108
|
|
143
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--hint-bottom', '108px')
|
|
134
144
|
})
|
|
135
145
|
|
|
136
146
|
test.each([
|
|
@@ -200,6 +210,41 @@ describe('useLayoutMeasurements', () => {
|
|
|
200
210
|
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--left-offset-top', '48px')
|
|
201
211
|
})
|
|
202
212
|
|
|
213
|
+
test('keeps attributions docked beside the logo when the natural text width fits', () => {
|
|
214
|
+
const { layoutRefs } = setup({ refs: { bottom: { rect: { left: 0 } } } })
|
|
215
|
+
appendChild(layoutRefs.bottomRef.current, {}) // logo column
|
|
216
|
+
appendChild(layoutRefs.bottomRef.current, { offsetWidth: 192, rect: { left: 115 } }) // bottom-right column
|
|
217
|
+
layoutRefs.bottomRef.current.appendChild(layoutRefs.attributionsRef.current) // sibling of both columns
|
|
218
|
+
appendChild(layoutRefs.attributionsRef.current, { scrollWidth: 150 }) // attribution text, fits
|
|
219
|
+
renderHook(() => useLayoutMeasurements())
|
|
220
|
+
expect(layoutRefs.attributionsRef.current.classList.contains('im-o-app__attributions--stacked')).toBe(false)
|
|
221
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--attributions-left', '115px')
|
|
222
|
+
// docked: attributions.offsetHeight (16) + dividerGap (8) - primaryGap (8) = 16 — the
|
|
223
|
+
// bled attribution creeps 16px back up into the row, so bottom-right needs pushing clear
|
|
224
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--bottom-right-clearance', '16px')
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
test('stacks attributions onto their own row when the text is wider than the space beside the logo', () => {
|
|
228
|
+
const { layoutRefs } = setup({ refs: { bottom: { rect: { left: 0 } } } })
|
|
229
|
+
appendChild(layoutRefs.bottomRef.current, {}) // logo column
|
|
230
|
+
appendChild(layoutRefs.bottomRef.current, { offsetWidth: 192, rect: { left: 115 } }) // bottom-right column
|
|
231
|
+
layoutRefs.bottomRef.current.appendChild(layoutRefs.attributionsRef.current) // sibling of both columns
|
|
232
|
+
appendChild(layoutRefs.attributionsRef.current, { scrollWidth: 250 }) // attribution text, too wide to fit
|
|
233
|
+
renderHook(() => useLayoutMeasurements())
|
|
234
|
+
expect(layoutRefs.attributionsRef.current.classList.contains('im-o-app__attributions--stacked')).toBe(true)
|
|
235
|
+
// stacked already has its own clearance (the stacked rule's own margin-top), so none needed here
|
|
236
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--bottom-right-clearance', '0px')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test('never gives bottom-right negative clearance when attributions is shorter than the bleed', () => {
|
|
240
|
+
const { layoutRefs } = setup({ refs: { bottom: { rect: { left: 0 } }, attributions: { offsetHeight: 0 } } })
|
|
241
|
+
appendChild(layoutRefs.bottomRef.current, {}) // logo column
|
|
242
|
+
appendChild(layoutRefs.bottomRef.current, { offsetWidth: 192, rect: { left: 115 } }) // bottom-right column
|
|
243
|
+
layoutRefs.bottomRef.current.appendChild(layoutRefs.attributionsRef.current) // sibling of both columns
|
|
244
|
+
renderHook(() => useLayoutMeasurements())
|
|
245
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--bottom-right-clearance', '0px')
|
|
246
|
+
})
|
|
247
|
+
|
|
203
248
|
test('uses 0 when bottomRightRef current is null', () => {
|
|
204
249
|
const { layoutRefs } = setup()
|
|
205
250
|
layoutRefs.bottomRightRef.current = null
|
|
@@ -324,7 +369,7 @@ describe('useLayoutMeasurements', () => {
|
|
|
324
369
|
const { layoutRefs } = setup()
|
|
325
370
|
renderHook(() => useLayoutMeasurements())
|
|
326
371
|
expect(useResizeObserver).toHaveBeenCalledWith(
|
|
327
|
-
[layoutRefs.bannerRef, layoutRefs.mainRef, layoutRefs.headerRef, layoutRefs.topRef, layoutRefs.topLeftColRef, layoutRefs.topRightColRef, layoutRefs.actionsRef, layoutRefs.bottomRef, layoutRefs.bottomRightRef, layoutRefs.leftTopRef, layoutRefs.leftBottomRef, layoutRefs.rightTopRef, layoutRefs.rightBottomRef, layoutRefs.drawerRef, layoutRefs.leftRef, layoutRefs.rightRef],
|
|
372
|
+
[layoutRefs.bannerRef, layoutRefs.mainRef, layoutRefs.headerRef, layoutRefs.topRef, layoutRefs.topLeftColRef, layoutRefs.topRightColRef, layoutRefs.actionsRef, layoutRefs.bottomRef, layoutRefs.bottomRightRef, layoutRefs.attributionsRef, layoutRefs.leftTopRef, layoutRefs.leftBottomRef, layoutRefs.rightTopRef, layoutRefs.rightBottomRef, layoutRefs.drawerRef, layoutRefs.leftRef, layoutRefs.rightRef],
|
|
328
373
|
expect.any(Function)
|
|
329
374
|
)
|
|
330
375
|
layoutRefs.appContainerRef.current.style.setProperty.mockClear()
|
|
@@ -97,9 +97,13 @@ export const Layout = () => {
|
|
|
97
97
|
<div className='im-o-app__bottom-right' ref={layoutRefs.bottomRightRef}>
|
|
98
98
|
<SlotRenderer slot={layoutSlots.BOTTOM_RIGHT} />
|
|
99
99
|
</div>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
</div>
|
|
101
|
+
{/* A sibling of both bottom-cols (not nested in one) so a stacked attribution
|
|
102
|
+
(useLayoutMeasurements) can wrap onto its own full row and genuinely grow
|
|
103
|
+
.im-o-app__bottom, pushing the logo up — nesting it in a column can't do that,
|
|
104
|
+
since only .im-o-app__bottom's own flex-wrap can force a new line. */}
|
|
105
|
+
<div className='im-o-app__attributions' ref={layoutRefs.attributionsRef}>
|
|
106
|
+
<Attributions />
|
|
103
107
|
</div>
|
|
104
108
|
</div>
|
|
105
109
|
<div className='im-o-app__drawer' ref={layoutRefs.drawerRef}>
|
|
@@ -261,7 +261,9 @@
|
|
|
261
261
|
// ---------------------------------------------------
|
|
262
262
|
|
|
263
263
|
.im-o-app__bottom {
|
|
264
|
+
position: relative; // anchor for the docked (absolutely-positioned) attributions
|
|
264
265
|
display: flex;
|
|
266
|
+
flex-wrap: wrap; // lets a stacked attributions row wrap onto its own line
|
|
265
267
|
justify-content: space-between;
|
|
266
268
|
align-items: flex-end;
|
|
267
269
|
z-index: -2; // Support masking the viewport
|
|
@@ -279,7 +281,9 @@
|
|
|
279
281
|
gap: var(--divider-gap);
|
|
280
282
|
}
|
|
281
283
|
|
|
282
|
-
|
|
284
|
+
// :nth-child, not :last-child — .im-o-app__attributions (Layout.jsx) is a third sibling
|
|
285
|
+
// after both bottom-cols, so this is no longer literally the last child.
|
|
286
|
+
.im-o-app__bottom-col:nth-child(2) {
|
|
283
287
|
position: relative; // anchor for absolutely-positioned attributions
|
|
284
288
|
flex: 1 1 auto;
|
|
285
289
|
flex-direction: column;
|
|
@@ -297,20 +301,45 @@
|
|
|
297
301
|
|
|
298
302
|
.im-o-app__bottom-right {
|
|
299
303
|
justify-content: flex-end;
|
|
304
|
+
// Docked attributions (below) bleeds past the row's own bottom edge and can grow back up
|
|
305
|
+
// into it — --bottom-right-clearance (useLayoutMeasurements) keeps this content clear.
|
|
306
|
+
margin-bottom: var(--bottom-right-clearance, 0);
|
|
300
307
|
}
|
|
301
308
|
|
|
302
309
|
// ---------------------------------------------------
|
|
303
310
|
// Attributions:
|
|
304
311
|
// ---------------------------------------------------
|
|
312
|
+
//
|
|
313
|
+
// Docked (default): absolutely positioned against .im-o-app__bottom (not the bottom-right
|
|
314
|
+
// column it's a DOM sibling of — see Layout.jsx), so it can bleed flush into the corner
|
|
315
|
+
// past .im-o-app__main's padding on bottom/right. `left` (--attributions-left, computed in
|
|
316
|
+
// useLayoutMeasurements from the bottom-right column's own measured position) caps it to
|
|
317
|
+
// that column's width, so it never overlaps the logo — long text wraps within that width.
|
|
318
|
+
//
|
|
319
|
+
// Stacked (im-o-app__attributions--stacked, toggled in useLayoutMeasurements when the
|
|
320
|
+
// copyright text no longer fits beside the logo at its natural width): back in normal flex
|
|
321
|
+
// flow as .im-o-app__bottom's third (wrapped) item, `flex-basis` forces it onto its own
|
|
322
|
+
// full-width row below both columns — genuinely growing .im-o-app__bottom's height, which
|
|
323
|
+
// (being bottom-pinned) pushes the logo and bottom-right buttons up rather than squeezing
|
|
324
|
+
// the text into an ever-narrower column. The oversized flex-basis plus negative
|
|
325
|
+
// right/bottom/left margin bleeds that row flush to the true edges the same way the docked
|
|
326
|
+
// box's absolute offsets do above.
|
|
305
327
|
|
|
306
328
|
.im-o-app__attributions:not(:empty) {
|
|
307
329
|
position: absolute;
|
|
308
330
|
bottom: calc(var(--primary-gap) * -1);
|
|
309
331
|
right: calc(var(--primary-gap) * -1);
|
|
332
|
+
left: var(--attributions-left, 0);
|
|
310
333
|
display: flex;
|
|
311
334
|
justify-content: flex-end;
|
|
312
335
|
}
|
|
313
336
|
|
|
337
|
+
.im-o-app__attributions--stacked:not(:empty) {
|
|
338
|
+
position: static;
|
|
339
|
+
flex-basis: calc(100% + var(--primary-gap) * 2);
|
|
340
|
+
margin: var(--divider-gap) calc(var(--primary-gap) * -1) calc(var(--primary-gap) * -1) calc(var(--primary-gap) * -1);
|
|
341
|
+
}
|
|
342
|
+
|
|
314
343
|
// ---------------------------------------------------
|
|
315
344
|
// Drawer:
|
|
316
345
|
// ---------------------------------------------------
|
package/src/types.js
CHANGED
|
@@ -394,6 +394,11 @@
|
|
|
394
394
|
* @property {string} [attribution]
|
|
395
395
|
* Attribution text.
|
|
396
396
|
*
|
|
397
|
+
* @property {boolean} [showAttributionOnMobile]
|
|
398
|
+
* Some basemap providers' terms require attribution to remain visible on all devices, while
|
|
399
|
+
* others are content for it to be hidden on mobile to save space. Attribution is hidden on
|
|
400
|
+
* mobile by default; set to `true` to keep it visible on mobile for this style.
|
|
401
|
+
*
|
|
397
402
|
* @property {string} [backgroundColor]
|
|
398
403
|
* CSS background color. Allows the viewport background to matche the background layer of the style.
|
|
399
404
|
*
|