Skip to content

Commit

Permalink
Attribution ShadowDOM (#306)
Browse files Browse the repository at this point in the history
* add attribution by shadow dom
* fix css for break line
* remove mapbox css selector
  • Loading branch information
naogify authored Oct 5, 2022
1 parent 89d5021 commit eba5f22
Show file tree
Hide file tree
Showing 5 changed files with 447 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@geolonia/mbgl-geolonia-control": "^0.4.2",
"@geolonia/mbgl-gesture-handling": "^1.0.15",
"@mapbox/geojson-extent": "^0.3.2",
"@mapbox/point-geometry": "^0.1.0",
"@turf/center": "^6.0.1",
"core-js": "^3.6.5",
"intersection-observer": "^0.5.1",
Expand Down
317 changes: 317 additions & 0 deletions src/lib/CustomAttributionControl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
import { DOM, bindAll } from './maplibre-util';

/**
* This class is a copy of maplibre-gl-js's AttributionControl class and rewrite by shadow DOM.
* https://github.com/maplibre/maplibre-gl-js/blob/main/src/ui/control/attribution_control.ts
*/

class CustomAttributionControl {

constructor(options = {}) {
this.options = options;
this._map;
this._compact;
this._container;
this._shadowContainer;
this._innerContainer;
this._compactButton;
this._editLink;
this._attribHTML;
this.styleId;
this.styleOwner;

bindAll([
'_toggleAttribution',
'_updateData',
'_updateCompact',
'_updateCompactMinimize',
], this);
}

getDefaultPosition() {
return 'bottom-right';
}

onAdd(map) {
this._map = map;
this._compact = this.options && this.options.compact;
this._container = DOM.create('div');

const shadow = this._container.attachShadow({mode: 'open'});

this._shadowContainer = DOM.create('details', 'maplibregl-ctrl maplibregl-ctrl-attrib');
this._compactButton = DOM.create('summary', 'maplibregl-ctrl-attrib-button', this._shadowContainer);
this._compactButton.addEventListener('click', this._toggleAttribution);
this._setElementTitle(this._compactButton, 'ToggleAttribution');
this._innerContainer = DOM.create('div', 'maplibregl-ctrl-attrib-inner', this._shadowContainer);

const style = document.createElement('style');
style.textContent = `
.maplibregl-ctrl {
clear: both;
pointer-events: auto;
transform: translate(0);
}
.maplibregl-ctrl-attrib-button:focus,.maplibregl-ctrl-group button:focus {
box-shadow: 0 0 2px 2px #0096ff
}
.maplibregl-ctrl.maplibregl-ctrl-attrib {
background-color: hsla(0,0%,100%,.5);
margin: 0;
padding: 0 5px
}
@media screen {
.maplibregl-ctrl-attrib.maplibregl-compact {
background-color: #fff;
border-radius: 12px;
box-sizing: content-box;
margin: 10px;
min-height: 20px;
padding: 2px 24px 2px 0;
position: relative
}
.maplibregl-ctrl-attrib.maplibregl-compact-show {
padding: 2px 28px 2px 8px;
visibility: visible
}
.maplibregl-ctrl-bottom-left>.maplibregl-ctrl-attrib.maplibregl-compact-show,.maplibregl-ctrl-top-left>.maplibregl-ctrl-attrib.maplibregl-compact-show {
border-radius: 12px;
padding: 2px 8px 2px 28px
}
.maplibregl-ctrl-attrib.maplibregl-compact .maplibregl-ctrl-attrib-inner {
display: none
}
.maplibregl-ctrl-attrib-button {
background-color: hsla(0,0%,100%,.5);
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1 0 12 0 6 6 0 1 0-12 0m5-3a1 1 0 1 0 2 0 1 1 0 1 0-2 0m0 3a1 1 0 1 1 2 0v3a1 1 0 1 1-2 0'/%3E%3C/svg%3E");
border: 0;
border-radius: 12px;
box-sizing: border-box;
cursor: pointer;
display: none;
height: 24px;
outline: none;
position: absolute;
right: 0;
top: 0;
width: 24px
}
.maplibregl-ctrl-attrib summary.maplibregl-ctrl-attrib-button {
appearance: none;
list-style: none
}
.maplibregl-ctrl-attrib summary.maplibregl-ctrl-attrib-button::-webkit-details-marker {
display: none
}
.maplibregl-ctrl-bottom-left .maplibregl-ctrl-attrib-button,.maplibregl-ctrl-top-left .maplibregl-ctrl-attrib-button {
left: 0
}
.maplibregl-ctrl-attrib.maplibregl-compact .maplibregl-ctrl-attrib-button,.maplibregl-ctrl-attrib.maplibregl-compact-show .maplibregl-ctrl-attrib-inner {
display: block
}
.maplibregl-ctrl-attrib.maplibregl-compact-show .maplibregl-ctrl-attrib-button {
background-color: rgb(0 0 0/5%)
}
.maplibregl-ctrl-bottom-right>.maplibregl-ctrl-attrib.maplibregl-compact:after {
bottom: 0;
right: 0
}
.maplibregl-ctrl-top-right>.maplibregl-ctrl-attrib.maplibregl-compact:after {
right: 0;
top: 0
}
.maplibregl-ctrl-top-left>.maplibregl-ctrl-attrib.maplibregl-compact:after {
left: 0;
top: 0
}
.maplibregl-ctrl-bottom-left>.maplibregl-ctrl-attrib.maplibregl-compact:after {
bottom: 0;
left: 0
}
}
@media screen and (-ms-high-contrast:active) {
.maplibregl-ctrl-attrib.maplibregl-compact:after {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd' fill='%23fff'%3E%3Cpath d='M4 10a6 6 0 1 0 12 0 6 6 0 1 0-12 0m5-3a1 1 0 1 0 2 0 1 1 0 1 0-2 0m0 3a1 1 0 1 1 2 0v3a1 1 0 1 1-2 0'/%3E%3C/svg%3E")
}
}
@media screen and (-ms-high-contrast:black-on-white) {
.maplibregl-ctrl-attrib.maplibregl-compact:after {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1 0 12 0 6 6 0 1 0-12 0m5-3a1 1 0 1 0 2 0 1 1 0 1 0-2 0m0 3a1 1 0 1 1 2 0v3a1 1 0 1 1-2 0'/%3E%3C/svg%3E")
}
}
.maplibregl-ctrl-attrib a {
color: rgba(0,0,0,.75);
text-decoration: none;
white-space: nowrap;
}
.maplibregl-ctrl-attrib a:hover {
color: inherit;
text-decoration: underline
}
.maplibregl-attrib-empty {
display: none
}
`;

this._updateAttributions();
this._updateCompact();

this._map.on('styledata', this._updateData);
this._map.on('sourcedata', this._updateData);
this._map.on('terrain', this._updateData);
this._map.on('resize', this._updateCompact);
this._map.on('drag', this._updateCompactMinimize);

shadow.appendChild(style);
shadow.appendChild(this._shadowContainer);

return this._container;
}

onRemove() {
DOM.remove(this._container);

this._map.off('styledata', this._updateData);
this._map.off('sourcedata', this._updateData);
this._map.off('terrain', this._updateData);
this._map.off('resize', this._updateCompact);
this._map.off('drag', this._updateCompactMinimize);

this._map = undefined;
this._compact = undefined;
this._attribHTML = undefined;
}

_setElementTitle(element, title) {
const str = this._map._getUIString(`AttributionControl.${title}`);
element.title = str;
element.setAttribute('aria-label', str);
}

_toggleAttribution() {
if (this._shadowContainer.classList.contains('maplibregl-compact')) {
if (this._shadowContainer.classList.contains('maplibregl-compact-show')) {
this._shadowContainer.setAttribute('open', '');
this._shadowContainer.classList.remove('maplibregl-compact-show');
} else {
this._shadowContainer.classList.add('maplibregl-compact-show');
this._shadowContainer.removeAttribute('open');
}
}
}

_updateData(e) {
if (e && (e.sourceDataType === 'metadata' || e.sourceDataType === 'visibility' || e.dataType === 'style' || e.type === 'terrain')) {
this._updateAttributions();
}
}

_updateAttributions() {
if (!this._map.style) return;
let attributions = [];
if (this.options.customAttribution) {
if (Array.isArray(this.options.customAttribution)) {
attributions = attributions.concat(
this.options.customAttribution.map((attribution) => {
if (typeof attribution !== 'string') return '';
return attribution;
}),
);
} else if (typeof this.options.customAttribution === 'string') {
attributions.push(this.options.customAttribution);
}
}

if (this._map.style.stylesheet) {
const stylesheet = this._map.style.stylesheet;
this.styleOwner = stylesheet.owner;
this.styleId = stylesheet.id;
}

const sourceCaches = this._map.style.sourceCaches;
for (const id in sourceCaches) {
const sourceCache = sourceCaches[id];
if (sourceCache.used || sourceCache.usedForTerrain) {
const source = sourceCache.getSource();
if (source.attribution && attributions.indexOf(source.attribution) < 0) {
attributions.push(source.attribution);
}
}
}

// remove any entries that are whitespace
attributions = attributions.filter((e) => String(e).trim());

// remove any entries that are substrings of another entry.
// first sort by length so that substrings come first
attributions.sort((a, b) => a.length - b.length);
attributions = attributions.filter((attrib, i) => {
for (let j = i + 1; j < attributions.length; j++) {
if (attributions[j].indexOf(attrib) >= 0) { return false; }
}
return true;
});

// check if attribution string is different to minimize DOM changes
const attribHTML = attributions.join(' | ');
if (attribHTML === this._attribHTML) return;

this._attribHTML = attribHTML;

if (attributions.length) {
this._innerContainer.innerHTML = attribHTML;
this._shadowContainer.classList.remove('maplibregl-attrib-empty');
} else {
this._shadowContainer.classList.add('maplibregl-attrib-empty');
}
this._updateCompact();
// remove old DOM node from _editLink
this._editLink = null;
}

_updateCompact() {
if (this._map.getCanvasContainer().offsetWidth <= 640 || this._compact) {
if (this._compact === false) {
this._shadowContainer.setAttribute('open', '');
} else if (!this._shadowContainer.classList.contains('maplibregl-compact') && !this._shadowContainer.classList.contains('maplibregl-attrib-empty')) {
this._shadowContainer.setAttribute('open', '');
this._shadowContainer.classList.add('maplibregl-compact', 'maplibregl-compact-show');
}
} else {
this._shadowContainer.setAttribute('open', '');
if (this._shadowContainer.classList.contains('maplibregl-compact')) {
this._shadowContainer.classList.remove('maplibregl-compact', 'maplibregl-compact-show');
}
}
}

_updateCompactMinimize() {
if (this._shadowContainer.classList.contains('maplibregl-compact')) {
if (this._shadowContainer.classList.contains('maplibregl-compact-show')) {
this._shadowContainer.classList.remove('maplibregl-compact-show');
}
}
}

}

export default CustomAttributionControl;
3 changes: 3 additions & 0 deletions src/lib/geolonia-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'whatwg-fetch';
import 'promise-polyfill/src/polyfill';
import maplibregl from 'maplibre-gl';
import GeoloniaControl from '@geolonia/mbgl-geolonia-control';
import CustomAttributionControl from './CustomAttributionControl';
import GestureHandling from '@geolonia/mbgl-gesture-handling';
import parseAtts from './parse-atts';

Expand Down Expand Up @@ -128,6 +129,8 @@ export default class GeoloniaMap extends maplibregl.Map {
const { position: geoloniaControlPosition } = util.parseControlOption(atts.geoloniaControl);
map.addControl(new GeoloniaControl(), geoloniaControlPosition);

map.addControl(new CustomAttributionControl(), 'bottom-right');

const { enabled: fullscreenControlEnabled, position: fullscreenControlPosition } = util.parseControlOption(atts.fullscreenControl);
if (fullscreenControlEnabled) {
// IE patch for fullscreen mode
Expand Down
Loading

0 comments on commit eba5f22

Please sign in to comment.