Mottie / Keyboard

Virtual Keyboard using jQuery ~
http://mottie.github.io/Keyboard/
Other
1.77k stars 722 forks source link

Input Event fired multiple times on physical keyboard input #813

Open pilz97 opened 2 years ago

pilz97 commented 2 years ago

Hi,

i have a problem with the alway open mode. In one component i have two inputs. One input have the keyboard where always open = false (this input behaves correctly) One input have the keyboard with alway open = true (here are the inputs which i make via physical keyboard multiplied)

So for example. I click the input with always open = false, then the one with always open = true, i repeat this for example 3 times. When i press on the pysichal keyboard any letter e.g. "s" i get as input "sss", when i click again in the input with always open = false, then again in the one with always open = true, and i press again an "s" i get "ssss"

Has anyone an idea whats wrong here?

pilz97 commented 2 years ago

@Mottie you can reproduce the behaviour in the play ground easily.

Just put this inside the js + jquery part: `/ VIRTUAL KEYBOARD DEMO - https://github.com/Mottie/Keyboard / $(function() {

// text that is typed when when pressing the // keyboard icon (actual code using .typeIn() // is at the bottom of this code block var simulateTyping = "Hello World!!1\b <3";

// create a new language (love, awww) by copying the english language // file. we're doing this just for this demo, so we can add "<3" to the // combo regex $.keyboard.language.love = $.extend($.keyboard.language.en);

$('#keyboard').keyboard({ // set this to ISO 639-1 language code to override language set by // the layout: http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes // language defaults to ["en"] if not found language: ['love'], rtl: false,

  // *** choose layout & positioning ***
  // choose from 'qwerty', 'alpha',
  // 'international', 'dvorak', 'num' or
  // 'custom' (to use the customLayout below)
  layout: 'qwerty',
  customLayout: {
    'default': [
      'd e f a u l t',
      '{meta1} {meta2} {accept} {cancel}'
    ],
    'meta1': [
      'm y m e t a 1',
      '{meta1} {meta2} {accept} {cancel}'
    ],
    'meta2': [
      'M Y M E T A 2',
      '{meta1} {meta2} {accept} {cancel}'
    ]
  },
  // Used by jQuery UI position utility
  position: {
    // null = attach to input/textarea;
    // use $(sel) to attach elsewhere
    of: null,
    my: 'center top',
    at: 'center top',
    // used when "usePreview" is false
    at2: 'center bottom'
  },

  // allow jQuery position utility to reposition the keyboard on
  // window resize
  reposition: true,

  // true: preview added above keyboard;
  // false: original input/textarea used
  usePreview: false,

  // if true, the keyboard will always be visible
  alwaysOpen: false,

  // give the preview initial focus when the keyboard
  // becomes visible
  initialFocus: true,
  // Avoid focusing the input the keyboard is attached to
  noFocus: false,

  // if true, keyboard will remain open even if
  // the input loses focus.
  stayOpen: false,

  // Prevents the keyboard from closing when the user clicks or
  // presses outside the keyboard. The `autoAccept` option must
  // also be set to true when this option is true or changes are lost
  userClosed: false,

  // if true, keyboard will not close if you press escape.
  ignoreEsc: false,

  // *** change keyboard language & look ***
  display: {
    'meta1': '\u2666', // Diamond
    'meta2': '\u2665', // Heart

    // check mark (accept)
    'a': '\u2714:Accept (Shift-Enter)',
    'accept': 'Accept:Accept (Shift-Enter)',
    'alt': 'AltGr:Alternate Graphemes',
    // Left arrow (same as &larr;)
    'b': '\u2190:Backspace',
    'bksp': 'Bksp:Backspace',
    // big X, close/cancel
    'c': '\u2716:Cancel (Esc)',
    'cancel': 'Cancel:Cancel (Esc)',
    // clear num pad
    'clear': 'C:Clear',
    'combo': '\u00f6:Toggle Combo Keys',
    // num pad decimal '.' (US) & ',' (EU)
    'dec': '.:Decimal',
    // down, then left arrow - enter symbol
    'e': '\u21b5:Enter',
    'empty': '\u00a0', // &nbsp;
    'enter': 'Enter:Enter',
    // left arrow (move caret)
    'left': '\u2190',
    // caps lock
    'lock': '\u21ea Lock:Caps Lock',
    'next': 'Next \u21e8',
    'prev': '\u21e6 Prev',
    // right arrow (move caret)
    'right': '\u2192',
    // thick hollow up arrow
    's': '\u21e7:Shift',
    'shift': 'Shift:Shift',
    // +/- sign for num pad
    'sign': '\u00b1:Change Sign',
    'space': '\u00a0:Space',
    // right arrow to bar
    // \u21b9 is the true tab symbol
    't': '\u21e5:Tab',
    'tab': '\u21e5 Tab:Tab',
    // replaced by an image
    'toggle': ' ',

    // added to titles of keys
    // accept key status when acceptValid:true
    'valid': 'valid',
    'invalid': 'invalid',
    // combo key states
    'active': 'active',
    'disabled': 'disabled'

  },

  // Message added to the key title while hovering,
  // if the mousewheel plugin exists
  wheelMessage: 'Use mousewheel to see other keys',

  css: {
    // input & preview
    input: 'ui-widget-content ui-corner-all',
    // keyboard container
    container: 'ui-widget-content ui-widget ui-corner-all ui-helper-clearfix',
    // keyboard container extra class (same as container, but separate)
    popup: '',
    // default state
    buttonDefault: 'ui-state-default ui-corner-all',
    // hovered button
    buttonHover: 'ui-state-hover',
    // Action keys (e.g. Accept, Cancel, Tab, etc);
    // this replaces "actionClass" option
    buttonAction: 'ui-state-active',
    // Active keys
    // (e.g. shift down, meta keyset active, combo keys active)
    buttonActive: 'ui-state-active',
    // used when disabling the decimal button {dec}
    // when a decimal exists in the input area
    buttonDisabled: 'ui-state-disabled',
    // {empty} button class name
    buttonEmpty: 'ui-keyboard-empty'
  },

  // *** Useability ***
  // Auto-accept content when clicking outside the
  // keyboard (popup will close)
  autoAccept: false,
  // Auto-accept content even if the user presses escape
  // (only works if `autoAccept` is `true`)
  autoAcceptOnEsc: false,

  // Prevents direct input in the preview window when true
  lockInput: false,

  // Prevent keys not in the displayed keyboard from being
  // typed in
  restrictInput: false,
  // Additional allowed characters while restrictInput is true
  restrictInclude: '', // e.g. 'a b foo \ud83d\ude38'

  // Check input against validate function, if valid the
  // accept button is clickable; if invalid, the accept
  // button is disabled.
  acceptValid: true,
  // Auto-accept when input is valid; requires `acceptValid`
  // set `true` & validate callback
  autoAcceptOnValid: false,

  // if acceptValid is true & the validate function returns
  // a false, this option will cancel a keyboard close only
  // after the accept button is pressed
  cancelClose: true,

  // tab to go to next, shift-tab for previous
  // (default behavior)
  tabNavigation: false,

  // enter for next input; shift-enter accepts content &
  // goes to next shift + "enterMod" + enter ("enterMod"
  // is the alt as set below) will accept content and go
  // to previous in a textarea
  enterNavigation: false,
  // mod key options: 'ctrlKey', 'shiftKey', 'altKey',
  // 'metaKey' (MAC only)
  // alt-enter to go to previous;
  // shift-alt-enter to accept & go to previous
  enterMod: 'altKey',

  // if true, the next button will stop on the last
  // keyboard input/textarea; prev button stops at first
  // if false, the next button will wrap to target the
  // first input/textarea; prev will go to the last
  stopAtEnd: true,

  // Set this to append the keyboard immediately after the
  // input/textarea it is attached to. This option works
  // best when the input container doesn't have a set width
  // and when the "tabNavigation" option is true
  appendLocally: false,
  // When appendLocally is false, the keyboard will be appended
  // to this object
  appendTo: 'body',

  // If false, the shift key will remain active until the
  // next key is (mouse) clicked on; if true it will stay
  // active until pressed again
  stickyShift: false,

  // Prevent pasting content into the area
  preventPaste: false,

  // caret places at the end of any text
  caretToEnd: false,

  // caret stays this many pixels from the edge of the input
  // while scrolling left/right; use "c" or "center" to center
  // the caret while scrolling
  scrollAdjustment: 10,

  // Set the max number of characters allowed in the input,
  // setting it to false disables this option
  maxLength: false,
  // allow inserting characters @ caret when maxLength is set
  maxInsert: true,

  // Mouse repeat delay - when clicking/touching a virtual
  // keyboard key, after this delay the key will start
  // repeating
  repeatDelay: 500,

  // Mouse repeat rate - after the repeatDelay, this is the
  // rate (characters per second) at which the key is
  // repeated. Added to simulate holding down a real keyboard
  // key and having it repeat. I haven't calculated the upper
  // limit of this rate, but it is limited to how fast the
  // javascript can process the keys. And for me, in Firefox,
  // it's around 20.
  repeatRate: 20,

  // resets the keyboard to the default keyset when visible
  resetDefault: false,

  // Event (namespaced) on the input to reveal the keyboard.
  // To disable it, just set it to ''.
  openOn: 'focus',

  // Event (namepaced) for when the character is added to the
  // input (clicking on the keyboard)
  keyBinding: 'mousedown touchstart',

  // enable/disable mousewheel functionality
  // enabling still depends on the mousewheel plugin
  useWheel: true,

  // combos (emulate dead keys)
  // if user inputs `a the script converts it to à,
  // ^o becomes ô, etc.
  useCombos: true,
  // if you add a new combo, you will need to update the
  // regex below
  combos: {
    // uncomment out the next line, then read the Combos
    //Regex section below
    '<': { 3: '\u2665' }, // turn <3 into ♥ - change regex below
    'a': { e: "\u00e6" }, // ae ligature
    'A': { E: "\u00c6" },
    'o': { e: "\u0153" }, // oe ligature
    'O': { E: "\u0152" }
  },

  // *** Methods ***
  // Callbacks - attach a function to any of these
  // callbacks as desired
  initialized: function(e, keyboard, el) {},
  beforeVisible: function(e, keyboard, el) {},
  visible: function(e, keyboard, el) {},
  beforeInsert: function(e, keyboard, el, textToAdd) { return textToAdd; },
  change: function(e, keyboard, el) {},
  beforeClose: function(e, keyboard, el, accepted) {},
  accepted: function(e, keyboard, el) {},
  canceled: function(e, keyboard, el) {},
  restricted: function(e, keyboard, el) {},
  hidden: function(e, keyboard, el) {},

  // called instead of base.switchInput
  // Go to next or prev inputs
  // goToNext = true, then go to next input;
  //   if false go to prev
  // isAccepted is from autoAccept option or
  //   true if user presses shift-enter
  switchInput: function(keyboard, goToNext, isAccepted) {},

  /*
          // build key callback
      buildKey : function( keyboard, data ) {
            /* data = {
              // READ ONLY
              isAction: [boolean] true if key is an action key
            // name... may include decimal ascii value of character
              // prefix = 'ui-keyboard-'
              name    : [string]  key class name suffix
              value   : [string]  text inserted (non-action keys)
              title   : [string]  title attribute of key
              action  : [string]  keyaction name
              // html includes a <span> wrapping the text
              html    : [string]  HTML of the key;
              // DO NOT MODIFY THE ABOVE SETTINGS

            // use to modify key HTML
              $key    : [object]  jQuery selector of key already added to keyboard
            }
            * /
            data.$key.html('<span class="ui-keyboard-text">Foo</span>');
            return data;
            },
          */

  // this callback is called just before the "beforeClose"
  // to check the value if the value is valid, return true
  // and the keyboard will continue as it should (close if
  // not always open, etc)
  // if the value is not value, return false and the clear
  // the keyboard value ( like this
  // "keyboard.$preview.val('');" ), if desired
  validate: function(keyboard, value, isClosing) {
    return true;
  }

})
// activate the typing extension
.addTyping({
  showTyping: true,
  delay: 250
});

$('#keyboard2').keyboard({
  // set this to ISO 639-1 language code to override language set by
  // the layout: http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
  // language defaults to ["en"] if not found
  language: ['love'],
  rtl: false,

  // *** choose layout & positioning ***
  // choose from 'qwerty', 'alpha',
  // 'international', 'dvorak', 'num' or
  // 'custom' (to use the customLayout below)
  layout: 'qwerty',
  customLayout: {
    'default': [
      'd e f a u l t',
      '{meta1} {meta2} {accept} {cancel}'
    ],
    'meta1': [
      'm y m e t a 1',
      '{meta1} {meta2} {accept} {cancel}'
    ],
    'meta2': [
      'M Y M E T A 2',
      '{meta1} {meta2} {accept} {cancel}'
    ]
  },
  // Used by jQuery UI position utility
  position: {
    // null = attach to input/textarea;
    // use $(sel) to attach elsewhere
    of: null,
    my: 'center top',
    at: 'center top',
    // used when "usePreview" is false
    at2: 'center bottom'
  },

  // allow jQuery position utility to reposition the keyboard on
  // window resize
  reposition: true,

  // true: preview added above keyboard;
  // false: original input/textarea used
  usePreview: false,

  // if true, the keyboard will always be visible
  alwaysOpen: true,

  // give the preview initial focus when the keyboard
  // becomes visible
  initialFocus: true,
  // Avoid focusing the input the keyboard is attached to
  noFocus: false,

  // if true, keyboard will remain open even if
  // the input loses focus.
  stayOpen: false,

  // Prevents the keyboard from closing when the user clicks or
  // presses outside the keyboard. The `autoAccept` option must
  // also be set to true when this option is true or changes are lost
  userClosed: false,

  // if true, keyboard will not close if you press escape.
  ignoreEsc: false,

  // *** change keyboard language & look ***
  display: {
    'meta1': '\u2666', // Diamond
    'meta2': '\u2665', // Heart

    // check mark (accept)
    'a': '\u2714:Accept (Shift-Enter)',
    'accept': 'Accept:Accept (Shift-Enter)',
    'alt': 'AltGr:Alternate Graphemes',
    // Left arrow (same as &larr;)
    'b': '\u2190:Backspace',
    'bksp': 'Bksp:Backspace',
    // big X, close/cancel
    'c': '\u2716:Cancel (Esc)',
    'cancel': 'Cancel:Cancel (Esc)',
    // clear num pad
    'clear': 'C:Clear',
    'combo': '\u00f6:Toggle Combo Keys',
    // num pad decimal '.' (US) & ',' (EU)
    'dec': '.:Decimal',
    // down, then left arrow - enter symbol
    'e': '\u21b5:Enter',
    'empty': '\u00a0', // &nbsp;
    'enter': 'Enter:Enter',
    // left arrow (move caret)
    'left': '\u2190',
    // caps lock
    'lock': '\u21ea Lock:Caps Lock',
    'next': 'Next \u21e8',
    'prev': '\u21e6 Prev',
    // right arrow (move caret)
    'right': '\u2192',
    // thick hollow up arrow
    's': '\u21e7:Shift',
    'shift': 'Shift:Shift',
    // +/- sign for num pad
    'sign': '\u00b1:Change Sign',
    'space': '\u00a0:Space',
    // right arrow to bar
    // \u21b9 is the true tab symbol
    't': '\u21e5:Tab',
    'tab': '\u21e5 Tab:Tab',
    // replaced by an image
    'toggle': ' ',

    // added to titles of keys
    // accept key status when acceptValid:true
    'valid': 'valid',
    'invalid': 'invalid',
    // combo key states
    'active': 'active',
    'disabled': 'disabled'

  },

  // Message added to the key title while hovering,
  // if the mousewheel plugin exists
  wheelMessage: 'Use mousewheel to see other keys',

  css: {
    // input & preview
    input: 'ui-widget-content ui-corner-all',
    // keyboard container
    container: 'ui-widget-content ui-widget ui-corner-all ui-helper-clearfix',
    // keyboard container extra class (same as container, but separate)
    popup: '',
    // default state
    buttonDefault: 'ui-state-default ui-corner-all',
    // hovered button
    buttonHover: 'ui-state-hover',
    // Action keys (e.g. Accept, Cancel, Tab, etc);
    // this replaces "actionClass" option
    buttonAction: 'ui-state-active',
    // Active keys
    // (e.g. shift down, meta keyset active, combo keys active)
    buttonActive: 'ui-state-active',
    // used when disabling the decimal button {dec}
    // when a decimal exists in the input area
    buttonDisabled: 'ui-state-disabled',
    // {empty} button class name
    buttonEmpty: 'ui-keyboard-empty'
  },

  // *** Useability ***
  // Auto-accept content when clicking outside the
  // keyboard (popup will close)
  autoAccept: false,
  // Auto-accept content even if the user presses escape
  // (only works if `autoAccept` is `true`)
  autoAcceptOnEsc: false,

  // Prevents direct input in the preview window when true
  lockInput: false,

  // Prevent keys not in the displayed keyboard from being
  // typed in
  restrictInput: false,
  // Additional allowed characters while restrictInput is true
  restrictInclude: '', // e.g. 'a b foo \ud83d\ude38'

  // Check input against validate function, if valid the
  // accept button is clickable; if invalid, the accept
  // button is disabled.
  acceptValid: true,
  // Auto-accept when input is valid; requires `acceptValid`
  // set `true` & validate callback
  autoAcceptOnValid: false,

  // if acceptValid is true & the validate function returns
  // a false, this option will cancel a keyboard close only
  // after the accept button is pressed
  cancelClose: true,

  // tab to go to next, shift-tab for previous
  // (default behavior)
  tabNavigation: false,

  // enter for next input; shift-enter accepts content &
  // goes to next shift + "enterMod" + enter ("enterMod"
  // is the alt as set below) will accept content and go
  // to previous in a textarea
  enterNavigation: false,
  // mod key options: 'ctrlKey', 'shiftKey', 'altKey',
  // 'metaKey' (MAC only)
  // alt-enter to go to previous;
  // shift-alt-enter to accept & go to previous
  enterMod: 'altKey',

  // if true, the next button will stop on the last
  // keyboard input/textarea; prev button stops at first
  // if false, the next button will wrap to target the
  // first input/textarea; prev will go to the last
  stopAtEnd: true,

  // Set this to append the keyboard immediately after the
  // input/textarea it is attached to. This option works
  // best when the input container doesn't have a set width
  // and when the "tabNavigation" option is true
  appendLocally: false,
  // When appendLocally is false, the keyboard will be appended
  // to this object
  appendTo: 'body',

  // If false, the shift key will remain active until the
  // next key is (mouse) clicked on; if true it will stay
  // active until pressed again
  stickyShift: false,

  // Prevent pasting content into the area
  preventPaste: false,

  // caret places at the end of any text
  caretToEnd: false,

  // caret stays this many pixels from the edge of the input
  // while scrolling left/right; use "c" or "center" to center
  // the caret while scrolling
  scrollAdjustment: 10,

  // Set the max number of characters allowed in the input,
  // setting it to false disables this option
  maxLength: false,
  // allow inserting characters @ caret when maxLength is set
  maxInsert: true,

  // Mouse repeat delay - when clicking/touching a virtual
  // keyboard key, after this delay the key will start
  // repeating
  repeatDelay: 500,

  // Mouse repeat rate - after the repeatDelay, this is the
  // rate (characters per second) at which the key is
  // repeated. Added to simulate holding down a real keyboard
  // key and having it repeat. I haven't calculated the upper
  // limit of this rate, but it is limited to how fast the
  // javascript can process the keys. And for me, in Firefox,
  // it's around 20.
  repeatRate: 20,

  // resets the keyboard to the default keyset when visible
  resetDefault: false,

  // Event (namespaced) on the input to reveal the keyboard.
  // To disable it, just set it to ''.
  openOn: 'focus',

  // Event (namepaced) for when the character is added to the
  // input (clicking on the keyboard)
  keyBinding: 'mousedown touchstart',

  // enable/disable mousewheel functionality
  // enabling still depends on the mousewheel plugin
  useWheel: true,

  // combos (emulate dead keys)
  // if user inputs `a the script converts it to à,
  // ^o becomes ô, etc.
  useCombos: true,
  // if you add a new combo, you will need to update the
  // regex below
  combos: {
    // uncomment out the next line, then read the Combos
    //Regex section below
    '<': { 3: '\u2665' }, // turn <3 into ♥ - change regex below
    'a': { e: "\u00e6" }, // ae ligature
    'A': { E: "\u00c6" },
    'o': { e: "\u0153" }, // oe ligature
    'O': { E: "\u0152" }
  },

  // *** Methods ***
  // Callbacks - attach a function to any of these
  // callbacks as desired
  initialized: function(e, keyboard, el) {},
  beforeVisible: function(e, keyboard, el) {},
  visible: function(e, keyboard, el) {},
  beforeInsert: function(e, keyboard, el, textToAdd) { return textToAdd; },
  change: function(e, keyboard, el) {},
  beforeClose: function(e, keyboard, el, accepted) {},
  accepted: function(e, keyboard, el) {},
  canceled: function(e, keyboard, el) {},
  restricted: function(e, keyboard, el) {},
  hidden: function(e, keyboard, el) {},

  // called instead of base.switchInput
  // Go to next or prev inputs
  // goToNext = true, then go to next input;
  //   if false go to prev
  // isAccepted is from autoAccept option or
  //   true if user presses shift-enter
  switchInput: function(keyboard, goToNext, isAccepted) {},

  /*
          // build key callback
      buildKey : function( keyboard, data ) {
            /* data = {
              // READ ONLY
              isAction: [boolean] true if key is an action key
            // name... may include decimal ascii value of character
              // prefix = 'ui-keyboard-'
              name    : [string]  key class name suffix
              value   : [string]  text inserted (non-action keys)
              title   : [string]  title attribute of key
              action  : [string]  keyaction name
              // html includes a <span> wrapping the text
              html    : [string]  HTML of the key;
              // DO NOT MODIFY THE ABOVE SETTINGS

            // use to modify key HTML
              $key    : [object]  jQuery selector of key already added to keyboard
            }
            * /
            data.$key.html('<span class="ui-keyboard-text">Foo</span>');
            return data;
            },
          */

  // this callback is called just before the "beforeClose"
  // to check the value if the value is valid, return true
  // and the keyboard will continue as it should (close if
  // not always open, etc)
  // if the value is not value, return false and the clear
  // the keyboard value ( like this
  // "keyboard.$preview.val('');" ), if desired
  validate: function(keyboard, value, isClosing) {
    return true;
  }

})
// activate the typing extension
.addTyping({
  showTyping: true,
  delay: 250
});

/ Combos Regex - You could change $.keyboard.comboRegex so that it applies to all keyboards, but if a specific layout language has a comboRegex defined, it has precidence over this setting. This regex is used to match combos to combine, the first part looks for the accent/letter and the second part matches the following letter so lets say you want to do something crazy like turn "<3" into a heart. Add this to the combos '<' : { 3: "\u2665" } and add a comma to the end if needed. Then change the regex to this: /([<`\'~\^\"ao])([a-z3])/mig; ( first part )( 2nd ) / $.keyboard.language.love.comboRegex = /([<`\'~\^\"ao])([a-z3])/mig;

/ if you look close at the regex, all that was added was "<" to the beginning of the first part; some characters need to be escaped with a backslash in front because they mean something else in regex. The second part has a 3 added after the 'a-z', so that should cover both parts :P /

// Typing Extension $('#icon').click(function() { var kb = $('#keyboard').getkeyboard(); // typeIn( text, delay, callback ); kb.reveal().typeIn(simulateTyping, 500, function() { // do something after text is added // kb.accept(); }); });

}); `

and add a second keyboard input inside the div (in the html part): <input id="keyboard2" type="text" />

!important make the input via physical keyboard. jquery_keyboard_issue

pilz97 commented 2 years ago

I found an workarround but it's not that beautiful.

I modified the beforeInsert function: Here i check that there has to be a delay between the inputs from more than 50ms

beforeInsert: (e, keyboard, el, textToAdd) => {
  if (this.elementRef === undefined) {
    return;
  }

  if (!this.alwaysOpen) {
  }

  const inputDelay = new Date().getTime() - this.lastInput.getTime();
  console.log('inout delay', inputDelay, textToAdd);
  if (inputDelay < 50) {
    return '';
  }
  this.lastInput = new Date();

  return textToAdd;
},
Mottie commented 2 years ago

Hi @pilz97!

I still can't duplicate this issue (Windows 10 using Chrome, Brave, Firefox and Edge). I'm starting to wonder if you have a gaming keyboard or something that repeats keypresses faster? What OS & browser are you running it on?

pilz97 commented 2 years ago

Hi @Mottie,

thx for your anwer. I tried it with google chrome on windows 11 with a "normal" dell bluetooth keyboard. The issue only occures when there are more than one "virtual" keyboards with always open option.