mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-27 23:35:23 +01:00
Accessibility fixes
- Aria landmarks - Title attributes to all icon only buttons - <Multibutton/> now internally a radio group - Replaced 1 'skip navigation link' with UI group links - Added map specific shortcuts to the shortcut menu - Hidden layer list actions from tab index
This commit is contained in:
parent
e3e6647e03
commit
b28407a4a0
25 changed files with 260 additions and 74 deletions
|
@ -12,10 +12,13 @@ class Button extends React.Component {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
|
id: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <button
|
return <button
|
||||||
|
id={this.props.id}
|
||||||
|
title={this.props.title}
|
||||||
type={this.props.type}
|
type={this.props.type}
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
|
|
|
@ -131,6 +131,16 @@ export default class Toolbar extends React.Component {
|
||||||
this.props.onSetMapState(val);
|
this.props.onSetMapState(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSkip = (target) => {
|
||||||
|
if (target === "map") {
|
||||||
|
document.querySelector(".mapboxgl-canvas").focus();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const el = document.querySelector("#skip-target-"+target);
|
||||||
|
el.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const views = [
|
const views = [
|
||||||
{
|
{
|
||||||
|
@ -144,22 +154,22 @@ export default class Toolbar extends React.Component {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "filter-deuteranopia",
|
id: "filter-deuteranopia",
|
||||||
title: "Map (deuteranopia)",
|
title: "Deuteranopia color filter",
|
||||||
disabled: !colorAccessibilityFiltersEnabled,
|
disabled: !colorAccessibilityFiltersEnabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "filter-protanopia",
|
id: "filter-protanopia",
|
||||||
title: "Map (protanopia)",
|
title: "Protanopia color filter",
|
||||||
disabled: !colorAccessibilityFiltersEnabled,
|
disabled: !colorAccessibilityFiltersEnabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "filter-tritanopia",
|
id: "filter-tritanopia",
|
||||||
title: "Map (tritanopia)",
|
title: "Tritanopia color filter",
|
||||||
disabled: !colorAccessibilityFiltersEnabled,
|
disabled: !colorAccessibilityFiltersEnabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "filter-achromatopsia",
|
id: "filter-achromatopsia",
|
||||||
title: "Map (achromatopsia)",
|
title: "Achromatopsia color filter",
|
||||||
disabled: !colorAccessibilityFiltersEnabled,
|
disabled: !colorAccessibilityFiltersEnabled,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -173,23 +183,37 @@ export default class Toolbar extends React.Component {
|
||||||
<div
|
<div
|
||||||
className="maputnik-toolbar-logo-container"
|
className="maputnik-toolbar-logo-container"
|
||||||
>
|
>
|
||||||
<a className="maputnik-toolbar-skip" href="#skip-menu">
|
{/* Keyboard accessible quick links */}
|
||||||
Skip navigation
|
<button
|
||||||
</a>
|
className="maputnik-toolbar-skip"
|
||||||
<a
|
onClick={e => this.onSkip("layer-list")}
|
||||||
href="https://github.com/maputnik/editor"
|
>
|
||||||
rel="noopener noreferrer"
|
Layers list
|
||||||
target="_blank"
|
</button>
|
||||||
|
<button
|
||||||
|
className="maputnik-toolbar-skip"
|
||||||
|
onClick={e => this.onSkip("layer-editor")}
|
||||||
|
>
|
||||||
|
Layer editor
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="maputnik-toolbar-skip"
|
||||||
|
onClick={e => this.onSkip("map")}
|
||||||
|
>
|
||||||
|
Map view
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
className="maputnik-toolbar-logo"
|
className="maputnik-toolbar-logo"
|
||||||
|
tabIndex="-1"
|
||||||
>
|
>
|
||||||
<span dangerouslySetInnerHTML={{__html: logoImage}} />
|
<span dangerouslySetInnerHTML={{__html: logoImage}} />
|
||||||
<h1>
|
<h1>
|
||||||
<span className="maputnik-toolbar-name">{pkgJson.name}</span>
|
<span className="maputnik-toolbar-name">{pkgJson.name}</span>
|
||||||
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
|
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
|
||||||
</h1>
|
</h1>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="maputnik-toolbar__actions">
|
<div className="maputnik-toolbar__actions" role="navigation" aria-label="Toolbar">
|
||||||
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
|
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
|
||||||
<MdOpenInBrowser />
|
<MdOpenInBrowser />
|
||||||
<IconText>Open</IconText>
|
<IconText>Open</IconText>
|
||||||
|
@ -209,20 +233,21 @@ export default class Toolbar extends React.Component {
|
||||||
|
|
||||||
<ToolbarSelect wdKey="nav:inspect">
|
<ToolbarSelect wdKey="nav:inspect">
|
||||||
<MdFindInPage />
|
<MdFindInPage />
|
||||||
<IconText>View </IconText>
|
<label>View
|
||||||
<select
|
<select
|
||||||
className="maputnik-select"
|
className="maputnik-select"
|
||||||
onChange={(e) => this.handleSelection(e.target.value)}
|
onChange={(e) => this.handleSelection(e.target.value)}
|
||||||
value={currentView.id}
|
value={currentView.id}
|
||||||
>
|
>
|
||||||
{views.map((item) => {
|
{views.map((item) => {
|
||||||
return (
|
return (
|
||||||
<option key={item.id} value={item.id} disabled={item.disabled}>
|
<option key={item.id} value={item.id} disabled={item.disabled}>
|
||||||
{item.title}
|
{item.title}
|
||||||
</option>
|
</option>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</select>
|
</select>
|
||||||
|
</label>
|
||||||
</ToolbarSelect>
|
</ToolbarSelect>
|
||||||
|
|
||||||
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
|
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
|
||||||
|
|
|
@ -14,7 +14,7 @@ export default class DeleteStopButton extends React.Component {
|
||||||
return <Button
|
return <Button
|
||||||
className="maputnik-delete-stop"
|
className="maputnik-delete-stop"
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
title={"Remove zoom level stop."}
|
title={"Remove zoom level from stop"}
|
||||||
>
|
>
|
||||||
<MdDelete />
|
<MdDelete />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -64,6 +64,7 @@ export default class ExpressionProperty extends React.Component {
|
||||||
onClick={this.props.onUndo}
|
onClick={this.props.onUndo}
|
||||||
disabled={undoDisabled}
|
disabled={undoDisabled}
|
||||||
className="maputnik-delete-stop"
|
className="maputnik-delete-stop"
|
||||||
|
title="Revert from expression"
|
||||||
>
|
>
|
||||||
<MdUndo />
|
<MdUndo />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -72,6 +73,7 @@ export default class ExpressionProperty extends React.Component {
|
||||||
key="delete_action"
|
key="delete_action"
|
||||||
onClick={this.props.onDelete}
|
onClick={this.props.onDelete}
|
||||||
className="maputnik-delete-stop"
|
className="maputnik-delete-stop"
|
||||||
|
title="Delete expression"
|
||||||
>
|
>
|
||||||
<MdDelete />
|
<MdDelete />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -40,6 +40,7 @@ export default class FunctionButtons extends React.Component {
|
||||||
<Button
|
<Button
|
||||||
className="maputnik-make-zoom-function"
|
className="maputnik-make-zoom-function"
|
||||||
onClick={this.props.onExpressionClick}
|
onClick={this.props.onExpressionClick}
|
||||||
|
title="Convert to expression"
|
||||||
>
|
>
|
||||||
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d={mdiFunctionVariant} />
|
<path fill="currentColor" d={mdiFunctionVariant} />
|
||||||
|
@ -50,7 +51,7 @@ export default class FunctionButtons extends React.Component {
|
||||||
makeZoomButton = <Button
|
makeZoomButton = <Button
|
||||||
className="maputnik-make-zoom-function"
|
className="maputnik-make-zoom-function"
|
||||||
onClick={this.props.onZoomClick}
|
onClick={this.props.onZoomClick}
|
||||||
title={"Turn property into a zoom function to enable a map feature to change with map's zoom level."}
|
title="Convert property into a zoom function"
|
||||||
>
|
>
|
||||||
<MdFunctions />
|
<MdFunctions />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -59,7 +60,7 @@ export default class FunctionButtons extends React.Component {
|
||||||
makeDataButton = <Button
|
makeDataButton = <Button
|
||||||
className="maputnik-make-data-function"
|
className="maputnik-make-data-function"
|
||||||
onClick={this.props.onDataClick}
|
onClick={this.props.onDataClick}
|
||||||
title={"Turn property into a data function to enable a map feature to change according to data properties and the map's zoom level."}
|
title="Convert property to data function"
|
||||||
>
|
>
|
||||||
<MdInsertChart />
|
<MdInsertChart />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -194,6 +194,7 @@ export default class CombiningFilterEditor extends React.Component {
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
onClick={this.makeExpression}
|
onClick={this.makeExpression}
|
||||||
|
title="Convert to expression"
|
||||||
>
|
>
|
||||||
<svg style={{marginRight: "0.2em", width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
<svg style={{marginRight: "0.2em", width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d={mdiFunctionVariant} />
|
<path fill="currentColor" d={mdiFunctionVariant} />
|
||||||
|
@ -211,6 +212,7 @@ export default class CombiningFilterEditor extends React.Component {
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
onClick={this.makeExpression}
|
onClick={this.makeExpression}
|
||||||
|
title="Convert to expression"
|
||||||
className="maputnik-make-zoom-function"
|
className="maputnik-make-zoom-function"
|
||||||
>
|
>
|
||||||
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
||||||
|
|
|
@ -15,6 +15,7 @@ class FilterEditorBlock extends React.Component {
|
||||||
<Button
|
<Button
|
||||||
className="maputnik-delete-filter"
|
className="maputnik-delete-filter"
|
||||||
onClick={this.props.onDelete}
|
onClick={this.props.onDelete}
|
||||||
|
title="Delete filter block"
|
||||||
>
|
>
|
||||||
<MdDelete />
|
<MdDelete />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -124,10 +124,11 @@ class DeleteValueButton extends React.Component {
|
||||||
return <Button
|
return <Button
|
||||||
className="maputnik-delete-stop"
|
className="maputnik-delete-stop"
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
|
title="Remove array item"
|
||||||
>
|
>
|
||||||
<DocLabel
|
<DocLabel
|
||||||
label={<MdDelete />}
|
label={<MdDelete />}
|
||||||
doc={"Remove array entry."}
|
doc={"Remove array item."}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,17 @@ class EnumInput extends React.Component {
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
default: PropTypes.string,
|
default: PropTypes.string,
|
||||||
|
name: PropTypes.string,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
options: PropTypes.array,
|
options: PropTypes.array,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {options, value, onChange} = this.props;
|
const {options, value, onChange, name} = this.props;
|
||||||
|
|
||||||
if(options.length <= 3 && optionsLabelLength(options) <= 20) {
|
if(options.length <= 3 && optionsLabelLength(options) <= 20) {
|
||||||
return <MultiButtonInput
|
return <MultiButtonInput
|
||||||
|
name={name}
|
||||||
options={options}
|
options={options}
|
||||||
value={value || this.props.default}
|
value={value || this.props.default}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Button from '../Button'
|
||||||
|
|
||||||
class MultiButtonInput extends React.Component {
|
class MultiButtonInput extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
options: PropTypes.array.isRequired,
|
options: PropTypes.array.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
|
@ -17,19 +18,24 @@ class MultiButtonInput extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedValue = this.props.value || options[0][0]
|
const selectedValue = this.props.value || options[0][0]
|
||||||
const buttons = options.map(([val, label])=> {
|
const radios = options.map(([val, label])=> {
|
||||||
return <Button
|
return <label
|
||||||
key={val}
|
key={val}
|
||||||
onClick={e => this.props.onChange(val)}
|
className={classnames("maputnik-radio-as-button", {"maputnik-button-selected": val === selectedValue})}
|
||||||
className={classnames({"maputnik-button-selected": val === selectedValue})}
|
|
||||||
>
|
>
|
||||||
|
<input type="radio"
|
||||||
|
name={this.props.name}
|
||||||
|
onChange={e => this.props.onChange(val)}
|
||||||
|
value={val}
|
||||||
|
checked={val === selectedValue}
|
||||||
|
/>
|
||||||
{label}
|
{label}
|
||||||
</Button>
|
</label>
|
||||||
})
|
})
|
||||||
|
|
||||||
return <div className="maputnik-multibutton">
|
return <fieldset className="maputnik-multibutton">
|
||||||
{buttons}
|
{radios}
|
||||||
</div>
|
</fieldset>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -289,7 +289,9 @@ export default class LayerEditor extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="maputnik-layer-editor"
|
return <div className="maputnik-layer-editor"
|
||||||
>
|
role="main"
|
||||||
|
aria-label="Layer editor"
|
||||||
|
>
|
||||||
<header>
|
<header>
|
||||||
<div className="layer-header">
|
<div className="layer-header">
|
||||||
<h2 className="layer-header__title">
|
<h2 className="layer-header__title">
|
||||||
|
@ -301,7 +303,7 @@ export default class LayerEditor extends React.Component {
|
||||||
onSelection={handleSelection}
|
onSelection={handleSelection}
|
||||||
closeOnSelection={false}
|
closeOnSelection={false}
|
||||||
>
|
>
|
||||||
<Button className='more-menu__button'>
|
<Button id="skip-target-layer-editor" className='more-menu__button' title="Layer options">
|
||||||
<MdMoreVert className="more-menu__button__svg" />
|
<MdMoreVert className="more-menu__button__svg" />
|
||||||
</Button>
|
</Button>
|
||||||
<Menu>
|
<Menu>
|
||||||
|
|
|
@ -91,9 +91,18 @@ class LayerListContainer extends React.Component {
|
||||||
|
|
||||||
groupedLayers() {
|
groupedLayers() {
|
||||||
const groups = []
|
const groups = []
|
||||||
|
const layerIdCount = new Map();
|
||||||
|
|
||||||
for (let i = 0; i < this.props.layers.length; i++) {
|
for (let i = 0; i < this.props.layers.length; i++) {
|
||||||
|
const origLayer = this.props.layers[i];
|
||||||
const previousLayer = this.props.layers[i-1]
|
const previousLayer = this.props.layers[i-1]
|
||||||
const layer = this.props.layers[i]
|
layerIdCount.set(origLayer.id,
|
||||||
|
layerIdCount.has(origLayer.id) ? layerIdCount.get(origLayer.id) + 1 : 0
|
||||||
|
);
|
||||||
|
const layer = {
|
||||||
|
...origLayer,
|
||||||
|
key: `layers-list-${origLayer.id}-${layerIdCount.get(origLayer.id)}`,
|
||||||
|
}
|
||||||
if(previousLayer && layerPrefix(previousLayer.id) == layerPrefix(layer.id)) {
|
if(previousLayer && layerPrefix(previousLayer.id) == layerPrefix(layer.id)) {
|
||||||
const lastGroup = groups[groups.length - 1]
|
const lastGroup = groups[groups.length - 1]
|
||||||
lastGroup.push(layer)
|
lastGroup.push(layer)
|
||||||
|
@ -191,14 +200,13 @@ class LayerListContainer extends React.Component {
|
||||||
|
|
||||||
const listItems = []
|
const listItems = []
|
||||||
let idx = 0
|
let idx = 0
|
||||||
const layerIdCount = new Map();
|
|
||||||
|
|
||||||
const layersByGroup = this.groupedLayers();
|
const layersByGroup = this.groupedLayers();
|
||||||
layersByGroup.forEach(layers => {
|
layersByGroup.forEach(layers => {
|
||||||
const groupPrefix = layerPrefix(layers[0].id)
|
const groupPrefix = layerPrefix(layers[0].id)
|
||||||
if(layers.length > 1) {
|
if(layers.length > 1) {
|
||||||
const grp = <LayerListGroup
|
const grp = <LayerListGroup
|
||||||
data-wd-key={[groupPrefix, idx].join('-')}
|
data-wd-key={[groupPrefix, idx].join('-')}
|
||||||
|
aria-controls={layers.map(l => l.key).join(" ")}
|
||||||
key={`group-${groupPrefix}-${idx}`}
|
key={`group-${groupPrefix}-${idx}`}
|
||||||
title={groupPrefix}
|
title={groupPrefix}
|
||||||
isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
|
isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
|
||||||
|
@ -223,10 +231,6 @@ class LayerListContainer extends React.Component {
|
||||||
additionalProps.ref = this.selectedItemRef;
|
additionalProps.ref = this.selectedItemRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
layerIdCount.set(layer.id,
|
|
||||||
layerIdCount.has(layer.id) ? layerIdCount.get(layer.id) + 1 : 0
|
|
||||||
);
|
|
||||||
const key = `${layer.id}-${layerIdCount.get(layer.id)}`;
|
|
||||||
const listItem = <LayerListItem
|
const listItem = <LayerListItem
|
||||||
className={classnames({
|
className={classnames({
|
||||||
'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex,
|
'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex,
|
||||||
|
@ -234,7 +238,7 @@ class LayerListContainer extends React.Component {
|
||||||
'maputnik-layer-list-item--error': !!layerError
|
'maputnik-layer-list-item--error': !!layerError
|
||||||
})}
|
})}
|
||||||
index={idx}
|
index={idx}
|
||||||
key={key}
|
key={layer.key}
|
||||||
layerId={layer.id}
|
layerId={layer.id}
|
||||||
layerIndex={idx}
|
layerIndex={idx}
|
||||||
layerType={layer.type}
|
layerType={layer.type}
|
||||||
|
@ -251,7 +255,12 @@ class LayerListContainer extends React.Component {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return <div className="maputnik-layer-list" ref={this.scrollContainerRef}>
|
return <div
|
||||||
|
className="maputnik-layer-list"
|
||||||
|
role="complementary"
|
||||||
|
aria-label="Layers list"
|
||||||
|
ref={this.scrollContainerRef}
|
||||||
|
>
|
||||||
<AddModal
|
<AddModal
|
||||||
layers={this.props.layers}
|
layers={this.props.layers}
|
||||||
sources={this.props.sources}
|
sources={this.props.sources}
|
||||||
|
@ -265,7 +274,7 @@ class LayerListContainer extends React.Component {
|
||||||
<div className="maputnik-default-property">
|
<div className="maputnik-default-property">
|
||||||
<div className="maputnik-multibutton">
|
<div className="maputnik-multibutton">
|
||||||
<button
|
<button
|
||||||
id="skip-menu"
|
id="skip-target-layer-list"
|
||||||
onClick={this.toggleLayers}
|
onClick={this.toggleLayers}
|
||||||
className="maputnik-button">
|
className="maputnik-button">
|
||||||
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
|
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
|
||||||
|
@ -283,7 +292,10 @@ class LayerListContainer extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<ul className="maputnik-layer-list-container">
|
<ul className="maputnik-layer-list-container"
|
||||||
|
role="navigation"
|
||||||
|
aria-label="Layers list"
|
||||||
|
>
|
||||||
{listItems}
|
{listItems}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,7 +16,13 @@ export default class LayerListGroup extends React.Component {
|
||||||
data-wd-key={"layer-list-group:"+this.props["data-wd-key"]}
|
data-wd-key={"layer-list-group:"+this.props["data-wd-key"]}
|
||||||
onClick={e => this.props.onActiveToggle(!this.props.isActive)}
|
onClick={e => this.props.onActiveToggle(!this.props.isActive)}
|
||||||
>
|
>
|
||||||
<span className="maputnik-layer-list-group-title">{this.props.title}</span>
|
<button
|
||||||
|
className="maputnik-layer-list-group-title"
|
||||||
|
aria-controls={this.props['aria-controls']}
|
||||||
|
aria-expanded={this.props.isActive}
|
||||||
|
>
|
||||||
|
{this.props.title}
|
||||||
|
</button>
|
||||||
<span className="maputnik-space" />
|
<span className="maputnik-space" />
|
||||||
<Collapser
|
<Collapser
|
||||||
style={{ height: 14, width: 14 }}
|
style={{ height: 14, width: 14 }}
|
||||||
|
|
|
@ -14,7 +14,9 @@ const DraggableLabel = SortableHandle((props) => {
|
||||||
className="layer-handle__icon"
|
className="layer-handle__icon"
|
||||||
type={props.layerType}
|
type={props.layerType}
|
||||||
/>
|
/>
|
||||||
<span className="maputnik-layer-list-item-id">{props.layerId}</span>
|
<button className="maputnik-layer-list-item-id">
|
||||||
|
{props.layerId}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -54,6 +56,7 @@ class IconAction extends React.Component {
|
||||||
className={`maputnik-layer-list-icon-action ${classAdditions}`}
|
className={`maputnik-layer-list-icon-action ${classAdditions}`}
|
||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{this.renderIcon()}
|
{this.renderIcon()}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -221,6 +221,8 @@ export default class MapboxGlMap extends React.Component {
|
||||||
if(IS_SUPPORTED) {
|
if(IS_SUPPORTED) {
|
||||||
return <div
|
return <div
|
||||||
className="maputnik-map__map"
|
className="maputnik-map__map"
|
||||||
|
role="region"
|
||||||
|
aria-label="Map view"
|
||||||
ref={x => this.container = x}
|
ref={x => this.container = x}
|
||||||
></div>
|
></div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,6 +179,8 @@ export default class OpenLayersMap extends React.Component {
|
||||||
<div
|
<div
|
||||||
className="maputnik-ol"
|
className="maputnik-ol"
|
||||||
ref={x => this.container = x}
|
ref={x => this.container = x}
|
||||||
|
role="region"
|
||||||
|
aria-label="Map view"
|
||||||
style={{
|
style={{
|
||||||
...this.props.style,
|
...this.props.style,
|
||||||
}}>
|
}}>
|
||||||
|
|
|
@ -104,7 +104,10 @@ class ExportModal extends React.Component {
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button onClick={this.downloadStyle.bind(this)}>
|
<Button
|
||||||
|
onClick={this.downloadStyle.bind(this)}
|
||||||
|
title="Download style"
|
||||||
|
>
|
||||||
<MdFileDownload />
|
<MdFileDownload />
|
||||||
Download
|
Download
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -54,6 +54,7 @@ class Modal extends React.Component {
|
||||||
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
|
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
|
||||||
<span className="maputnik-modal-header-space"></span>
|
<span className="maputnik-modal-header-space"></span>
|
||||||
<button className="maputnik-modal-header-toggle"
|
<button className="maputnik-modal-header-toggle"
|
||||||
|
title="Close modal"
|
||||||
onClick={this.onClose}
|
onClick={this.onClose}
|
||||||
data-wd-key={this.props["data-wd-key"]+".close-modal"}
|
data-wd-key={this.props["data-wd-key"]+".close-modal"}
|
||||||
>
|
>
|
||||||
|
|
|
@ -226,7 +226,7 @@ class OpenModal extends React.Component {
|
||||||
type="submit"
|
type="submit"
|
||||||
className="maputnik-big-button"
|
className="maputnik-big-button"
|
||||||
disabled={this.state.styleUrl.length < 1}
|
disabled={this.state.styleUrl.length < 1}
|
||||||
>Open URL</Button>
|
>Load from URL</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -190,6 +190,7 @@ class SettingsModal extends React.Component {
|
||||||
<InputBlock label={"Light anchor"} fieldSpec={latest.light.anchor}>
|
<InputBlock label={"Light anchor"} fieldSpec={latest.light.anchor}>
|
||||||
<EnumInput
|
<EnumInput
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
|
name="light-anchor"
|
||||||
value={light.anchor}
|
value={light.anchor}
|
||||||
options={Object.keys(latest.light.anchor.values)}
|
options={Object.keys(latest.light.anchor.values)}
|
||||||
default={latest.light.anchor.default}
|
default={latest.light.anchor.default}
|
||||||
|
|
|
@ -13,40 +13,92 @@ class ShortcutsModal extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const help = [
|
const help = [
|
||||||
{
|
{
|
||||||
key: "?",
|
key: <kbd>?</kbd>,
|
||||||
text: "Shortcuts menu"
|
text: "Shortcuts menu"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "o",
|
key: <kbd>o</kbd>,
|
||||||
text: "Open modal"
|
text: "Open modal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "e",
|
key: <kbd>e</kbd>,
|
||||||
text: "Export modal"
|
text: "Export modal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "d",
|
key: <kbd>d</kbd>,
|
||||||
text: "Data Sources modal"
|
text: "Data Sources modal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "s",
|
key: <kbd>s</kbd>,
|
||||||
text: "Style Settings modal"
|
text: "Style Settings modal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "i",
|
key: <kbd>i</kbd>,
|
||||||
text: "Toggle inspect"
|
text: "Toggle inspect"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "m",
|
key: <kbd>m</kbd>,
|
||||||
text: "Focus map"
|
text: "Focus map"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "!",
|
key: <kbd>!</kbd>,
|
||||||
text: "Debug modal"
|
text: "Debug modal"
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
const mapShortcuts = [
|
||||||
|
{
|
||||||
|
key: <kbd>+</kbd>,
|
||||||
|
text: "Increase the zoom level by 1.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: <><kbd>Shift</kbd> + <kbd>+</kbd></>,
|
||||||
|
text: "Increase the zoom level by 2.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: <kbd>-</kbd>,
|
||||||
|
text: "Decrease the zoom level by 1.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: <><kbd>Shift</kbd> + <kbd>-</kbd></>,
|
||||||
|
text: "Decrease the zoom level by 2.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: <kbd>Up</kbd>,
|
||||||
|
text: "Pan up by 100 pixels.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: <kbd>Down</kbd>,
|
||||||
|
text: "Pan down by 100 pixels.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: <kbd>Left</kbd>,
|
||||||
|
text: "Pan left by 100 pixels.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: <kbd>Right</kbd>,
|
||||||
|
text: "Pan right by 100 pixels.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: <><kbd>Shift</kbd> + <kbd>Right</kbd></>,
|
||||||
|
text: "Increase the rotation by 15 degrees.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: <><kbd>Shift</kbd> + <kbd>Left</kbd></>,
|
||||||
|
text: "Decrease the rotation by 15 degrees."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: <><kbd>Shift</kbd> + <kbd>Up</kbd></>,
|
||||||
|
text: "Increase the pitch by 10 degrees."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: <><kbd>Shift</kbd> + <kbd>Down</kbd></>,
|
||||||
|
text: "Decrease the pitch by 10 degrees."
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="shortcuts-modal"
|
data-wd-key="shortcuts-modal"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
|
@ -57,10 +109,19 @@ class ShortcutsModal extends React.Component {
|
||||||
<p>
|
<p>
|
||||||
Press <code>ESC</code> to lose focus of any active elements, then press one of:
|
Press <code>ESC</code> to lose focus of any active elements, then press one of:
|
||||||
</p>
|
</p>
|
||||||
|
<dl>
|
||||||
|
{help.map((item, idx) => {
|
||||||
|
return <div key={idx} className="maputnik-modal-shortcuts__shortcut">
|
||||||
|
<dt key={"dt"+idx}>{item.key}</dt>
|
||||||
|
<dd key={"dd"+idx}>{item.text}</dd>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</dl>
|
||||||
|
<p>If the Map is in focused you can use the following shortcuts</p>
|
||||||
<ul>
|
<ul>
|
||||||
{help.map((item) => {
|
{mapShortcuts.map((item, idx) => {
|
||||||
return <li key={item.key}>
|
return <li key={idx}>
|
||||||
<code>{item.key}</code> {item.text}
|
<span>{item.key}</span> {item.text}
|
||||||
</li>
|
</li>
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -78,3 +78,19 @@
|
||||||
width: 92.5%;
|
width: 92.5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-radio-as-button {
|
||||||
|
@extend .maputnik-button;
|
||||||
|
|
||||||
|
border: solid 1px transparent;
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border: solid 1px $color-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
&-item-handle {
|
&-item-handle {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
cursor: grab;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
@ -43,12 +44,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
|
border: solid 1px transparent;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: $color-lowgray;
|
color: $color-lowgray;
|
||||||
font-size: $font-size-6;
|
font-size: $font-size-6;
|
||||||
border-width: 0 0 1px;
|
border-bottom-color: lighten($color-black, 0.1);
|
||||||
border-style: solid;
|
|
||||||
border-color: lighten($color-black, 0.1);
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
|
@ -61,6 +61,10 @@
|
||||||
-webkit-transition: opacity 600ms, visibility 600ms;
|
-webkit-transition: opacity 600ms, visibility 600ms;
|
||||||
transition: opacity 600ms, visibility 600ms;
|
transition: opacity 600ms, visibility 600ms;
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border: solid 1px $color-lowgray;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (prefers-reduced-motion: reduce) {
|
@media screen and (prefers-reduced-motion: reduce) {
|
||||||
transition-duration: 0;
|
transition-duration: 0;
|
||||||
}
|
}
|
||||||
|
@ -131,22 +135,33 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item-id {
|
&-item-id {
|
||||||
|
all: inherit;
|
||||||
width: 115px;
|
width: 115px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-group-header {
|
&-group-header {
|
||||||
|
border: solid 1px transparent;
|
||||||
font-size: $font-size-6;
|
font-size: $font-size-6;
|
||||||
color: $color-lowgray;
|
color: $color-lowgray;
|
||||||
background-color: lighten($color-black, 2);
|
background-color: lighten($color-black, 2);
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
padding: $margin-2;
|
padding: $margin-2;
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border: solid 1px $color-lowgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
all: unset;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
@include flex-row;
|
@include flex-row;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
|
|
@ -256,17 +256,33 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-modal-shortcuts {
|
.maputnik-modal-shortcuts {
|
||||||
code {
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: 30em;
|
||||||
|
|
||||||
|
kbd {
|
||||||
color: white;
|
color: white;
|
||||||
background: #3c3c3c;
|
background: #3c3c3c;
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
margin-right: 4px;
|
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__shortcut {
|
||||||
|
margin-bottom: $margin-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
display: inline;
|
||||||
|
margin-right: $margin-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-toolbar-skip {
|
.maputnik-toolbar-skip {
|
||||||
|
all: unset;
|
||||||
|
border: solid 1px transparent;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 0px;
|
width: 0px;
|
||||||
|
@ -147,6 +149,7 @@
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
border-color: $color-lowgray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue