From b28407a4a009225fbad09084f5ec60b1cdccd899 Mon Sep 17 00:00:00 2001 From: orangemug Date: Mon, 18 May 2020 19:37:49 +0100 Subject: [PATCH] Accessibility fixes - Aria landmarks - Title attributes to all icon only buttons - 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 --- src/components/Button.jsx | 3 + src/components/Toolbar.jsx | 79 ++++++++++++------ src/components/fields/_DeleteStopButton.jsx | 2 +- src/components/fields/_ExpressionProperty.jsx | 2 + src/components/fields/_FunctionButtons.jsx | 5 +- src/components/filter/FilterEditor.jsx | 2 + src/components/filter/FilterEditorBlock.jsx | 1 + src/components/inputs/DynamicArrayInput.jsx | 3 +- src/components/inputs/EnumInput.jsx | 4 +- src/components/inputs/MultiButtonInput.jsx | 22 +++-- src/components/layers/LayerEditor.jsx | 6 +- src/components/layers/LayerList.jsx | 34 +++++--- src/components/layers/LayerListGroup.jsx | 8 +- src/components/layers/LayerListItem.jsx | 5 +- src/components/map/MapboxGlMap.jsx | 2 + src/components/map/OpenLayersMap.jsx | 2 + src/components/modals/ExportModal.jsx | 5 +- src/components/modals/Modal.jsx | 1 + src/components/modals/OpenModal.jsx | 2 +- src/components/modals/SettingsModal.jsx | 1 + src/components/modals/ShortcutsModal.jsx | 83 ++++++++++++++++--- src/styles/_filtereditor.scss | 16 ++++ src/styles/_layer.scss | 23 ++++- src/styles/_modal.scss | 20 ++++- src/styles/_toolbar.scss | 3 + 25 files changed, 260 insertions(+), 74 deletions(-) diff --git a/src/components/Button.jsx b/src/components/Button.jsx index 277e375..6e52c47 100644 --- a/src/components/Button.jsx +++ b/src/components/Button.jsx @@ -12,10 +12,13 @@ class Button extends React.Component { children: PropTypes.node, disabled: PropTypes.bool, type: PropTypes.string, + id: PropTypes.string, } render() { return + + +

{pkgJson.name} v{pkgJson.version}

-
+
-
+
Open @@ -209,20 +233,21 @@ export default class Toolbar extends React.Component { - View - + diff --git a/src/components/fields/_DeleteStopButton.jsx b/src/components/fields/_DeleteStopButton.jsx index fe98421..b282adf 100644 --- a/src/components/fields/_DeleteStopButton.jsx +++ b/src/components/fields/_DeleteStopButton.jsx @@ -14,7 +14,7 @@ export default class DeleteStopButton extends React.Component { return diff --git a/src/components/fields/_ExpressionProperty.jsx b/src/components/fields/_ExpressionProperty.jsx index 9900599..7aecd6c 100644 --- a/src/components/fields/_ExpressionProperty.jsx +++ b/src/components/fields/_ExpressionProperty.jsx @@ -64,6 +64,7 @@ export default class ExpressionProperty extends React.Component { onClick={this.props.onUndo} disabled={undoDisabled} className="maputnik-delete-stop" + title="Revert from expression" > @@ -72,6 +73,7 @@ export default class ExpressionProperty extends React.Component { key="delete_action" onClick={this.props.onDelete} className="maputnik-delete-stop" + title="Delete expression" > diff --git a/src/components/fields/_FunctionButtons.jsx b/src/components/fields/_FunctionButtons.jsx index e96b135..915a66b 100644 --- a/src/components/fields/_FunctionButtons.jsx +++ b/src/components/fields/_FunctionButtons.jsx @@ -40,6 +40,7 @@ export default class FunctionButtons extends React.Component { diff --git a/src/components/layers/LayerList.jsx b/src/components/layers/LayerList.jsx index f7bf9ef..e111a08 100644 --- a/src/components/layers/LayerList.jsx +++ b/src/components/layers/LayerList.jsx @@ -91,9 +91,18 @@ class LayerListContainer extends React.Component { groupedLayers() { const groups = [] + const layerIdCount = new Map(); + for (let i = 0; i < this.props.layers.length; i++) { + const origLayer = this.props.layers[i]; 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)) { const lastGroup = groups[groups.length - 1] lastGroup.push(layer) @@ -191,14 +200,13 @@ class LayerListContainer extends React.Component { const listItems = [] let idx = 0 - const layerIdCount = new Map(); - const layersByGroup = this.groupedLayers(); layersByGroup.forEach(layers => { const groupPrefix = layerPrefix(layers[0].id) if(layers.length > 1) { const grp = l.key).join(" ")} key={`group-${groupPrefix}-${idx}`} title={groupPrefix} isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex} @@ -223,10 +231,6 @@ class LayerListContainer extends React.Component { 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 = 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex, @@ -234,7 +238,7 @@ class LayerListContainer extends React.Component { 'maputnik-layer-list-item--error': !!layerError })} index={idx} - key={key} + key={layer.key} layerId={layer.id} layerIndex={idx} layerType={layer.type} @@ -251,7 +255,12 @@ class LayerListContainer extends React.Component { }) }) - return
+ return
-
    +
      {listItems}
diff --git a/src/components/layers/LayerListGroup.jsx b/src/components/layers/LayerListGroup.jsx index b078cae..480f6fd 100644 --- a/src/components/layers/LayerListGroup.jsx +++ b/src/components/layers/LayerListGroup.jsx @@ -16,7 +16,13 @@ export default class LayerListGroup extends React.Component { data-wd-key={"layer-list-group:"+this.props["data-wd-key"]} onClick={e => this.props.onActiveToggle(!this.props.isActive)} > - {this.props.title} + { className="layer-handle__icon" type={props.layerType} /> - {props.layerId} +
}); @@ -54,6 +56,7 @@ class IconAction extends React.Component { className={`maputnik-layer-list-icon-action ${classAdditions}`} data-wd-key={this.props.wdKey} onClick={this.props.onClick} + aria-hidden="true" > {this.renderIcon()} diff --git a/src/components/map/MapboxGlMap.jsx b/src/components/map/MapboxGlMap.jsx index e6ef90b..b1c34fb 100644 --- a/src/components/map/MapboxGlMap.jsx +++ b/src/components/map/MapboxGlMap.jsx @@ -221,6 +221,8 @@ export default class MapboxGlMap extends React.Component { if(IS_SUPPORTED) { return
this.container = x} >
} diff --git a/src/components/map/OpenLayersMap.jsx b/src/components/map/OpenLayersMap.jsx index d0a056e..5fe2942 100644 --- a/src/components/map/OpenLayersMap.jsx +++ b/src/components/map/OpenLayersMap.jsx @@ -179,6 +179,8 @@ export default class OpenLayersMap extends React.Component {
this.container = x} + role="region" + aria-label="Map view" style={{ ...this.props.style, }}> diff --git a/src/components/modals/ExportModal.jsx b/src/components/modals/ExportModal.jsx index 0caa319..cea856d 100644 --- a/src/components/modals/ExportModal.jsx +++ b/src/components/modals/ExportModal.jsx @@ -104,7 +104,10 @@ class ExportModal extends React.Component {
- diff --git a/src/components/modals/Modal.jsx b/src/components/modals/Modal.jsx index dc34754..56b9d79 100644 --- a/src/components/modals/Modal.jsx +++ b/src/components/modals/Modal.jsx @@ -54,6 +54,7 @@ class Modal extends React.Component {

{this.props.title}

+ >Load from URL
diff --git a/src/components/modals/SettingsModal.jsx b/src/components/modals/SettingsModal.jsx index b8eba02..978fe0d 100644 --- a/src/components/modals/SettingsModal.jsx +++ b/src/components/modals/SettingsModal.jsx @@ -190,6 +190,7 @@ class SettingsModal extends React.Component { ?, text: "Shortcuts menu" }, { - key: "o", + key: o, text: "Open modal" }, { - key: "e", + key: e, text: "Export modal" }, { - key: "d", + key: d, text: "Data Sources modal" }, { - key: "s", + key: s, text: "Style Settings modal" }, { - key: "i", + key: i, text: "Toggle inspect" }, { - key: "m", + key: m, text: "Focus map" }, { - key: "!", + key: !, text: "Debug modal" }, ] + const mapShortcuts = [ + { + key: +, + text: "Increase the zoom level by 1.", + }, + { + key: <>Shift + +, + text: "Increase the zoom level by 2.", + }, + { + key: -, + text: "Decrease the zoom level by 1.", + }, + { + key: <>Shift + -, + text: "Decrease the zoom level by 2.", + }, + { + key: Up, + text: "Pan up by 100 pixels.", + }, + { + key: Down, + text: "Pan down by 100 pixels.", + }, + { + key: Left, + text: "Pan left by 100 pixels.", + }, + { + key: Right, + text: "Pan right by 100 pixels.", + }, + { + key: <>Shift + Right, + text: "Increase the rotation by 15 degrees.", + }, + { + key: <>Shift + Left, + text: "Decrease the rotation by 15 degrees." + }, + { + key: <>Shift + Up, + text: "Increase the pitch by 10 degrees." + }, + { + key: <>Shift + Down, + text: "Decrease the pitch by 10 degrees." + }, + ] + + return Press ESC to lose focus of any active elements, then press one of:

+
+ {help.map((item, idx) => { + return
+
{item.key}
+
{item.text}
+
+ })} +
+

If the Map is in focused you can use the following shortcuts

    - {help.map((item) => { - return
  • - {item.key} {item.text} + {mapShortcuts.map((item, idx) => { + return
  • + {item.key} {item.text}
  • })}
diff --git a/src/styles/_filtereditor.scss b/src/styles/_filtereditor.scss index d836be9..2db3919 100644 --- a/src/styles/_filtereditor.scss +++ b/src/styles/_filtereditor.scss @@ -78,3 +78,19 @@ 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; + } +} diff --git a/src/styles/_layer.scss b/src/styles/_layer.scss index ac34bd9..394f6f1 100644 --- a/src/styles/_layer.scss +++ b/src/styles/_layer.scss @@ -36,6 +36,7 @@ &-item-handle { flex: 1; display: flex; + cursor: grab; svg { margin-right: 4px; @@ -43,12 +44,11 @@ } &-item { + border: solid 1px transparent; font-weight: 400; color: $color-lowgray; font-size: $font-size-6; - border-width: 0 0 1px; - border-style: solid; - border-color: lighten($color-black, 0.1); + border-bottom-color: lighten($color-black, 0.1); user-select: none; list-style: none; z-index: 2000; @@ -61,6 +61,10 @@ -webkit-transition: opacity 600ms, visibility 600ms; transition: opacity 600ms, visibility 600ms; + &:focus-within { + border: solid 1px $color-lowgray; + } + @media screen and (prefers-reduced-motion: reduce) { transition-duration: 0; } @@ -131,22 +135,33 @@ } &-item-id { + all: inherit; width: 115px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: inherit; text-decoration: none; + cursor: pointer; } &-group-header { + border: solid 1px transparent; font-size: $font-size-6; color: $color-lowgray; background-color: lighten($color-black, 2); - cursor: pointer; user-select: none; padding: $margin-2; + &:focus-within { + border: solid 1px $color-lowgray; + } + + button { + all: unset; + cursor: pointer; + } + @include flex-row; svg { diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss index 424a59c..5e321f2 100644 --- a/src/styles/_modal.scss +++ b/src/styles/_modal.scss @@ -256,17 +256,33 @@ } .maputnik-modal-shortcuts { - code { + position: relative; + overflow: hidden; + max-width: 30em; + + kbd { color: white; background: #3c3c3c; padding: 2px 6px; display: inline-block; text-align: center; border-radius: 2px; - margin-right: 4px; font-family: monospace; } + &__shortcut { + margin-bottom: $margin-2; + } + + dt { + display: inline; + margin-right: $margin-2; + } + + dd { + display: inline; + } + li { margin-bottom: 4px; } diff --git a/src/styles/_toolbar.scss b/src/styles/_toolbar.scss index a9e81f1..0b261eb 100644 --- a/src/styles/_toolbar.scss +++ b/src/styles/_toolbar.scss @@ -132,6 +132,8 @@ } .maputnik-toolbar-skip { + all: unset; + border: solid 1px transparent; position: absolute; overflow: hidden; width: 0px; @@ -147,6 +149,7 @@ &:active, &:focus { width: 100%; + border-color: $color-lowgray; } }