diff --git a/src/css/mapbox-gl.css b/src/css/mapbox-gl.css index e12df2d0c21..17e5ed318b3 100644 --- a/src/css/mapbox-gl.css +++ b/src/css/mapbox-gl.css @@ -135,6 +135,7 @@ padding: 0; } +.mapboxgl-ctrl-attrib-button:focus, .mapboxgl-ctrl-group button:focus { box-shadow: 0 0 2px 2px rgba(0, 150, 255, 1); } @@ -440,35 +441,30 @@ a.mapboxgl-ctrl-logo.mapboxgl-compact { @media screen { .mapboxgl-ctrl-attrib.mapboxgl-compact { min-height: 20px; - padding: 0; + padding: 2px 24px 2px 0; margin: 10px; position: relative; background-color: #fff; - border-radius: 3px 12px 12px 3px; + border-radius: 12px; } - .mapboxgl-ctrl-attrib.mapboxgl-compact:hover { - padding: 2px 24px 2px 4px; + .mapboxgl-ctrl-attrib.mapboxgl-compact-show { + padding: 2px 28px 2px 8px; visibility: visible; - margin-top: 6px; } - .mapboxgl-ctrl-top-left > .mapboxgl-ctrl-attrib.mapboxgl-compact:hover, - .mapboxgl-ctrl-bottom-left > .mapboxgl-ctrl-attrib.mapboxgl-compact:hover { - padding: 2px 4px 2px 24px; - border-radius: 12px 3px 3px 12px; + .mapboxgl-ctrl-top-left > .mapboxgl-ctrl-attrib.mapboxgl-compact-show, + .mapboxgl-ctrl-bottom-left > .mapboxgl-ctrl-attrib.mapboxgl-compact-show { + padding: 2px 8px 2px 28px; + border-radius: 12px; } .mapboxgl-ctrl-attrib.mapboxgl-compact .mapboxgl-ctrl-attrib-inner { display: none; } - .mapboxgl-ctrl-attrib.mapboxgl-compact:hover .mapboxgl-ctrl-attrib-inner { - display: block; - } - - .mapboxgl-ctrl-attrib.mapboxgl-compact::after { - content: ''; + .mapboxgl-ctrl-attrib-button { + display: none; cursor: pointer; position: absolute; background-image: svg-load('svg/mapboxgl-ctrl-attrib.svg'); @@ -477,6 +473,24 @@ a.mapboxgl-ctrl-logo.mapboxgl-compact { height: 24px; box-sizing: border-box; border-radius: 12px; + outline: none; + top: 0; + right: 0; + border: 0; + } + + .mapboxgl-ctrl-top-left .mapboxgl-ctrl-attrib-button, + .mapboxgl-ctrl-bottom-left .mapboxgl-ctrl-attrib-button { + left: 0; + } + + .mapboxgl-ctrl-attrib.mapboxgl-compact .mapboxgl-ctrl-attrib-button, + .mapboxgl-ctrl-attrib.mapboxgl-compact-show .mapboxgl-ctrl-attrib-inner { + display: block; + } + + .mapboxgl-ctrl-attrib.mapboxgl-compact-show .mapboxgl-ctrl-attrib-button { + background-color: rgba(0, 0, 0, 0.05); } .mapboxgl-ctrl-bottom-right > .mapboxgl-ctrl-attrib.mapboxgl-compact::after { diff --git a/src/ui/control/attribution_control.js b/src/ui/control/attribution_control.js index b057efde359..21b6a289a71 100644 --- a/src/ui/control/attribution_control.js +++ b/src/ui/control/attribution_control.js @@ -29,6 +29,7 @@ class AttributionControl { _map: Map; _container: HTMLElement; _innerContainer: HTMLElement; + _compactButton: HTMLButtonElement; _editLink: ?HTMLAnchorElement; _attribHTML: string; styleId: string; @@ -38,6 +39,7 @@ class AttributionControl { this.options = options; bindAll([ + '_toggleAttribution', '_updateEditLink', '_updateData', '_updateCompact' @@ -53,7 +55,11 @@ class AttributionControl { this._map = map; this._container = DOM.create('div', 'mapboxgl-ctrl mapboxgl-ctrl-attrib'); + this._compactButton = DOM.create('button', 'mapboxgl-ctrl-attrib-button', this._container); + this._compactButton.addEventListener('click', this._toggleAttribution); + this._setElementTitle(this._compactButton, 'ToggleAttribution'); this._innerContainer = DOM.create('div', 'mapboxgl-ctrl-attrib-inner', this._container); + this._innerContainer.setAttribute('role', 'list'); if (compact) { this._container.classList.add('mapboxgl-compact'); @@ -86,6 +92,22 @@ class AttributionControl { this._attribHTML = (undefined: any); } + _setElementTitle(element: HTMLElement, title: string) { + const str = this._map._getUIString(`AttributionControl.${title}`); + element.title = str; + element.setAttribute('aria-label', str); + } + + _toggleAttribution() { + if (this._container.classList.contains('mapboxgl-compact-show')) { + this._container.classList.remove('mapboxgl-compact-show'); + this._compactButton.setAttribute('aria-pressed', 'false'); + } else { + this._container.classList.add('mapboxgl-compact-show'); + this._compactButton.setAttribute('aria-pressed', 'true'); + } + } + _updateEditLink() { let editLink = this._editLink; if (!editLink) { @@ -93,9 +115,9 @@ class AttributionControl { } const params = [ - {key: "owner", value: this.styleOwner}, - {key: "id", value: this.styleId}, - {key: "access_token", value: this._map._requestManager._customAccessToken || config.ACCESS_TOKEN} + {key: 'owner', value: this.styleOwner}, + {key: 'id', value: this.styleId}, + {key: 'access_token', value: this._map._requestManager._customAccessToken || config.ACCESS_TOKEN} ]; if (editLink) { @@ -106,7 +128,8 @@ class AttributionControl { return acc; }, `?`); editLink.href = `${config.FEEDBACK_URL}/${paramString}${this._map._hash ? this._map._hash.getHashString(true) : ''}`; - editLink.rel = "noopener nofollow"; + editLink.rel = 'noopener nofollow'; + this._setElementTitle(editLink, 'MapFeedback'); } } @@ -180,7 +203,7 @@ class AttributionControl { if (this._map.getCanvasContainer().offsetWidth <= 640) { this._container.classList.add('mapboxgl-compact'); } else { - this._container.classList.remove('mapboxgl-compact'); + this._container.classList.remove('mapboxgl-compact', 'mapboxgl-compact-show'); } } diff --git a/src/ui/control/navigation_control.js b/src/ui/control/navigation_control.js index 743eb21f1b6..4ec8da6647b 100644 --- a/src/ui/control/navigation_control.js +++ b/src/ui/control/navigation_control.js @@ -77,8 +77,12 @@ class NavigationControl { _updateZoomButtons() { const zoom = this._map.getZoom(); - this._zoomInButton.disabled = zoom === this._map.getMaxZoom(); - this._zoomOutButton.disabled = zoom === this._map.getMinZoom(); + const isMax = zoom === this._map.getMaxZoom(); + const isMin = zoom === this._map.getMinZoom(); + this._zoomInButton.disabled = isMax; + this._zoomOutButton.disabled = isMin; + this._zoomInButton.setAttribute('aria-disabled', isMax.toString()); + this._zoomOutButton.setAttribute('aria-disabled', isMin.toString()); } _rotateCompassArrow() { diff --git a/src/ui/default_locale.js b/src/ui/default_locale.js index d5696f38695..92c548d4504 100644 --- a/src/ui/default_locale.js +++ b/src/ui/default_locale.js @@ -1,6 +1,8 @@ // @flow const defaultLocale = { + 'AttributionControl.ToggleAttribution': 'Toggle attribution', + 'AttributionControl.MapFeedback': 'Map feedback', 'FullscreenControl.Enter': 'Enter fullscreen', 'FullscreenControl.Exit': 'Exit fullscreen', 'GeolocateControl.FindMyLocation': 'Find my location', diff --git a/src/ui/map.js b/src/ui/map.js index aa5341a17e1..8ddf341b161 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -2289,6 +2289,7 @@ class Map extends Camera { this._canvas.addEventListener('webglcontextrestored', this._contextRestored, false); this._canvas.setAttribute('tabindex', '0'); this._canvas.setAttribute('aria-label', 'Map'); + this._canvas.setAttribute('role', 'region'); const dimensions = this._containerDimensions(); this._resizeCanvas(dimensions[0], dimensions[1]); diff --git a/test/unit/ui/control/attribution.test.js b/test/unit/ui/control/attribution.test.js index 3256110b533..99cd411ea52 100644 --- a/test/unit/ui/control/attribution.test.js +++ b/test/unit/ui/control/attribution.test.js @@ -2,6 +2,7 @@ import {test} from '../../../util/test'; import config from '../../../../src/util/config'; import AttributionControl from '../../../../src/ui/control/attribution_control'; import {createMap as globalCreateMap} from '../../../util'; +import simulate from '../../../util/simulate_interaction'; function createMap(t) { config.ACCESS_TOKEN = 'pk.123'; @@ -75,6 +76,28 @@ test('AttributionControl appears in compact mode if container is less then 640 p t.end(); }); +test('AttributionControl compact mode control toggles attribution', (t) => { + const map = createMap(t); + map.addControl(new AttributionControl({ + compact: true + })); + + const container = map.getContainer(); + const toggle = container.querySelector('.mapboxgl-ctrl-attrib-button'); + + t.equal(container.querySelectorAll('.mapboxgl-compact-show').length, 0); + + simulate.click(toggle); + + t.equal(container.querySelectorAll('.mapboxgl-compact-show').length, 1); + + simulate.click(toggle); + + t.equal(container.querySelectorAll('.mapboxgl-compact-show').length, 0); + + t.end(); +}); + test('AttributionControl dedupes attributions that are substrings of others', (t) => { const map = createMap(t); const attribution = new AttributionControl();