A11y: add labels to previously unlabeled buttons, fix order

This commit is contained in:
Pieter Vander Vennet 2023-12-14 18:25:35 +01:00
parent 736ab13ceb
commit fdde0aaeb3
21 changed files with 287 additions and 86 deletions

View file

@ -524,7 +524,7 @@
},
{
"id": "Reservation",
"condition": "amenity=restaurant",
"condition": "takeaway=only",
"question": {
"en": "Is a reservation required for this place?",
"nl": "Is reserveren verplicht voor deze zaak?",
@ -581,7 +581,8 @@
"cs": "Rezervace na tomto místě není možná"
}
}
]
],
"#condition": "If one can only do takeaway or deliveries, it is nonsensical to ask if a 'reservation' is possible"
},
{
"question": {
@ -731,6 +732,13 @@
"pl": "Wszystkie dania są wegetariańskie",
"cs": "Všechna jídla jsou vegetariánská"
}
},
{
"if": "diet:vegetarian=on_demand",
"then": {
"en": "Some dishes might be adapted to a vegetarian version, but this should be demanded",
"nl": "Sommige gerechten kunnen op vraag vegetarisch gemaakt worden"
}
}
],
"condition": "cuisine!=friture",
@ -798,6 +806,13 @@
"pl": "Wszystkie dania są wegańskie",
"cs": "Všechna jídla jsou veganská"
}
},
{
"if": "diet:vegan=on_demand",
"then": {
"en": "Some dishes might be adapted to a vegan version if asked for",
"nl": "Op vraag kan een veganistische variant van een gerecht gemaakt worden"
}
}
],
"condition": "cuisine!=friture",

View file

@ -28,7 +28,7 @@
{
"#": "ignore-image-in-then",
"if": "wikipedia=",
"then": "<a class='h-8' href='https://www.wikidata.org/wiki/{wikidata}' target='_blank' rel='noopener'><img src='./assets/svg/wikidata.svg' alt='WD'/></a>"
"then": "<a class='h-8' href='https://www.wikidata.org/wiki/{wikidata}' target='_blank' rel='noopener'><img src='./assets/svg/wikidata.svg' alt='Wikidata'/></a>"
}
]
},
@ -119,12 +119,32 @@
"defaults",
"in_favourite"
],
"render": "<a href='tel:{phone}'><img textmode='📞' alt='phone' src='./assets/layers/questions/phone.svg'/></a>",
"render": {
"special": {
"type": "link",
"href": "tel:{phone}",
"text": "<img textmode='📞' alt='phone' src='./assets/layers/questions/phone.svg'/>",
"arialabel": {
"en": "phone",
"nl": "Telefoneer"
}
}
},
"mappings": [
{
"#": "ignore-image-in-then",
"if": "contact:phone~*",
"then": "<a href='tel:{contact:phone}'><img textmode='📞' alt='phone' src='./assets/layers/questions/phone.svg'/></a>"
"then": {
"special": {
"type": "link",
"href": "tel:{contact:phone}",
"text": "<img textmode='📞' alt='phone' src='./assets/layers/questions/phone.svg'/>",
"arialabel": {
"en": "phone",
"nl": "Telefoneer"
}
}
}
}
],
"condition": {
@ -174,12 +194,12 @@
{
"#": "ignore-image-in-then",
"if": "smoking=no",
"then": "<img textmode='🚭️' alt='no-smoking' src='./assets/layers/questions/no_smoking.svg'/>"
"then": "<img textmode='🚭️' alt='no smoking' src='./assets/layers/questions/no_smoking.svg'/>"
},
{
"#": "ignore-image-in-then",
"if": "smoking=yes",
"then": "<img textmode='🚬️' alt='smoking-allowed' src='./assets/layers/questions/smoking.svg'/>"
"then": "<img textmode='🚬️' alt='smoking allowed' src='./assets/layers/questions/smoking.svg'/>"
}
]
},
@ -206,7 +226,17 @@
"labels": [
"defaults"
],
"render": "<a href='https://openstreetmap.org/{id}' target='_blank' rel='noopener'><img alt='on osm' textmode='🗺️' src='./assets/svg/osm-logo-us.svg'/></a>",
"render": {
"special": {
"type": "link",
"text": "<img alt='on osm' textmode='🗺️' src='./assets/svg/osm-logo-us.svg'/>",
"href": "https://openstreetmap.org/{id}",
"arialabel": {
"en": "Open on openstreetmap.org",
"nl": "Bekijk op openstreetmap.org"
}
}
},
"mappings": [
{
"if": "id~.*/-.*",
@ -215,7 +245,17 @@
{
"#": "ignore-image-in-then",
"if": "_backend~*",
"then": "<a href='{_backend}/{id}' target='_blank' rel='noopener'><img src='./assets/svg/osm-logo-us.svg'/></a>"
"then": {
"special": {
"type": "link",
"text": "<img alt='on osm' textmode='🗺️' src='./assets/svg/osm-logo-us.svg'/>",
"href": "{_backend}/{id}",
"arialabel": {
"en": "Open on openstreetmap.org",
"nl": "Bekijk op openstreetmap.org"
}
}
}
}
],
"condition": "id~(node|way|relation)/[0-9]*"

View file

@ -4721,6 +4721,9 @@
},
"3": {
"then": "All dishes are vegan"
},
"4": {
"then": "Some dishes might be adapted to a vegan version if asked for"
}
},
"question": "Does this business serve vegan meals?"
@ -4738,6 +4741,9 @@
},
"3": {
"then": "All dishes are vegetarian"
},
"4": {
"then": "Some dishes might be adapted to a vegetarian version, but this should be demanded"
}
},
"question": "Does this restaurant have a vegetarian option?"
@ -5252,7 +5258,41 @@
}
},
"icons": {
"description": "A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI"
"description": "A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI",
"tagRenderings": {
"osmlink": {
"mappings": {
"1": {
"then": {
"special": {
"arialabel": "Open on openstreetmap.org"
}
}
}
},
"render": {
"special": {
"arialabel": "Open on openstreetmap.org"
}
}
},
"phonelink": {
"mappings": {
"0": {
"then": {
"special": {
"arialabel": "phone"
}
}
}
},
"render": {
"special": {
"arialabel": "phone"
}
}
}
}
},
"indoors": {
"description": "Basic indoor mapping: shows room outlines",

View file

@ -4190,6 +4190,9 @@
},
"3": {
"then": "Enkel veganistische opties zijn beschikbaar"
},
"4": {
"then": "Op vraag kan een veganistische variant van een gerecht gemaakt worden"
}
},
"question": "Heeft deze eetgelegenheid een veganistische optie?"
@ -4207,6 +4210,9 @@
},
"3": {
"then": "Enkel vegetarische opties zijn beschikbaar"
},
"4": {
"then": "Sommige gerechten kunnen op vraag vegetarisch gemaakt worden"
}
},
"question": "Heeft deze eetgelegenheid een vegetarische optie?"
@ -4654,6 +4660,42 @@
}
}
},
"icons": {
"tagRenderings": {
"osmlink": {
"mappings": {
"1": {
"then": {
"special": {
"arialabel": "Bekijk op openstreetmap.org"
}
}
}
},
"render": {
"special": {
"arialabel": "Bekijk op openstreetmap.org"
}
}
},
"phonelink": {
"mappings": {
"0": {
"then": {
"special": {
"arialabel": "Telefoneer"
}
}
}
},
"render": {
"special": {
"arialabel": "Telefoneer"
}
}
}
}
},
"indoors": {
"description": "Een basis voor indoor-navigatie: toont binnenruimtes",
"name": "Binnenruimtes",

View file

@ -886,16 +886,6 @@ video {
margin-right: 0.25rem;
}
.mx-10 {
margin-left: 2.5rem;
margin-right: 2.5rem;
}
.my-3 {
margin-top: 0.75rem;
margin-bottom: 0.75rem;
}
.mx-12 {
margin-left: 3rem;
margin-right: 3rem;
@ -969,10 +959,6 @@ video {
margin-left: 1rem;
}
.mb-10 {
margin-bottom: 2.5rem;
}
.mt-8 {
margin-top: 2rem;
}
@ -1529,10 +1515,6 @@ video {
justify-self: end;
}
.justify-self-center {
justify-self: center;
}
.overflow-auto {
overflow: auto;
}
@ -1935,6 +1917,11 @@ video {
line-height: 2.5rem;
}
.text-5xl {
font-size: 3rem;
line-height: 1;
}
.font-extrabold {
font-weight: 800;
}

View file

@ -1,10 +1,10 @@
import ImageProvider, { ProvidedImage } from "./ImageProvider"
import BaseUIElement from "../../UI/BaseUIElement"
import Svg from "../../Svg"
import { Utils } from "../../Utils"
import { LicenseInfo } from "./LicenseInfo"
import Constants from "../../Models/Constants"
import Link from "../../UI/Base/Link"
import SvelteUIElement from "../../UI/Base/SvelteUIElement"
import MapillaryIcon from "./MapillaryIcon.svelte"
export class Mapillary extends ImageProvider {
public static readonly singleton = new Mapillary()
@ -112,11 +112,11 @@ export class Mapillary extends ImageProvider {
lat: number
}
): BaseUIElement {
const icon = Svg.mapillary_svg()
if (!id) {
return icon
let url: string = undefined
if (id) {
url = Mapillary.createLink(location, 16, "" + id)
}
return new Link(icon, Mapillary.createLink(location, 16, "" + id), true)
return new SvelteUIElement(MapillaryIcon, { url })
}
async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {

View file

@ -0,0 +1,21 @@
<script lang="ts">
import Mapillary from "../../assets/svg/Mapillary.svelte"
import { ariaLabel } from "../../Utils/ariaLabel"
import Translations from "../../UI/i18n/Translations"
/**
* Accessible, linked mapillary icon
*/
export let url: string = undefined
</script>
{#if url}
<a href={url}
use:ariaLabel={Translations.t.general.attribution.seeOnMapillary}
target="_blank"
rel="noopener nofollower" >
<Mapillary />
</a>
{:else}
<Mapillary />
{/if}

12
src/UI/Base/Link.svelte Normal file
View file

@ -0,0 +1,12 @@
<script lang="ts">
export let text : string
export let href : string
export let classnames : string = undefined
export let download : string = undefined
export let ariaLabel : string = undefined
export let newTab: boolean = false
</script>
<a {href} aria-label={ariaLabel} target={newTab ? "_blank" : undefined} {download} class={classnames}>
{@html text}</a>

View file

@ -1,7 +1,6 @@
import Translations from "../i18n/Translations"
import BaseUIElement from "../BaseUIElement"
import { Store } from "../../Logic/UIEventSource"
import { Utils } from "../../Utils"
export default class Link extends BaseUIElement {
private readonly _href: string | Store<string>

View file

@ -1,17 +1,21 @@
<script lang="ts">
import { createEventDispatcher } from "svelte"
import { twJoin } from "tailwind-merge"
import { Translation } from "../i18n/Translation"
import { ariaLabel } from "../../Utils/ariaLabel"
/**
* A round button with an icon and possible a small text, which hovers above the map
*/
const dispatch = createEventDispatcher()
export let cls = ""
export let arialabel: Translation = undefined
</script>
<button
on:click={(e) => dispatch("click", e)}
on:keydown
use:ariaLabel={arialabel}
class={twJoin("pointer-events-auto m-0.5 h-fit w-fit rounded-full p-0.5 sm:p-1 md:m-1", cls)}
>
<slot />

View file

@ -2,6 +2,8 @@
import ToSvelte from "./ToSvelte.svelte"
import Svg from "../../Svg"
import Share from "../../assets/svg/Share.svelte"
import { ariaLabel } from "../../Utils/ariaLabel"
import Translations from "../i18n/Translations"
export let generateShareData: () => {
text: string
@ -24,7 +26,7 @@
}
</script>
<button on:click={share} class="secondary m-0 h-8 w-8 p-0">
<button on:click={share} class="secondary m-0 h-8 w-8 p-0" use:ariaLabel={Translations.t.general.share}>
<slot name="content">
<Share class="h-7 w-7 p-1" />
</slot>

View file

@ -13,7 +13,8 @@
export let hideTooltip = false
</script>
<MapControlButton on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}>
<MapControlButton arialabel={Translations.t.general.labels.background}
on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}>
<Square3Stack3dIcon class="h-6 w-6" />
{#if !hideTooltip}
<Tr cls="mx-2" t={Translations.t.general.backgroundSwitch} />

View file

@ -7,6 +7,7 @@
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import { ariaLabel } from "../../Utils/ariaLabel"
export let state: SpecialVisualizationState;
export let layer: LayerConfig;
@ -50,8 +51,10 @@
</div>
</div>
<button on:click={() => state.selectedElement.setData(undefined)} class="border-none p-0">
<XCircleIcon class="h-8 w-8" />
<button on:click={() => state.selectedElement.setData(undefined)}
use:ariaLabel={Translations.t.general.backToMap}
class="border-none p-0">
<XCircleIcon aria-hidden={true} class="h-8 w-8" />
</button>
</div>
{/if}

View file

@ -92,7 +92,7 @@
<Tr t={title} />
{#if selected}
<span class="alert">
<span class="alert" aria-hidden="true">
<Tr t={Translations.t.general.morescreen.enterToOpen} />
</span>
{/if}

View file

@ -26,7 +26,7 @@
<div class="flex flex-col">
{#if $license.title}
{#if $license.informationLocation}
<a href={$license.informationLocation} target="_blank" rel="noopener nofollower">{$license.title}</a>
<a href={$license.informationLocation.href} target="_blank" rel="noopener nofollower">{$license.title}</a>
{:else}
$license.title
{/if}

View file

@ -18,6 +18,7 @@
import DateInput from "./Helpers/DateInput.svelte"
import ColorInput from "./Helpers/ColorInput.svelte"
import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte"
import SlopeInput from "./Helpers/SlopeInput.svelte"
export let type: ValidatorType
export let value: UIEventSource<string | object>
@ -47,6 +48,8 @@
<SimpleTagInput {value} {args} on:submit />
{:else if type === "opening_hours"}
<OpeningHoursInput {value} />
{:else if type === "slope"}
<SlopeInput {value} />
{:else if type === "wikidata"}
<ToSvelte construct={() => InputHelpers.constructWikidataHelper(value, properties)} />
{/if}

View file

@ -1,36 +1,39 @@
<script lang="ts">
import type { SpecialVisualizationState } from "../SpecialVisualization";
import { HeartIcon as HeartSolidIcon } from "@babeard/svelte-heroicons/solid";
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline";
import Translations from "../i18n/Translations";
import LoginToggle from "../Base/LoginToggle.svelte";
import type { Feature } from "geojson";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { HeartIcon as HeartSolidIcon } from "@babeard/svelte-heroicons/solid"
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline"
import Translations from "../i18n/Translations"
import LoginToggle from "../Base/LoginToggle.svelte"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { ariaLabel } from "../../Utils/ariaLabel"
import { UIEventSource } from "../../Logic/UIEventSource"
/**
* A small 'mark as favourite'-button to serve as title-icon
*/
export let state: SpecialVisualizationState;
export let feature: Feature;
export let tags: Record<string, string>;
export let layer: LayerConfig;
let isFavourite = tags?.map(tags => tags._favourite === "yes");
const t = Translations.t.favouritePoi;
export let state: SpecialVisualizationState
export let feature: Feature
export let tags: UIEventSource<Record<string, string>>
export let layer: LayerConfig
let isFavourite = tags?.map(tags => tags._favourite === "yes")
const t = Translations.t.favouritePoi
function markFavourite(isFavourite: boolean) {
state.favourites.markAsFavourite(feature, layer.id, state.layout.id, tags, isFavourite);
state.favourites.markAsFavourite(feature, layer.id, state.layout.id, tags, isFavourite)
}
</script>
<LoginToggle ignoreLoading={true} {state}>
{#if $isFavourite}
<button class="p-0 m-0 h-8 w-8" on:click={() => markFavourite(false)}>
<HeartSolidIcon/>
<button class="p-0 m-0 h-8 w-8" on:click={() => markFavourite(false)}
use:ariaLabel={Translations.t.favouritePoi.button.isMarkedShort}>
<HeartSolidIcon aria-hidden={true} />
</button>
{:else}
<button class="p-0 m-0 h-8 w-8 no-image-background" on:click={() => markFavourite(true)} >
<HeartOutlineIcon/>
<button class="p-0 m-0 h-8 w-8 no-image-background" on:click={() => markFavourite(true)}
use:ariaLabel={Translations.t.favouritePoi.button.isNotMarkedShort}>
<HeartOutlineIcon aria-hidden={true} />
</button>
{/if}
</LoginToggle>

View file

@ -45,7 +45,6 @@ import { GeoOperations } from "../Logic/GeoOperations"
import CreateNewNote from "./Popup/CreateNewNote.svelte"
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
import UserProfile from "./BigComponents/UserProfile.svelte"
import Link from "./Base/Link"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
import { WayId } from "../Models/OsmFeature"
@ -81,9 +80,9 @@ import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte"
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
import NearbyImages from "./Image/NearbyImages.svelte"
import NearbyImagesCollapsed from "./Image/NearbyImagesCollapsed.svelte"
import { svelte } from "@sveltejs/vite-plugin-svelte"
import MoveWizard from "./Popup/MoveWizard.svelte"
import { Unit } from "../Models/Unit"
import Link from "./Base/Link.svelte"
class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -1288,6 +1287,10 @@ export default class SpecialVisualizations {
name: "download",
doc: "If set, this link will act as a download-button. The contents of `href` will be offered for download; this parameter will act as the proposed filename",
},
{
name: "arialabel",
doc: "If set, this text will be used as aria-label",
},
],
needsUrls: [],
constr(
@ -1295,15 +1298,19 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
args: string[]
): BaseUIElement {
const [text, href, classnames, download] = args
const [text, href, classnames, download, ariaLabel] = args
const newTab = download === undefined && !href.startsWith("#")
return new VariableUiElement(
tagSource.map((tags) =>
new Link(
Utils.SubstituteKeys(text, tags),
Utils.SubstituteKeys(href, tags),
download === undefined && !href.startsWith("#"),
Utils.SubstituteKeys(download, tags)
).SetClass(classnames)
tagSource.map(
(tags) =>
new SvelteUIElement(Link, {
text: Utils.SubstituteKeys(text, tags),
href: Utils.SubstituteKeys(href, tags),
classnames,
download: Utils.SubstituteKeys(download, tags),
ariaLabel: Utils.SubstituteKeys(ariaLabel, tags),
newTab,
})
)
)
},
@ -1422,12 +1429,11 @@ export default class SpecialVisualizations {
const [_, username, host] = fediAccount.match(
FediverseValidator.usernameAtServer
)
return new Link(
fediAccount,
"https://" + host + "/@" + username,
true
)
return new SvelteUIElement(Link, {
text: fediAccount,
url: "https://" + host + "/@" + username,
newTab: true,
})
})
)
},

View file

@ -1,5 +1,11 @@
<script lang="ts">
// Testing grounds
import { UIEventSource } from "../Logic/UIEventSource"
import SlopeInput from "./InputElement/Helpers/SlopeInput.svelte"
let value: UIEventSource<string> = new UIEventSource(undefined)
</script>
<div class="w-full">No tests</div>
<div class="w-full flex flex-col">
<div>Value: {$value}</div>
</div>
<SlopeInput {value}></SlopeInput>

View file

@ -64,10 +64,10 @@
import Share from "../assets/svg/Share.svelte"
import Favourites from "./Favourites/Favourites.svelte"
import ImageOperations from "./Image/ImageOperations.svelte"
import { ariaLabel } from "../Utils/ariaLabel"
export let state: ThemeViewState
let layout = state.layout
let maplibremap: UIEventSource<MlMap> = state.map
let selectedElement: UIEventSource<Feature> = new UIEventSource<Feature>(undefined)
@ -142,7 +142,8 @@
</div>
</If>
<div class="float-left m-1 flex flex-col sm:mt-2">
<MapControlButton on:click={() => state.guistate.themeIsOpened.setData(true)} on:keydown={forwardEventToMap}>
<MapControlButton on:click={() => state.guistate.themeIsOpened.setData(true)}
on:keydown={forwardEventToMap}>
<div class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2">
<img class="mr-0.5 block h-6 w-6 sm:mr-1 md:mr-2 md:h-8 md:w-8" src={layout.icon} />
<b class="mr-1">
@ -150,15 +151,19 @@
</b>
</div>
</MapControlButton>
<MapControlButton on:click={() => state.guistate.menuIsOpened.setData(true)} on:keydown={forwardEventToMap}>
<MapControlButton
on:click={() => state.guistate.menuIsOpened.setData(true)}
on:keydown={forwardEventToMap}
arialabel={Translations.t.general.labels.menu}
>
<MenuIcon class="h-8 w-8 cursor-pointer" />
</MapControlButton>
{#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()}
<MapControlButton
on:click={() => {
selectedElement.setData(state.currentView.features?.data?.[0])
}} on:keydown={forwardEventToMap}
}}
on:keydown={forwardEventToMap}
>
<ToSvelte
construct={() => currentViewLayer.defaultIcon().SetClass("w-8 h-8 cursor-pointer")}
@ -206,7 +211,9 @@
<div class="flex">
<!-- bottom left elements -->
<If condition={state.featureSwitches.featureSwitchFilter}>
<MapControlButton on:click={() => state.guistate.openFilterView()} on:keydown={forwardEventToMap}>
<MapControlButton on:click={() => state.guistate.openFilterView()} on:keydown={forwardEventToMap}
arialabel={Translations.t.general.labels.filter}
>
<Filter class="h-6 w-6" />
</MapControlButton>
</If>
@ -246,14 +253,21 @@
/>
</div>
</If>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z + 1)} on:keydown={forwardEventToMap}>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z + 1)}
on:keydown={forwardEventToMap}
arialabel={Translations.t.general.labels.zoomIn}
>
<Plus class="h-8 w-8" />
</MapControlButton>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z - 1)} on:keydown={forwardEventToMap}>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z - 1)} on:keydown={forwardEventToMap}
arialabel={Translations.t.general.labels.zoomOut}
>
<Min class="h-8 w-8" />
</MapControlButton>
<If condition={featureSwitches.featureSwitchGeolocation}>
<MapControlButton on:keydown={forwardEventToMap} on:click={() => geolocationControl.handleClick()}>
<MapControlButton on:keydown={forwardEventToMap} on:click={() => geolocationControl.handleClick()}
arialabel={Translations.t.general.labels.jumpToLocation}
>
<ToSvelte
construct={geolocationControl.SetClass("block w-8 h-8")}
/>

View file

@ -1,6 +1,9 @@
import { Translation } from "../UI/i18n/Translation"
export function ariaLabel(htmlElement: Element, t: Translation) {
if (!t) {
return
}
let destroy: () => void = undefined
t.current.map(