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

@ -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(