diff --git a/DESCRIPTION b/DESCRIPTION index 0b29768..5a6b695 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -11,11 +11,12 @@ License: Apache License (>= 2) Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.1 +RoxygenNote: 7.2.3 Imports: htmltools, shiny, - jsonlite + jsonlite, + glue URL: https://github.com/r4fun/keys BugReports: https://github.com/r4fun/keys/issues Suggests: diff --git a/R/add_key.R b/R/add_key.R index 844eaf8..7ec8f8d 100644 --- a/R/add_key.R +++ b/R/add_key.R @@ -7,6 +7,7 @@ addKeys <- function( inputId, keys, + nullOnKeyRelease = FALSE, session = shiny::getDefaultReactiveDomain() ){ if (is.null(session)) alert_null_session() @@ -17,7 +18,8 @@ addKeys <- function( "add_mousetrap_binding", list( id = inputId, - keys = x + keys = x, + nullOnKeyRelease = nullOnKeyRelease ) ) } diff --git a/R/keys.R b/R/keys.R index 1fe758c..b8b6d6a 100644 --- a/R/keys.R +++ b/R/keys.R @@ -6,8 +6,10 @@ #' @param inputId The input slot that will be used to access the value. #' @param keys A character vector of keys to bind. Examples include, `command`, #' `command+shift+a`, `up down left right`, and more. -#' @param global Should keys work anywhere? If TRUE, keys are triggered when +#' @param global Should keys work anywhere? If [TRUE], keys are triggered when #' inside a textInput. +#' @param nullOnKeyRelease If [TRUE], input will be set to [NULL] after keys were +#' released. #' #' @examples #' \dontrun{ @@ -31,8 +33,8 @@ #' } #' #' @export -keysInput <- function(inputId, keys, global = FALSE) { +keysInput <- function(inputId, keys, global = FALSE, nullOnKeyRelease = FALSE) { htmltools::tagList( - keys_js(inputId, keys, global) + keys_js(inputId, keys, global, nullOnKeyRelease) ) } diff --git a/R/utils.R b/R/utils.R index 11136e6..971aaa3 100644 --- a/R/utils.R +++ b/R/utils.R @@ -4,15 +4,37 @@ minify <- function(x) { #' @importFrom htmltools tags #' @importFrom jsonlite toJSON -keys_js <- function(id, keys, global = FALSE) { - if (global) global <- "Global" - else global <- "" - - x <- sprintf("$(document).on('shiny:sessioninitialized', function() { - Mousetrap.bind%s(%s, function(e, combo) { - Shiny.setInputValue('%s', combo, {priority: 'event'}); - }); - });", global, toJSON(keys), id) +keys_js <- function(id, keys, global = FALSE, nullOnKeyRelease = FALSE) { + x <- if (!nullOnKeyRelease) { + glue::glue( + "$(document).on('shiny:sessioninitialized', function() {", + " Mousetrap.{{bindFunc}}({{keys}}, function(e, combo) {", + " Shiny.setInputValue('{{id}}', combo, {priority: 'event'});", + " });", + "});", + bindFunc = if (global) "bindGlobal" else "bind", + keys = jsonlite::toJSON(keys), + id = id, + .open = "{{", + .close = "}}" + ) + } else { + glue::glue( + "$(document).on('shiny:sessioninitialized', function() {", + " Mousetrap.{{bindFunc}}({{keys}}, function(e, combo) {", + " Shiny.setInputValue('{{id}}', combo, {priority: 'event'});", + " }, 'keydown');", + " Mousetrap.{{bindFunc}}({{keys}}, function(e, combo) {", + " Shiny.setInputValue('{{id}}', null, {priority: 'event'});", + " }, 'keyup');", + "});", + bindFunc = if (global) "bindGlobal" else "bind", + keys = jsonlite::toJSON(keys), + id = id, + .open = "{{", + .close = "}}" + ) + } tags$head( tags$script( diff --git a/inst/js/handlers.js b/inst/js/handlers.js index 38dc6b7..b8fa461 100644 --- a/inst/js/handlers.js +++ b/inst/js/handlers.js @@ -1,8 +1,17 @@ $( document ).ready(function() { Shiny.addCustomMessageHandler('add_mousetrap_binding', function(arg) { - Mousetrap.bind(arg.keys, function() { - Shiny.setInputValue(arg.id, arg.keys, {priority: 'event'}); - }); + if (arg.nullOnKeyRelease) { + Mousetrap.bind(arg.keys, function() { + Shiny.setInputValue(arg.id, arg.keys, {priority: 'event'}); + }, 'keydown'); + Mousetrap.bind(arg.keys, function() { + Shiny.setInputValue(arg.id, null, {priority: 'event'}); + }, 'keyup'); + } else { + Mousetrap.bind(arg.keys, function() { + Shiny.setInputValue(arg.id, arg.keys, {priority: 'event'}); + }); + } }) Shiny.addCustomMessageHandler('remove_mousetrap_binding', function(arg) { Mousetrap.unbind(arg.keys); diff --git a/man/keysInput.Rd b/man/keysInput.Rd index cbe5fb5..6be08c3 100644 --- a/man/keysInput.Rd +++ b/man/keysInput.Rd @@ -4,7 +4,7 @@ \alias{keysInput} \title{Create a keys input control} \usage{ -keysInput(inputId, keys, global = FALSE) +keysInput(inputId, keys, global = FALSE, nullOnKeyRelease = FALSE) } \arguments{ \item{inputId}{The input slot that will be used to access the value.} @@ -12,8 +12,11 @@ keysInput(inputId, keys, global = FALSE) \item{keys}{A character vector of keys to bind. Examples include, \code{command}, \code{command+shift+a}, \verb{up down left right}, and more.} -\item{global}{Should keys work anywhere? If TRUE, keys are triggered when +\item{global}{Should keys work anywhere? If \link{TRUE}, keys are triggered when inside a textInput.} + +\item{nullOnKeyRelease}{If \link{TRUE}, input will be set to \link{NULL} after keys were +released.} } \description{ Create a key input that can be used to observe keys pressed by diff --git a/man/updateKeys.Rd b/man/updateKeys.Rd index b547053..3713a41 100644 --- a/man/updateKeys.Rd +++ b/man/updateKeys.Rd @@ -5,7 +5,12 @@ \alias{removeKeys} \title{Add a key binding from the server side} \usage{ -addKeys(inputId, keys, session = shiny::getDefaultReactiveDomain()) +addKeys( + inputId, + keys, + nullOnKeyRelease = FALSE, + session = shiny::getDefaultReactiveDomain() +) removeKeys(keys, session = shiny::getDefaultReactiveDomain()) } @@ -15,6 +20,9 @@ removeKeys(keys, session = shiny::getDefaultReactiveDomain()) \item{keys}{A character vector of keys to bind. Examples include, \code{command}, \code{command+shift+a}, \verb{up down left right}, and more.} +\item{nullOnKeyRelease}{If \link{TRUE}, input will be set to \link{NULL} after keys were +released.} + \item{session}{The \code{session} object passed to function given to \code{shinyServer}. Default is \code{getDefaultReactiveDomain()}.} }