diff --git a/docs/pages/APIs/JavaScript/Main/Event-Bus-API.md b/docs/pages/APIs/JavaScript/Main/Event-Bus-API.md index d12606b35e..dd0e81f7f5 100644 --- a/docs/pages/APIs/JavaScript/Main/Event-Bus-API.md +++ b/docs/pages/APIs/JavaScript/Main/Event-Bus-API.md @@ -391,6 +391,7 @@ has already delivered — the request can resolve after a later event lands. | `map:getBounds` | none | `LatLngBounds` | Get map bounds | | `map:getZoom` | none | `number` | Get current zoom level | | `map:getMetersPerPixel` | `{ clientX, clientY }` (optional) | `number \| null` | Ground metres per CSS pixel at that page position, or near the map's bottom-left corner when omitted. Null before the map exists, while it is tilted more than 10°, or when the planet radius is not configured | +| `map:getChromeInsets` | none | `{ bottomLeft, bottomRight, bottomLeftWidth, bottomRightWidth }` | How far, in CSS pixels, the basemap's own logo and attribution reach up from the bottom edge of the map on each side (`bottomLeft`, `bottomRight`), and in from that side's edge (`…Width`). Zero where there is none. `map:chromeInsetsChange` carries the same shape whenever it changes | | `map:setView` | `{ center, zoom }` | `true` | Set map view | | `map:fitBounds` | `bounds` | `true` | Fit map to bounds | | `map:panTo` | `{ lat, lng }` | `true` | Pan map to coordinates | diff --git a/src/essence/Basics/MapEngines/Adapters/DeckGLAdapter.ts b/src/essence/Basics/MapEngines/Adapters/DeckGLAdapter.ts index 1d92d2d641..eeaf26ae53 100644 --- a/src/essence/Basics/MapEngines/Adapters/DeckGLAdapter.ts +++ b/src/essence/Basics/MapEngines/Adapters/DeckGLAdapter.ts @@ -34,8 +34,10 @@ import type { IMapEngine, ComparisonConfig, ComparisonLayout, + MapChromeInsets, MapScreenshotResult } from '../IMapEngine' +import { NO_CHROME_INSETS } from '../IMapEngine' import { MAP_ENGINE } from '../types/engine' import type { MapEngineType } from '../types/engine' import type { LatLng, LatLngLike, BoundsLike, PointLike } from '../types/geometry' @@ -453,6 +455,10 @@ export class DeckGLAdapter implements IMapEngine { */ private _comparisonResizeObserver: ResizeObserver | null = null + /** Watches the basemap's bottom control corners (logo, attribution). */ + private _chromeObserver: ResizeObserver | null = null + private _chromeInsets: MapChromeInsets = { ...NO_CHROME_INSETS } + // ── Side-by-side comparison state ───────────────────────────────────────── // The other way of splitting the viewport: rather than one camera drawn // twice and wiped between, each half gets a map of its own — basemap @@ -626,6 +632,10 @@ export class DeckGLAdapter implements IMapEngine { this._comparisonEnabled = false this._destroyComparisonSurfaces() + this._chromeObserver?.disconnect() + this._chromeObserver = null + this._chromeInsets = { ...NO_CHROME_INSETS } + if (this._isOverlayMode) { if (this._basemap) { this._basemap.off('move', this._onBasemapMove) @@ -2430,6 +2440,55 @@ export class DeckGLAdapter implements IMapEngine { // switch re-anchors the layers on the new style's labels. this._basemap.on('style.load', this._onBasemapLoad) this._basemap.on('error', this._onBasemapError) + this._watchBasemapChrome() + } + + /** + * MapLibre and Mapbox create their corner containers with the map and + * fill them later (logo, attribution), so the corners are measured each + * time they resize. + */ + private _watchBasemapChrome(): void { + this._chromeObserver?.disconnect() + this._chromeObserver = null + if (typeof ResizeObserver === 'undefined') return + const corner = (side: string) => + this._container.querySelector( + `.maplibregl-ctrl-bottom-${side}, .mapboxgl-ctrl-bottom-${side}` + ) + const left = corner('left') + const right = corner('right') + // A corner's box includes its controls' margins; an empty one has no height. + const measure = (el: HTMLElement | null, map: DOMRect) => { + const rect = el?.getBoundingClientRect() + if (!rect || rect.height === 0) return { up: 0, left: 0, right: 0 } + return { + up: Math.max(0, Math.ceil(map.bottom - rect.top)), + left: Math.max(0, Math.ceil(rect.right - map.left)), + right: Math.max(0, Math.ceil(map.right - rect.left)), + } + } + this._chromeObserver = new ResizeObserver(() => { + const map = this._container.getBoundingClientRect() + const l = measure(left, map) + const r = measure(right, map) + const next: MapChromeInsets = { + bottomLeft: l.up, + bottomRight: r.up, + bottomLeftWidth: l.left, + bottomRightWidth: r.right, + } + const prev = this._chromeInsets + if ((Object.keys(next) as (keyof MapChromeInsets)[]).every((k) => next[k] === prev[k])) return + this._chromeInsets = next + this._emitEvent('chromeinsetschange', { ...next }) + }) + if (left) this._chromeObserver.observe(left) + if (right) this._chromeObserver.observe(right) + } + + getChromeInsets(): MapChromeInsets { + return { ...this._chromeInsets } } /** diff --git a/src/essence/Basics/MapEngines/Adapters/LeafletAdapter.ts b/src/essence/Basics/MapEngines/Adapters/LeafletAdapter.ts index 429cabd265..0f2553b12a 100644 --- a/src/essence/Basics/MapEngines/Adapters/LeafletAdapter.ts +++ b/src/essence/Basics/MapEngines/Adapters/LeafletAdapter.ts @@ -7,7 +7,7 @@ * */ -import { IMapEngine, MapScreenshotResult } from '../IMapEngine' +import { IMapEngine, MapChromeInsets, MapScreenshotResult, NO_CHROME_INSETS } from '../IMapEngine' import { LatLng, LatLngLike, @@ -511,6 +511,12 @@ export default class LeafletAdapter implements IMapEngine, IMapEn } } + /** The attribution control is removed at init. The scale factor control + * Map_ adds when `mapscale` is configured is not measured. */ + getChromeInsets(): MapChromeInsets { + return { ...NO_CHROME_INSETS } + } + /** * Get the current view state */ diff --git a/src/essence/Basics/MapEngines/IMapEngine.ts b/src/essence/Basics/MapEngines/IMapEngine.ts index 2ef6c0b7b5..1f84ed79e9 100644 --- a/src/essence/Basics/MapEngines/IMapEngine.ts +++ b/src/essence/Basics/MapEngines/IMapEngine.ts @@ -29,6 +29,22 @@ export interface MapScreenshotResult { metersPerPixel?: number | null } +/** How far, in CSS pixels, the basemap's logo and attribution reach up from + * the map's bottom edge on each side, and in from that side's edge. */ +export interface MapChromeInsets { + bottomLeft: number + bottomRight: number + bottomLeftWidth: number + bottomRightWidth: number +} + +export const NO_CHROME_INSETS: Readonly = Object.freeze({ + bottomLeft: 0, + bottomRight: 0, + bottomLeftWidth: 0, + bottomRightWidth: 0, +}) + /** * Core map engine contract. * @@ -133,6 +149,9 @@ export interface IMapEngine< */ getViewState(): ViewState + /** Bottom corner chrome; `'chromeinsetschange'` is emitted when it changes. */ + getChromeInsets(): MapChromeInsets + /** * Set the full camera state. Leaflet adapter ignores bearing and pitch. */ diff --git a/src/essence/Basics/Map_/Map_.js b/src/essence/Basics/Map_/Map_.js index 7e0362d9ce..924255d47f 100644 --- a/src/essence/Basics/Map_/Map_.js +++ b/src/essence/Basics/Map_/Map_.js @@ -262,6 +262,7 @@ let Map_ = { window.mmgisAPI.provide('map:getBounds', () => engine.getBounds()), window.mmgisAPI.provide('map:getZoom', () => engine.getZoom()), window.mmgisAPI.provide('map:getMetersPerPixel', (at) => metersPerPixelAt(engine, at)), + window.mmgisAPI.provide('map:getChromeInsets', () => engine.getChromeInsets()), window.mmgisAPI.provide('map:setView', ({ center, zoom } = {}) => { if (!center) return false engine.setView(center, zoom) @@ -409,6 +410,7 @@ let Map_ = { reEmit('drawcancel', 'map:drawcancel') reEmit('move', 'map:move') reEmit('moveend', 'map:moveend') + reEmit('chromeinsetschange', 'map:chromeInsetsChange') // Pointer streams consumed by plugins (e.g. measure tools, overlays). reEmit('click', 'map:click') reEmit('mousemove', 'map:mousemove') diff --git a/src/essence/Basics/UserInterface_/UserInterfaceModern_.css b/src/essence/Basics/UserInterface_/UserInterfaceModern_.css index 3806531cf6..83afc6c633 100644 --- a/src/essence/Basics/UserInterface_/UserInterfaceModern_.css +++ b/src/essence/Basics/UserInterface_/UserInterfaceModern_.css @@ -555,13 +555,18 @@ body.ui-is-dragging .ui-modern-grid .ui-panel { padding: var(--ui-float-outer-gap); } -/* Zone positions */ +/* Zone positions. The bottom zones sit above the basemap's logo and + attribution (--ui-map-chrome-*, set by the layout). */ .ui-float-zone-top-left { top: 0; left: 0; } .ui-float-zone-top-center { top: 0; left: 50%; transform: translateX(-50%); } .ui-float-zone-top-right { top: 0; right: 0; } -.ui-float-zone-bottom-left { bottom: 0; left: 0; } -.ui-float-zone-bottom-center { bottom: 0; left: 50%; transform: translateX(-50%); } -.ui-float-zone-bottom-right { bottom: 0; right: 0; } +.ui-float-zone-bottom-left { bottom: var(--ui-map-chrome-bottom-left, 0px); left: 0; } +.ui-float-zone-bottom-center { + bottom: max(var(--ui-map-chrome-bottom-left, 0px), var(--ui-map-chrome-bottom-right, 0px)); + left: 50%; + transform: translateX(-50%); +} +.ui-float-zone-bottom-right { bottom: var(--ui-map-chrome-bottom-right, 0px); right: 0; } /* Float panels re-enable pointer events */ .ui-float-panel { diff --git a/src/essence/Basics/UserInterface_/UserInterfaceModern_.js b/src/essence/Basics/UserInterface_/UserInterfaceModern_.js index cd8595a56c..ac65f001c6 100644 --- a/src/essence/Basics/UserInterface_/UserInterfaceModern_.js +++ b/src/essence/Basics/UserInterface_/UserInterfaceModern_.js @@ -19,6 +19,40 @@ let layoutStyle = '' let toolLoadQueue = [] let cleanupLayoutListener = null let _resizeObserver = null +let cleanupChromeListener = null +let mapChromeInsets = null + +const CHROME_INSET_VARS = { + bottomLeft: '--ui-map-chrome-bottom-left', + bottomRight: '--ui-map-chrome-bottom-right', +} + +// Lifts the bottom float zones above the basemap's logo and attribution. The +// chrome is measured from the map's edges. A zone needs no lift for chrome +// that sits beside it, under a side panel, or the part under a bottom panel. +const _applyMapChromeInsets = () => { + const center = document.getElementById('ui-modern-center') + const map = document.getElementById('map') + const lifts = { bottomLeft: 0, bottomRight: 0 } + if (center && map && mapChromeInsets) { + const m = map.getBoundingClientRect() + const c = center.getBoundingClientRect() + const covered = Math.max(0, m.bottom - c.bottom) + if (c.left < m.left + (mapChromeInsets.bottomLeftWidth || 0)) + lifts.bottomLeft = Math.max(0, mapChromeInsets.bottomLeft - covered) + if (c.right > m.right - (mapChromeInsets.bottomRightWidth || 0)) + lifts.bottomRight = Math.max(0, mapChromeInsets.bottomRight - covered) + } + Object.entries(CHROME_INSET_VARS).forEach(([side, name]) => { + document.documentElement.style.setProperty(name, `${lifts[side]}px`) + }) +} + +const _setMapChromeInsets = (insets) => { + if (!insets) return + mapChromeInsets = insets + _applyMapChromeInsets() +} // --- DOM Builder Helpers --- const _createPanelIconTray = (panel) => { @@ -313,11 +347,27 @@ const UserInterfaceModern_ = { cleanupLayoutListener = mmgisAPI.on('panels:changed', this.syncDOMState.bind(this)) } + if (!cleanupChromeListener) { + cleanupChromeListener = mmgisAPI.on( + 'map:chromeInsetsChange', + _setMapChromeInsets + ) + } + // The map may have reported its chrome before this listener existed. + if (mmgisAPI.hasHandler('map:getChromeInsets')) { + mmgisAPI + .request('map:getChromeInsets') + .then(_setMapChromeInsets) + .catch(() => {}) + } + _applyMapChromeInsets() + // Set up ResizeObserver to dispatch resize events when the center map area changes size if (_resizeObserver) { _resizeObserver.disconnect() } _resizeObserver = new ResizeObserver(() => { + _applyMapChromeInsets() window.dispatchEvent(new Event('resize')) }) const centerElement = document.getElementById('ui-modern-center') @@ -340,6 +390,14 @@ const UserInterfaceModern_ = { _resizeObserver.disconnect() _resizeObserver = null } + if (cleanupChromeListener) { + cleanupChromeListener() + cleanupChromeListener = null + } + mapChromeInsets = null + Object.values(CHROME_INSET_VARS).forEach((name) => + document.documentElement.style.removeProperty(name) + ) $('#modern-content').empty() panels = [] mmgisAPI_._pluginController = null @@ -870,4 +928,4 @@ const UserInterfaceModern_ = { } export default UserInterfaceModern_ -export { _createPanelHeader, _createPanelIconTray, _renderRegion } +export { _createPanelHeader, _createPanelIconTray, _renderRegion, _setMapChromeInsets } diff --git a/tests/unit/deckGLOverlayChrome.spec.js b/tests/unit/deckGLOverlayChrome.spec.js new file mode 100644 index 0000000000..ee7251c4a2 --- /dev/null +++ b/tests/unit/deckGLOverlayChrome.spec.js @@ -0,0 +1,208 @@ +import { test, expect, vi, beforeEach } from 'vitest' + +// What the deck.gl engine's overlay mode tells the layout and a live scale +// bar: the 'move' stream while the camera moves, and how far the basemap's +// logo and attribution reach up from the bottom. The GL constructors are +// mocked (jsdom has no WebGL); the basemap mock holds a settable camera. + +const basemaps = [] + +vi.mock('@deck.gl/core', async (importOriginal) => { + const actual = await importOriginal() + class MockDeck { + constructor(props) { + this.props = props + this.canvas = makeCanvas() + } + setProps() {} + redraw() {} + finalize() {} + getCanvas() { + return this.canvas + } + } + return { ...actual, Deck: MockDeck } +}) + +vi.mock('maplibre-gl', () => { + class MockMap { + constructor(opts) { + this.handlers = {} + // MapLibre builds its control corners with the map. + for (const side of ['left', 'right']) { + const corner = document.createElement('div') + corner.className = `maplibregl-ctrl-bottom-${side}` + opts.container.appendChild(corner) + } + this.center = { lat: 0, lng: 0 } + this.zoom = 0 + this.canvas = makeCanvas() + basemaps.push(this) + } + addControl() {} + on(event, handler) { + ;(this.handlers[event] ||= []).push(handler) + } + once(event, handler) { + this.on(event, handler) + } + off() {} + fire(event) { + for (const h of this.handlers[event] || []) h() + } + triggerRepaint() { + const pending = this.handlers.render || [] + this.handlers.render = [] + for (const h of pending) h() + } + setMaxBounds() {} + remove() {} + getCenter() { + return this.center + } + getZoom() { + return this.zoom + } + getBearing() { + return 0 + } + getPitch() { + return 0 + } + getCanvas() { + return this.canvas + } + } + class MockPopup {} + return { + Map: MockMap, + Popup: MockPopup, + default: { Map: MockMap, Popup: MockPopup }, + } +}) + +function makeCanvas() { + return { + width: 800, + height: 400, + toBlob: (cb) => cb(new Blob(['png'], { type: 'image/png' })), + } +} + +const { DeckGLAdapter } = await import( + '../../src/essence/Basics/MapEngines/Adapters/DeckGLAdapter.ts' +) +const { NO_CHROME_INSETS } = await import('../../src/essence/Basics/MapEngines/IMapEngine.ts') + +function makeContainer() { + let el = document.getElementById('map') + if (!el) { + el = document.createElement('div') + el.id = 'map' + document.body.appendChild(el) + } + return el +} + +beforeEach(() => { + basemaps.length = 0 + makeContainer() +}) + +const standalone = (center, zoom) => { + const adapter = new DeckGLAdapter() + adapter.init({ containerId: 'map', zoom, center }) + return adapter +} + +const overlay = () => { + const adapter = new DeckGLAdapter() + adapter.init({ + containerId: 'map', + zoom: 0, + center: { lat: 0, lng: 0 }, + basemap: { provider: 'maplibre', style: 'https://example/style.json' }, + }) + return { adapter, basemap: basemaps[0] } +} + +test.describe('DeckGLAdapter overlay move events', () => { + // A live scale bar follows the camera while it moves, not only when the + // gesture ends. + test("each basemap move reaches the engine's 'move' listeners", () => { + const { adapter, basemap } = overlay() + const moves = [] + adapter.on('move', (e) => moves.push(e)) + basemap.center = { lat: 10, lng: 20 } + basemap.zoom = 7 + basemap.fire('move') + expect(moves).toHaveLength(1) + expect(moves[0]).toMatchObject({ latitude: 10, longitude: 20, zoom: 7 }) + }) +}) + +test.describe('DeckGLAdapter basemap chrome', () => { + // A ResizeObserver that runs its callback on demand, standing in for the + // browser's (jsdom has none). + let observers + beforeEach(() => { + observers = [] + globalThis.ResizeObserver = class { + constructor(cb) { + this.cb = cb + this.targets = [] + observers.push(this) + } + observe(el) { + this.targets.push(el) + } + disconnect() { + this.targets = [] + } + } + }) + + const rect = (el, top, bottom, left = 0, right = 800) => { + el.getBoundingClientRect = () => ({ top, bottom, left, right, height: bottom - top }) + } + + test('reports how far the logo and attribution reach up from the bottom', () => { + const container = makeContainer() + container.innerHTML = '' + const { adapter } = overlay() + rect(container, 0, 600) + const left = container.querySelector('.maplibregl-ctrl-bottom-left') + const right = container.querySelector('.maplibregl-ctrl-bottom-right') + expect(observers[0].targets).toEqual([left, right]) + + const changes = [] + adapter.on('chromeinsetschange', (e) => changes.push(e)) + rect(left, 569, 600, 0, 97.5) + rect(right, 578.5, 600, 580, 800) + observers[0].cb() + const insets = { bottomLeft: 31, bottomRight: 22, bottomLeftWidth: 98, bottomRightWidth: 220 } + expect(adapter.getChromeInsets()).toEqual(insets) + expect(changes).toEqual([insets]) + + // A resize that leaves the rounded reach unchanged is not announced. + rect(left, 569.2, 600, 0, 97.6) + observers[0].cb() + expect(changes).toHaveLength(1) + }) + + test('an empty corner reaches nowhere', () => { + const container = makeContainer() + container.innerHTML = '' + const { adapter } = overlay() + rect(container, 0, 600) + rect(container.querySelector('.maplibregl-ctrl-bottom-left'), 600, 600) + rect(container.querySelector('.maplibregl-ctrl-bottom-right'), 600, 600) + observers[0].cb() + expect(adapter.getChromeInsets()).toEqual(NO_CHROME_INSETS) + }) + + test('standalone mode has no basemap chrome', () => { + expect(standalone({ lat: 0, lng: 0 }, 2).getChromeInsets()).toEqual(NO_CHROME_INSETS) + expect(observers).toHaveLength(0) + }) +}) diff --git a/tests/unit/deckGLOverlayEvents.spec.js b/tests/unit/deckGLOverlayEvents.spec.js deleted file mode 100644 index 003517624d..0000000000 --- a/tests/unit/deckGLOverlayEvents.spec.js +++ /dev/null @@ -1,128 +0,0 @@ -import { test, expect, vi, beforeEach } from 'vitest' - -// The deck.gl engine's overlay mode reports the 'move' stream while the -// camera moves. The GL constructors are mocked (jsdom has no WebGL); the -// basemap mock holds a settable camera. - -const basemaps = [] - -vi.mock('@deck.gl/core', async (importOriginal) => { - const actual = await importOriginal() - class MockDeck { - constructor(props) { - this.props = props - this.canvas = makeCanvas() - } - setProps() {} - redraw() {} - finalize() {} - getCanvas() { - return this.canvas - } - } - return { ...actual, Deck: MockDeck } -}) - -vi.mock('maplibre-gl', () => { - class MockMap { - constructor() { - this.handlers = {} - this.center = { lat: 0, lng: 0 } - this.zoom = 0 - this.canvas = makeCanvas() - basemaps.push(this) - } - addControl() {} - on(event, handler) { - ;(this.handlers[event] ||= []).push(handler) - } - once(event, handler) { - this.on(event, handler) - } - off() {} - fire(event) { - for (const h of this.handlers[event] || []) h() - } - triggerRepaint() { - const pending = this.handlers.render || [] - this.handlers.render = [] - for (const h of pending) h() - } - setMaxBounds() {} - remove() {} - getCenter() { - return this.center - } - getZoom() { - return this.zoom - } - getBearing() { - return 0 - } - getPitch() { - return 0 - } - getCanvas() { - return this.canvas - } - } - class MockPopup {} - return { - Map: MockMap, - Popup: MockPopup, - default: { Map: MockMap, Popup: MockPopup }, - } -}) - -function makeCanvas() { - return { - width: 800, - height: 400, - toBlob: (cb) => cb(new Blob(['png'], { type: 'image/png' })), - } -} - -const { DeckGLAdapter } = await import( - '../../src/essence/Basics/MapEngines/Adapters/DeckGLAdapter.ts' -) - -function makeContainer() { - let el = document.getElementById('map') - if (!el) { - el = document.createElement('div') - el.id = 'map' - document.body.appendChild(el) - } - return el -} - -beforeEach(() => { - basemaps.length = 0 - makeContainer() -}) - -const overlay = () => { - const adapter = new DeckGLAdapter() - adapter.init({ - containerId: 'map', - zoom: 0, - center: { lat: 0, lng: 0 }, - basemap: { provider: 'maplibre', style: 'https://example/style.json' }, - }) - return { adapter, basemap: basemaps[0] } -} - -test.describe('DeckGLAdapter overlay move events', () => { - // A live scale bar follows the camera while it moves, not only when the - // gesture ends. - test("each basemap move reaches the engine's 'move' listeners", () => { - const { adapter, basemap } = overlay() - const moves = [] - adapter.on('move', (e) => moves.push(e)) - basemap.center = { lat: 10, lng: 20 } - basemap.zoom = 7 - basemap.fire('move') - expect(moves).toHaveLength(1) - expect(moves[0]).toMatchObject({ latitude: 10, longitude: 20, zoom: 7 }) - }) -}) diff --git a/tests/unit/userInterfaceModern.mapChrome.spec.js b/tests/unit/userInterfaceModern.mapChrome.spec.js new file mode 100644 index 0000000000..a16d354560 --- /dev/null +++ b/tests/unit/userInterfaceModern.mapChrome.spec.js @@ -0,0 +1,62 @@ +import { test, expect, vi, afterEach } from 'vitest' +vi.mock('../../src/essence/Basics/Viewer_/Viewer_', () => ({ default: {} })) + +// The bottom float zones are lifted by CSS variables the layout sets from the +// map's reported chrome. jsdom does no layout, so the map and centre region +// are given the rectangles a real page would have. + +const { _setMapChromeInsets } = await import( + '../../src/essence/Basics/UserInterface_/UserInterfaceModern_.js' +) + +const box = (id, bottom, left = 0, right = 800) => { + const el = document.createElement('div') + el.id = id + el.getBoundingClientRect = () => ({ top: 0, bottom, left, right, height: bottom, width: right - left }) + document.body.appendChild(el) + return el +} + +const lift = (side) => + document.documentElement.style.getPropertyValue(`--ui-map-chrome-bottom-${side}`) + +afterEach(() => { + document.body.innerHTML = '' + document.documentElement.removeAttribute('style') +}) + +test('zones rise by the full chrome when the map and centre share a bottom edge', () => { + box('map', 600) + box('ui-modern-center', 600) + _setMapChromeInsets({ bottomLeft: 31, bottomRight: 18, bottomLeftWidth: 98, bottomRightWidth: 40 }) + expect(lift('left')).toBe('31px') + expect(lift('right')).toBe('18px') +}) + +// A bottom panel laid over the map already covers the lower part of the +// chrome; only what reaches above the centre region needs clearing. +test('chrome hidden under a panel overlaying the map is not cleared again', () => { + box('map', 600) + box('ui-modern-center', 580) + _setMapChromeInsets({ bottomLeft: 31, bottomRight: 12, bottomLeftWidth: 98, bottomRightWidth: 40 }) + expect(lift('left')).toBe('11px') + expect(lift('right')).toBe('0px') +}) + +test('a map with no bottom chrome leaves the zones on the edge', () => { + box('map', 600) + box('ui-modern-center', 600) + _setMapChromeInsets({ bottomLeft: 0, bottomRight: 0, bottomLeftWidth: 0, bottomRightWidth: 0 }) + expect(lift('left')).toBe('0px') + expect(lift('right')).toBe('0px') +}) + +// In the overlay layout the map spans the page behind the side panels, so a +// corner's chrome can sit under a panel, beside the centre region. +test('chrome beside the centre region, under a side panel, is not cleared', () => { + box('map', 600) + box('ui-modern-center', 600, 300, 760) + _setMapChromeInsets({ bottomLeft: 31, bottomRight: 22, bottomLeftWidth: 98, bottomRightWidth: 220 }) + expect(lift('left')).toBe('0px') + expect(lift('right')).toBe('22px') +})