Compare commits

...

11 Commits

Author SHA1 Message Date
paweldomas
855eeafc79 feat(accessibility): set html 'lang' attribute
...to match the currently selected language. Starts with
'en' by default.
2020-04-03 18:23:21 -05:00
Ruben Kerkhof
06ae1861ee log: fix showing verbatim %s
So turn 'hover in %s 1a5cd940' into 'hover in 1a5cd940'
2020-04-03 15:41:30 +02:00
Alexander Neumann
e0afd8fadb lang: fix german translation
This commit fixes a few translation errors, especially in the
kickParticipantTitle.
2020-04-03 15:39:38 +02:00
Michael Telatynski
953f838a2a Improve accessibility of Buttons in Webapp (#5432)
* Expose toggle buttons better via ARIA

* Wire up the divs/li as role=button as per ARIA patterns

* Add flow annotations to appease the linter

* For role=button use aria-pressed not aria-checked
2020-04-03 08:19:32 -05:00
ljo
bf5f1f0168 lang: improve sv translation 2020-04-03 15:13:26 +02:00
Saúl Ibarra Corretgé
cdf977ff3f doc: add documentation index 2020-04-03 14:57:40 +02:00
Joris Bodin
ee1dc9dd8e lang: update translation for fr, de, es, it for mobile app 2020-04-03 12:14:21 +02:00
Ali Karpuzoglu
7bec68e492 doc: fix typo 2020-04-03 12:13:20 +02:00
Bettenbuk Zoltan
97fff02c15 web: use password i18n placeholder 2020-04-03 11:22:11 +02:00
Дамян Минков
b372b2ccf2 Fix turn file link (#5601)
* debian: Does not add a link of turnserver will not be configured. Fixes #5596.

* debian: Partially reverts 990c77bd.

* debian: Skip filename duplication, use variable.
2020-04-02 18:20:41 -05:00
Bettenbuk Zoltan
d00ead932b feat: better room lock prompt 2020-04-02 17:27:22 +02:00
22 changed files with 386 additions and 25 deletions

View File

@@ -1 +0,0 @@
/usr/share/jitsi-meet-turnserver/jitsi-meet.conf /etc/nginx/modules-enabled/60-jitsi-meet.conf

View File

@@ -46,14 +46,22 @@ case "$1" in
# check whether other enabled hosts has listen 443
if cat ${site} | grep -v "^[[:space:]]*#" | grep listen | grep -q "^.*[[:space:]:]443[;[:space:]].*" ; then
# nothing to do
echo "------------------------------------------------"
echo ""
echo "turnserver not configured as other nginx sites use port 443"
echo ""
echo "------------------------------------------------"
db_stop
exit 0
fi
done
# if there was a turn config backup it so we can configure
# we cannot recognize at the moment is this a user config or default config when installing coturn
if [[ -f $TURN_CONFIG ]] && ! grep -q "jitsi-meet coturn config" "$TURN_CONFIG" ; then
mv $TURN_CONFIG $TURN_CONFIG.bak
fi
# detect dpkg-reconfigure, just delete old links
db_get jitsi-meet-turnserver/jvb-hostname
JVB_HOSTNAME_OLD=$RET
@@ -67,17 +75,21 @@ case "$1" in
db_get jitsi-meet/jvb-serve || true
if [ ! -f $NGINX_CONFIG -o "$RET" = "true" ] ; then
# nothing to do
echo "------------------------------------------------"
echo ""
echo "turnserver not configured as no nginx found to multiplex traffic"
echo ""
echo "------------------------------------------------"
db_stop
exit 0
fi
if [[ -f $TURN_CONFIG ]] ; then
echo "------------------------------------------------"
echo ""
echo "turnserver is already configured on this machine, skipping."
echo ""
echo "------------------------------------------------"
db_stop
exit 0
fi
@@ -130,7 +142,8 @@ case "$1" in
invoke-rc.d coturn restart || true
NGINX_STREAM_CONFIG="/etc/nginx/modules-enabled/60-jitsi-meet.conf"
if [ -f $NGINX_STREAM_CONFIG ] && [ -f $NGINX_CONFIG ] ; then
if [ ! -f $NGINX_STREAM_CONFIG ] && [ -f $NGINX_CONFIG ] ; then
ln -s /usr/share/jitsi-meet-turnserver/jitsi-meet.conf $NGINX_STREAM_CONFIG
sed -i "s/listen 443 ssl/listen 4444 ssl http2/g" $NGINX_CONFIG
sed -i "s/listen \[\:\:\]\:443 ssl/listen \[\:\:\]\:4444 ssl http2/g" $NGINX_CONFIG
invoke-rc.d nginx reload || true

View File

@@ -24,6 +24,7 @@ set -e
case "$1" in
remove)
rm -rf /etc/nginx/modules-enabled/60-jitsi-meet.conf
if [ -x "/etc/init.d/nginx" ]; then
invoke-rc.d nginx reload || true
fi
@@ -32,6 +33,8 @@ case "$1" in
fi
;;
purge)
rm -rf /etc/nginx/modules-enabled/60-jitsi-meet.conf
rm -rf /etc/turnserver.conf
if [ -x "/etc/init.d/nginx" ]; then
invoke-rc.d nginx reload || true
fi

43
doc/README.md Normal file
View File

@@ -0,0 +1,43 @@
# Documentation
This document is the entrypoint to different guides, divided in three groups:
* User guide: these documents are designed to help users of the service, to better
understand all the available features and how to use them.
* Developer guide: these documents are designed to help developers who want to either
integrate the Jitsi Meet API / SDK in their products or want to improve Jitsi Meet
itself by developing new features or fixing bugs.
* DevOps guide: these documents are designed for DevOps folks, system administrators
or anyone who wishes to deploy and operate their own Jitsi Meet instance.
## User guide
Work in progress.
## Developer guide
### Web
* [iframe API](https://github.com/jitsi/jitsi-meet/blob/master/doc/api.md)
* [Jitsi Meet development](https://github.com/jitsi/jitsi-meet/blob/master/doc/development.md)
### Mobile
* [Building the mobile apps](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md)
* [SDK usage examples](https://github.com/jitsi/jitsi-meet-sdk-samples)
* [Enabling Dropbox support](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile-dropbox.md)
* [Enabling Google authentication](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile-google-auth.md)
## DevOps guide
* [Quick install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md)
* [Docker install](https://github.com/jitsi/docker-jitsi-meet/blob/master/README.md)
* [Google Calendar, MS Calendar, Dropbox integrations](https://github.com/jitsi/jitsi-meet/blob/master/doc/integrations.md)
* [Video tutorials on deployment and scalability](https://jitsi.org/tutorials/)
* [Configuring a video SIP gateway](https://github.com/jitsi/jitsi-meet/blob/master/doc/sipgw-config.md)
* [Enabling speaker stats](https://github.com/jitsi/jitsi-meet/blob/master/doc/speakerstats-prosody.md)
* [Enabling TURN](https://github.com/jitsi/jitsi-meet/blob/master/doc/turn.md)
* [Networking FAQ](https://github.com/jitsi/jitsi-meet/blob/master/doc/faq.md)
* [Cloud APIs](https://github.com/jitsi/jitsi-meet/blob/master/doc/cloud-api.md)

View File

@@ -1,4 +1,4 @@
<html itemscope itemtype="http://schema.org/Product" prefix="og: http://ogp.me/ns#" xmlns="http://www.w3.org/1999/html">
<html itemscope itemtype="http://schema.org/Product" lang="en" prefix="og: http://ogp.me/ns#" xmlns="http://www.w3.org/1999/html">
<head>
<!--#include virtual="head.html" -->
<meta charset="utf-8">

View File

@@ -19,7 +19,7 @@ There are 2 ways to integrate the SDK into your project:
Follow the instructions [here](https://github.com/jitsi/jitsi-meet-ios-sdk-releases/blob/master/README.md).
### Builduing it yourself
### Building it yourself
1. Install all required [dependencies](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md).

View File

@@ -33,7 +33,7 @@
"error": {
"appConfiguration": "Kalenderintegration ist nicht richtig konfiguriert.",
"generic": "Ein Fehler ist aufgetreten. Prüfen Sie Ihre Kalendereinstellungen oder versuchen Sie, den Kalender zu aktualisieren.",
"notSignedIn": "Ein Fehler ist während der Authentifizierung zur Anzeige von Kalenderertermine aufgetreten. Prüfen Sie Ihre Kalendereinstellungen oder versuchen Sie, sich erneut anzumelden."
"notSignedIn": "Ein Fehler ist während der Authentifizierung zur Anzeige von Kalenderterminen aufgetreten. Prüfen Sie Ihre Kalendereinstellungen oder versuchen Sie, sich erneut anzumelden."
},
"join": "Teilnehmen",
"joinTooltip": "Am Meeting teilnehmen",
@@ -191,7 +191,7 @@
"kickMessage": "Sie können sich für weitere Einzelheiten an {{participantDisplayName}}} wenden.",
"kickParticipantButton": "Entfernen",
"kickParticipantDialog": "Wollen Sie diesen Teilnehmer wirklich entfernen?",
"kickParticipantTitle": "Teilnehmer stummschalten?",
"kickParticipantTitle": "Teilnehmer entfernen?",
"kickTitle": "Autsch! {{participantDisplayName}} hat Sie aus dem Meeting geworfen.",
"liveStreaming": "Livestreaming",
"liveStreamingDisabledForGuestTooltip": "Gäste können kein Livestreaming starten.",
@@ -241,7 +241,7 @@
"removePassword": "$t(lockRoomPassword) entfernen",
"removeSharedVideoMsg": "Sind Sie sicher, dass Sie das geteilte Video entfernen möchten?",
"removeSharedVideoTitle": "Freigegebenes Video entfernen",
"reservationError": "Fehler im Reservationssystem",
"reservationError": "Fehler im Reservierungssystem",
"reservationErrorMsg": "Fehler, Nummer: {{code}}, Nachricht: {{msg}}",
"retry": "Wiederholen",
"screenSharingFailedToInstall": "Oh! Die Erweiterung für die Bildschirmfreigabe konnte nicht installiert werden.",
@@ -315,7 +315,7 @@
"dialInConferenceID": "PIN:",
"dialInNotSupported": "Entschuldigung, leider wird das Einwählen derzeit nicht unterstützt.",
"dialInNumber": "Einwählen:",
"dialInSummaryError": "Fehler beim Abrufen der Einwählinformationen. Versuchen Sie es später erneut.",
"dialInSummaryError": "Fehler beim Abrufen der Einwahlinformationen. Versuchen Sie es später erneut.",
"dialInTollFree": "Gebührenfrei",
"genericError": "Es ist leider etwas schiefgegangen.",
"inviteLiveStream": "Klicken Sie auf {{url}} um den Livestream dieser Konferenz zu öffnen",
@@ -328,11 +328,11 @@
"moreNumbers": "Weitere Telefonnummern",
"noNumbers": "Keine Telefonnummern verfügbar.",
"noPassword": "Kein",
"noRoom": "Keine Konferenz für die Einwähl-Informationen angegeben.",
"noRoom": "Keine Konferenz für die Einwahlinformationen angegeben.",
"numbers": "Einwählnummern",
"password": "$t(lockRoomPasswordUppercase):",
"title": "Teilen",
"tooltip": "Freigabe-Link und Einwählinformationen für dieses Meeting",
"tooltip": "Freigabe-Link und Einwahlinformationen für dieses Meeting",
"label": "Meeting-Informationen"
},
"inviteDialog": {
@@ -367,7 +367,7 @@
"videoQuality": "Anrufqualität verwalten"
},
"liveStreaming": {
"busy": "Es werden Resourcen zum Streamen bereitgestellt. Bitte in ein paar Minuten erneut versuchen.",
"busy": "Es werden Ressourcen zum Streamen bereitgestellt. Bitte in ein paar Minuten erneut versuchen.",
"busyTitle": "Alle Streaming-Instanzen sind in Gebrauch",
"changeSignIn": "Konten wechseln.",
"choose": "Livestream auswählen",
@@ -405,7 +405,7 @@
"dialogTitle": "Lokale Aufzeichnungssteuerelemente",
"duration": "Dauer",
"durationNA": "N. v.",
"encoding": "Codierung",
"encoding": "Kodierung",
"label": "LOR",
"labelToolTip": "Lokale Aufzeichnung ist aktiviert",
"localRecording": "Lokale Aufzeichnung",
@@ -769,5 +769,9 @@
"sendFeedback": "Feedback senden",
"terms": "AGB",
"title": "Sichere, mit umfassenden Funktionen ausgestattete und vollkommen kostenlose Videokonferenzen"
},
"lonelyMeetingExperience": {
"button": "Weitere einladen",
"youAreAlone": "Sie sind der einzige in diesem Meeting"
}
}

View File

@@ -628,6 +628,7 @@
"logout": "Cerrar sesión",
"lowerYourHand": "Bajar la mano",
"moreActions": "Más acciones",
"moreOptions": "Más opciones",
"mute": "Activar / Desactivar Silencio",
"noAudioSignalTitle": "¡No hay entrada proveniente de su micrófono!",
"noAudioSignalDesc": "Si no lo silenció a propósito desde la configuración del sistema o el dispositivo, considere cambiar el dispositivo.",
@@ -752,5 +753,9 @@
"sendFeedback": "Enviar comentarios",
"terms": "Términos",
"title": "Seguro, lleno de funcionalidades y videoconferencias completamente gratuitas"
},
"lonelyMeetingExperience": {
"button": "Invitar a otros",
"youAreAlone": "Eres el único en la reunión"
}
}

View File

@@ -628,6 +628,7 @@
"logout": "Déconnexion",
"lowerYourHand": "Baisser la main",
"moreActions": "Plus d'actions",
"moreOptions": "Plus d'options",
"mute": "Muet / Actif",
"noAudioSignalTitle": "Il n'y a pas d'entrée provenant de votre micro !",
"noAudioSignalDesc": "Si vous n'avez pas délibérément coupé le son des paramètres du système ou du matériel, envisagez de changer le périphérique.",
@@ -752,5 +753,12 @@
"sendFeedback": "Envoyer votre avis",
"terms": "Termes",
"title": "Vidéoconférence Sécurisée, entièrement en vedette et gratuite"
},
"lonelyMeetingExperience": {
"button": "Inviter d'autres personnes",
"youAreAlone": "Vous êtes le seul participant de la réunion"
},
"helpView": {
"header": "Centre d'aide"
}
}

View File

@@ -594,6 +594,7 @@
"logout": "Logout",
"lowerYourHand": "Abbassa la mano",
"moreActions": "Più azioni",
"moreOptions": "Più opzioni",
"mute": "Microfono Attiva / Disattiva",
"openChat": "Apri una chat",
"pip": "Abilita visualizzazione immagine nellimmagine",
@@ -707,5 +708,9 @@
"sendFeedback": "Invia feedback",
"terms": "Termini di utilizzo",
"title": "Il sistema di conferenza sicuro, funzionale e completamente gratuito."
},
"lonelyMeetingExperience": {
"button": "Invita gli altri",
"youAreAlone": "Sei l'unico in riunione"
}
}

View File

@@ -569,9 +569,9 @@
"shareYourScreen": "Slå av eller på skärmdelning",
"shortcuts": "Stäng eller öppna genvägar",
"show": "",
"speakerStats": "Stäng eller öppna högstalarstatistik",
"speakerStats": "Stäng eller öppna talarstatistik",
"tileView": "Öppna eller stäng panelvyn",
"toggleCamera": "Byta kamera",
"toggleCamera": "Växla kamera",
"videomute": "Sätt på eller stäng av mikrofonen",
"videoblur": ""
},
@@ -606,7 +606,7 @@
"sharedvideo": "Dela en Youtube-video",
"shareRoom": "Bjud in någon",
"shortcuts": "Visa genvägar",
"speakerStats": "Högtalarspecifikationer",
"speakerStats": "Talarstatistik",
"startScreenSharing": "Starta skärmdelning",
"startSubtitles": "Starta undertextning",
"stopScreenSharing": "Avsluta skämdelning",

View File

@@ -220,7 +220,7 @@
"muteParticipantDialog": "Are you sure you want to mute this participant? You won't be able to unmute them, but they can unmute themselves at any time.",
"muteParticipantTitle": "Mute this participant?",
"Ok": "Ok",
"passwordLabel": "$t(lockRoomPasswordUppercase)",
"passwordLabel": "The meeting has been locked by a participant. Please enter the $t(lockRoomPassword) to join.",
"passwordNotSupported": "Setting a meeting $t(lockRoomPassword) is not supported.",
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) not supported",
"passwordRequired": "$t(lockRoomPasswordUppercase) required",

View File

@@ -184,7 +184,7 @@ export default class LargeVideoManager {
this.newStreamData = null;
logger.info('hover in %s', id);
logger.info(`hover in ${id}`);
this.state = videoType;
// eslint-disable-next-line no-shadow
const container = this.getCurrentContainer();

View File

@@ -0,0 +1,6 @@
/**
* See {@link _setLanguage}.
*
* @type {string}
*/
export const SET_LANGUAGE = 'SET_LANGUAGE';

View File

@@ -0,0 +1,33 @@
// @flow
import { i18next } from './index';
import { SET_LANGUAGE } from './actionTypes';
/**
* Makes an attempt to change the language.
*
* @param {string} newLanguage - The new language code which identifies
* the language to be set.
* @returns {Function}
*/
export function changeLanguage(newLanguage: string): Function {
return function(dispatch: (Object) => Object) {
return i18next.changeLanguage(newLanguage)
.then(() => {
dispatch(_setLanguage(i18next.language));
});
};
}
/**
* Sets the current language on the Redux store.
*
* @param {string} language - The new language code that will be stored.
* @returns {Object}
* @private
*/
export function _setLanguage(language: string): Object {
return {
type: SET_LANGUAGE,
language
};
}

View File

@@ -1,6 +1,10 @@
export * from './actions';
export * from './dateUtil';
export * from './functions';
// TODO Eventually (e.g. when the non-React Web app is rewritten into React), it
// should not be necessary to export i18next.
export { default as i18next, DEFAULT_LANGUAGE, LANGUAGES } from './i18next';
import './middleware';
import './reducer';

View File

@@ -0,0 +1,69 @@
/* @flow */
/* global document */
import i18next from 'i18next';
import { APP_WILL_MOUNT } from '../app';
import { MiddlewareRegistry } from '../redux';
import { SET_LANGUAGE } from './actionTypes';
import { _setLanguage } from './actions';
/**
* The Redux middleware of the feature base/i18n.
*
* @param {Store} store - The Redux store.
* @returns {Function}
* @private
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case APP_WILL_MOUNT:
return _appWillMount(store, next, action);
case SET_LANGUAGE:
return handleSetLanguage(store, next, action);
}
return next(action);
});
/**
* Side effects for {@link APP_WILL_MOUNT}.
*
* @param {Store} store - The Redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The Redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The Redux action {@code APP_WILL_MOUNT} which is
* being dispatched in the specified {@code store}.
* @private
* @returns {Object} The new state that is the result of the reduction of the
* specified {@code action}.
*/
function _appWillMount({ dispatch }, next, action) {
if (i18next.language) {
dispatch(_setLanguage(i18next.language));
} else {
i18next.on('initialized', () => {
dispatch(_setLanguage(i18next.language));
});
}
return next(action);
}
/**
* Side effects for the {@link SET_LANGUAGE} action.
*
* @param {Store} store - The Redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The Redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The Redux action {@code SET_LANGUAGE} which is
* being dispatched in the specified {@code store}.
* @returns {*}
*/
function handleSetLanguage(store, next, action) {
document && document.documentElement && document.documentElement.setAttribute('lang', action.language);
return next(action);
}

View File

@@ -0,0 +1,30 @@
// @flow
import { ReducerRegistry, set } from '../redux';
import {
SET_LANGUAGE
} from './actionTypes';
import { DEFAULT_LANGUAGE } from './i18next';
/**
* The default/initial redux state of the feature base/i18n.
*
* @type {{
* language: string
* }}
*/
const DEFAULT_STATE = {
language: DEFAULT_LANGUAGE
};
ReducerRegistry.register(
'features/base/i18n',
(state = DEFAULT_STATE, action) => {
switch (action.type) {
case SET_LANGUAGE:
return set(state, 'language', action.language);
default:
return state;
}
});

View File

@@ -230,13 +230,14 @@ export default class AbstractButton<P: Props, S: *> extends Component<P, S> {
/**
* Helper function to be implemented by subclasses, which must return a
* {@code boolean} value indicating if this button is toggled or not.
* {@code boolean} value indicating if this button is toggled or not or
* undefined if the button is not toggleable.
*
* @protected
* @returns {boolean}
* @returns {?boolean}
*/
_isToggled() {
return false;
return undefined;
}
_onClick: (*) => void;

View File

@@ -12,6 +12,69 @@ import type { Props } from './AbstractToolboxItem';
* Web implementation of {@code AbstractToolboxItem}.
*/
export default class ToolboxItem extends AbstractToolboxItem<Props> {
/**
* Initializes a new {@code ToolboxItem} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onKeyDown = this._onKeyDown.bind(this);
this._onKeyUp = this._onKeyUp.bind(this);
}
_onKeyDown: (Object) => void;
/**
* Handles 'Enter' key on the button to trigger onClick for accessibility.
*
* @param {Object} event - The key event.
* @private
* @returns {void}
*/
_onKeyDown(event) {
// If the event coming to the dialog has been subject to preventDefault
// we don't handle it here.
if (event.defaultPrevented) {
return;
}
if (event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
this.props.onClick();
} else if (event.key === ' ') {
// Space triggers button onKeyUp but we need to prevent PTT here
event.preventDefault();
event.stopPropagation();
}
}
_onKeyUp: (Object) => void;
/**
* Handles ' ' (Space) key on the button to trigger onClick for
* accessibility.
*
* @param {Object} event - The key event.
* @private
* @returns {void}
*/
_onKeyUp(event) {
// If the event coming to the dialog has been subject to preventDefault
// we don't handle it here.
if (event.defaultPrevented) {
return;
}
if (event.key === ' ') {
event.preventDefault();
event.stopPropagation();
this.props.onClick();
}
}
/**
* Handles rendering of the actual item. If the label is being shown, which
* is controlled with the `showLabel` prop, the item is rendered for its
@@ -27,14 +90,22 @@ export default class ToolboxItem extends AbstractToolboxItem<Props> {
elementAfter,
onClick,
showLabel,
tooltipPosition
tooltipPosition,
toggled
} = this.props;
const className = showLabel ? 'overflow-menu-item' : 'toolbox-button';
const props = {
'aria-pressed': toggled,
'aria-disabled': disabled,
'aria-label': this.accessibilityLabel,
className: className + (disabled ? ' disabled' : ''),
onClick: disabled ? undefined : onClick
onClick: disabled ? undefined : onClick,
onKeyDown: this._onKeyDown,
onKeyUp: this._onKeyUp,
tabIndex: 0,
role: 'button'
};
const elementType = showLabel ? 'li' : 'div';
const useTooltip = this.tooltip && this.tooltip.length > 0;
let children = (

View File

@@ -2,7 +2,7 @@
import { setFollowMe, setStartMutedPolicy } from '../base/conference';
import { openDialog } from '../base/dialog';
import { i18next } from '../base/i18n';
import { changeLanguage } from '../base/i18n';
import {
SET_AUDIO_SETTINGS_VISIBILITY,
@@ -89,7 +89,7 @@ export function submitMoreTab(newState: Object): Function {
}
if (newState.currentLanguage !== currentState.currentLanguage) {
i18next.changeLanguage(newState.currentLanguage);
dispatch(changeLanguage(newState.currentLanguage));
}
};
}

View File

@@ -41,6 +41,68 @@ class ToolbarButton extends AbstractToolbarButton<Props> {
tooltipPosition: 'top'
};
/**
* Initializes a new {@code ToolbarButton} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onKeyDown = this._onKeyDown.bind(this);
this._onKeyUp = this._onKeyUp.bind(this);
}
_onKeyDown: (Object) => void;
/**
* Handles 'Enter' key on the button to trigger onClick for accessibility.
*
* @param {Object} event - The key event.
* @private
* @returns {void}
*/
_onKeyDown(event) {
// If the event coming to the dialog has been subject to preventDefault
// we don't handle it here.
if (event.defaultPrevented) {
return;
}
if (event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
this.props.onClick();
} else if (event.key === ' ') {
// Space triggers button onKeyUp but we need to prevent default here
event.preventDefault();
}
}
_onKeyUp: (Object) => void;
/**
* Handles ' '(Space) key on the button to trigger onClick for
* accessibility.
*
* @param {Object} event - The key event.
* @private
* @returns {void}
*/
_onKeyUp(event) {
// If the event coming to the dialog has been subject to preventDefault
// we don't handle it here.
if (event.defaultPrevented) {
return;
}
if (event.key === ' ') {
event.preventDefault();
event.stopPropagation();
this.props.onClick();
}
}
/**
* Renders the button of this {@code ToolbarButton}.
*
@@ -53,8 +115,13 @@ class ToolbarButton extends AbstractToolbarButton<Props> {
return (
<div
aria-label = { this.props.accessibilityLabel }
aria-pressed = { this.props.toggled }
className = 'toolbox-button'
onClick = { this.props.onClick }>
onClick = { this.props.onClick }
onKeyDown = { this._onKeyDown }
onKeyUp = { this._onKeyUp }
role = 'button'
tabIndex = { 0 }>
{ this.props.tooltip
? <Tooltip
content = { this.props.tooltip }