Improve budget joining view and add screen reader compatibility

This commit is contained in:
Mikko Ahlroth 2019-07-04 09:23:10 +03:00
parent 1d10c9d9f7
commit 242fef400d
4 changed files with 77 additions and 23 deletions

View file

@ -1,12 +1,15 @@
import { LitElement, html } from 'lit-element' import { LitElement, html } from 'lit-element'
import { InputField } from './input'; import { InputField } from './input'
import { QRReadEvent } from './budget-qr-reader'; import './budget-qr-reader'
import { QRReadEvent } from './budget-qr-reader'
import { ERROR_TIMER } from '../config'
export class JoinBudgetEvent extends CustomEvent<{ uuid: string }> { } export class JoinBudgetEvent extends CustomEvent<{ uuid: string }> { }
class BudgetJoinComponent extends LitElement { class BudgetJoinComponent extends LitElement {
hasError: boolean = false hasError: boolean = false
showQRReader: boolean = false showQRReader: boolean = false
errorTimer: number = null
static get properties() { static get properties() {
return { return {
@ -15,9 +18,30 @@ class BudgetJoinComponent extends LitElement {
} }
} }
firstUpdated() {
const secretInputEl = <InputField>this.shadowRoot.getElementById('uuid')
secretInputEl.focusInput()
}
updated(changedProperties: Map<string | number | symbol, any>) {
// If error was turned on, turn it off after a moment
if (changedProperties.has('hasError')) {
if (this.errorTimer != null) {
window.clearTimeout(this.errorTimer)
}
this.errorTimer = window.setTimeout(() => {
this.hasError = false
this.errorTimer = null
}, ERROR_TIMER * 1000)
}
return super.updated(changedProperties)
}
onJoin(e: Event) { onJoin(e: Event) {
e.preventDefault() e.preventDefault()
const inputEl = <InputField>this.shadowRoot.getElementById('join-budget-uuid') const inputEl = <InputField>this.shadowRoot.getElementById('uuid')
this.dispatchEvent(new JoinBudgetEvent( this.dispatchEvent(new JoinBudgetEvent(
'joinBudget', 'joinBudget',
@ -42,12 +66,10 @@ class BudgetJoinComponent extends LitElement {
render() { render() {
return html` return html`
<p>Add below the budget secret that you can get from the person who created the budget.</p>
<input-field <input-field
id="join-budget-uuid" id="uuid"
type="text" type="text"
label="Budget secret" label="Budget secret (ask someone to share theirs)"
placeholder="aaaaaaaa-bbbb-4ccc-9ddd-ffffffffffff"
maxlength="36" maxlength="36"
pattern="[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}" pattern="[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}"
required required
@ -56,14 +78,17 @@ class BudgetJoinComponent extends LitElement {
<fa-icon name="plus" alt="Join budget"></fa-icon> <fa-icon name="plus" alt="Join budget"></fa-icon>
</fa-button> </fa-button>
<fa-button type="button" @click=${this.toggleShowQRReader}> <fa-button type="button" @click=${this.toggleShowQRReader}>
<fa-icon name="qrcode" alt="Read QR using camera"></fa-icon> <fa-icon
name="qrcode"
alt="${(!this.showQRReader) ? 'Read QR using camera' : 'Stop reading QR'}"
></fa-icon>
</fa-button> </fa-button>
<div> <div>
${this.showQRReader ? html`<budget-qr-reader @qrRead=${this.onQRRead}></budget-qr-reader>` : null} ${this.showQRReader ? html`<budget-qr-reader @qrRead=${this.onQRRead}></budget-qr-reader>` : null}
</div> </div>
${this.hasError ? html`<p>Could not add given budget, please check it is correctly typed.</p>` : null} ${this.hasError ? html`<p role="alert">Could not add given budget, please check it is correctly typed.</p>` : null}
` `
} }
} }

View file

@ -1,5 +1,6 @@
import { LitElement, html } from 'lit-element' import { LitElement, html, css } from 'lit-element'
import { BrowserQRCodeReader, VideoInputDevice } from '../vendor/zxing-typescript/src/browser/BrowserQRCodeReader' import { BrowserQRCodeReader, VideoInputDevice } from '../vendor/zxing-typescript/src/browser/BrowserQRCodeReader'
import { SR_ONLY } from './styles'
export class QRReadEvent extends CustomEvent<{ text: string }> { } export class QRReadEvent extends CustomEvent<{ text: string }> { }
@ -9,16 +10,26 @@ class BudgetQRReaderComponent extends LitElement {
reader: BrowserQRCodeReader reader: BrowserQRCodeReader
inputs: VideoInputDevice[] inputs: VideoInputDevice[]
currentInput: string currentInput: string
currentInputLabel: string
test: string test: string
scanActive: boolean
static get properties() { static get properties() {
return { return {
inputs: { type: Array }, inputs: { type: Array },
currentInput: { type: String }, currentInput: { type: String },
test: { type: String } currentInputLabel: { type: String },
test: { type: String },
scanActive: { type: Boolean }
} }
} }
static get styles() {
return css`
${SR_ONLY}
`
}
constructor() { constructor() {
super() super()
@ -31,6 +42,7 @@ class BudgetQRReaderComponent extends LitElement {
this.inputs = await this.reader.getVideoInputDevices() this.inputs = await this.reader.getVideoInputDevices()
if (this.inputs.length > 0) { if (this.inputs.length > 0) {
this.currentInput = this.inputs[0].deviceId this.currentInput = this.inputs[0].deviceId
this.currentInputLabel = this.inputs[0].label
this.startRead() this.startRead()
} }
} }
@ -54,6 +66,7 @@ class BudgetQRReaderComponent extends LitElement {
let found = false let found = false
do { do {
this.scanActive = true
const result = await this.reader.decodeFromInputVideoDevice(this.currentInput, videoEl) const result = await this.reader.decodeFromInputVideoDevice(this.currentInput, videoEl)
const text = result.getText() const text = result.getText()
if (this._isUUID(text)) { if (this._isUUID(text)) {
@ -67,13 +80,23 @@ class BudgetQRReaderComponent extends LitElement {
} }
stopRead() { stopRead() {
this.scanActive = false
this.reader.reset() this.reader.reset()
} }
inputChanged() {
const inputEl = <HTMLSelectElement>this.shadowRoot.getElementById('input')
this.currentInput = inputEl.value
this.currentInputLabel = inputEl.options[inputEl.selectedIndex].label
this.stopRead()
this.startRead()
}
render() { render() {
return html` return html`
${this.scanActive ? html`<p role="alert" class="sr-only">Now scanning for QR codes with device ${this.currentInputLabel}.</p>` : null}
<video id="video" width="300" height="200" style="border: 1px solid gray"></video> <video id="video" width="300" height="200" style="border: 1px solid gray"></video>
<select id="qr-reader-input"> <select id="input" @change="${this.inputChanged}">
${this.inputs.map(input => this.renderVideoInput(input))} ${this.inputs.map(input => this.renderVideoInput(input))}
</select> </select>
` `

View file

@ -1,4 +1,5 @@
import { LitElement, html, css, unsafeCSS } from 'lit-element' import { LitElement, html, css, unsafeCSS } from 'lit-element'
import { SR_ONLY } from './styles'
/** /**
* An accessible Font Awesome icon. Normally FA icons are not read by screen readers, leading to bad * An accessible Font Awesome icon. Normally FA icons are not read by screen readers, leading to bad
@ -54,17 +55,7 @@ function icon2css(name: string, code: string) {
} }
const FA_CSS = css` const FA_CSS = css`
/* From https://webaim.org/techniques/css/invisiblecontent/ */ ${SR_ONLY}
.sr-only {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
.fa, .fa,
.fas, .fas,

View file

@ -4,3 +4,18 @@ import { css } from 'lit-element'
* Breakpoint media query to use for mobile layouts. * Breakpoint media query to use for mobile layouts.
*/ */
export const MOB_BP = css`@media (max-width: 768px)` export const MOB_BP = css`@media (max-width: 768px)`
/** Show something only for screen readers. */
export const SR_ONLY = css`
/* From https://webaim.org/techniques/css/invisiblecontent/ */
.sr-only {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
`