mirror of
https://github.com/hodgef/simple-keyboard.git
synced 2025-02-21 00:23:02 +08:00
Allow CandidateBox caret positioning, passing event though onChange. Per https://github.com/hodgef/react-simple-keyboard/issues/1298
This commit is contained in:
parent
c6f12f0f92
commit
3032bef324
@ -1,6 +1,6 @@
|
||||
/*!
|
||||
*
|
||||
* simple-keyboard v3.1.57
|
||||
* simple-keyboard v3.2.0
|
||||
* https://github.com/hodgef/simple-keyboard
|
||||
*
|
||||
* Copyright (c) Francisco Hodge (https://github.com/hodgef) and project contributors.
|
||||
|
File diff suppressed because one or more lines are too long
12
build/types/interfaces.d.ts
vendored
12
build/types/interfaces.d.ts
vendored
@ -22,14 +22,14 @@ export declare type CandidateBoxParams = {
|
||||
export declare type CandidateBoxShowParams = {
|
||||
candidateValue: string;
|
||||
targetElement: KeyboardElement;
|
||||
onSelect: (selectedCandidate: string) => void;
|
||||
onSelect: (selectedCandidate: string, e: MouseEvent) => void;
|
||||
};
|
||||
export declare type CandidateBoxRenderParams = {
|
||||
candidateListPages: string[][];
|
||||
targetElement: KeyboardElement;
|
||||
pageIndex: number;
|
||||
nbPages: number;
|
||||
onItemSelected: (selectedCandidate: string) => void;
|
||||
onItemSelected: (selectedCandidate: string, e: MouseEvent) => void;
|
||||
};
|
||||
export declare type KeyboardElement = HTMLDivElement | HTMLButtonElement;
|
||||
export declare type KeyboardHandlerEvent = any;
|
||||
@ -195,6 +195,14 @@ export interface KeyboardOptions {
|
||||
* Executes the callback function once simple-keyboard is rendered for the first time (on initialization).
|
||||
*/
|
||||
onInit?: (instance?: SimpleKeyboard) => void;
|
||||
/**
|
||||
* Retrieves the current input
|
||||
*/
|
||||
onChange?: (input: string, e?: MouseEvent) => any;
|
||||
/**
|
||||
* Retrieves all inputs
|
||||
*/
|
||||
onChangeAll?: (inputObj: KeyboardInput, e?: MouseEvent) => any;
|
||||
/**
|
||||
* Module options can have any format
|
||||
*/
|
||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-keyboard",
|
||||
"version": "3.1.57",
|
||||
"version": "3.2.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-keyboard",
|
||||
"version": "3.1.57",
|
||||
"version": "3.2.0",
|
||||
"description": "On-screen Javascript Virtual Keyboard",
|
||||
"main": "build/index.js",
|
||||
"types": "build/types/index.d.ts",
|
||||
|
81
src/demo/CandidateBoxDemo.js
Normal file
81
src/demo/CandidateBoxDemo.js
Normal file
@ -0,0 +1,81 @@
|
||||
import Keyboard from "../lib";
|
||||
import "./css/CandidateBoxDemo.css";
|
||||
|
||||
const setDOM = () => {
|
||||
document.querySelector("body").innerHTML = `
|
||||
<input class="input" placeholder="Tap on the virtual keyboard to start" />
|
||||
<div class="simple-keyboard"></div>
|
||||
`;
|
||||
};
|
||||
|
||||
class Demo {
|
||||
constructor() {
|
||||
setDOM();
|
||||
|
||||
/**
|
||||
* Demo Start
|
||||
*/
|
||||
this.keyboard = new Keyboard({
|
||||
onChange: input => this.onChange(input),
|
||||
onKeyPress: button => this.onKeyPress(button),
|
||||
preventMouseDownDefault: true,
|
||||
layoutCandidates: {
|
||||
ni: "你 尼",
|
||||
hao: "好 号"
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Update simple-keyboard when input is changed directly
|
||||
*/
|
||||
document.querySelector(".input").addEventListener("input", event => {
|
||||
this.keyboard.setInput(event.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
onChange(input) {
|
||||
const inputElement = document.querySelector(".input");
|
||||
|
||||
/**
|
||||
* Updating input's value
|
||||
*/
|
||||
inputElement.value = input;
|
||||
console.log("Input changed", input);
|
||||
|
||||
/**
|
||||
* Synchronizing input caret position
|
||||
*/
|
||||
const caretPosition = this.keyboard.caretPosition;
|
||||
if (caretPosition !== null)
|
||||
this.setInputCaretPosition(inputElement, caretPosition);
|
||||
|
||||
console.log("caretPosition", caretPosition);
|
||||
}
|
||||
|
||||
setInputCaretPosition(elem, pos) {
|
||||
if (elem.setSelectionRange) {
|
||||
elem.focus();
|
||||
elem.setSelectionRange(pos, pos);
|
||||
}
|
||||
}
|
||||
|
||||
onKeyPress(button) {
|
||||
console.log("Button pressed", button);
|
||||
|
||||
/**
|
||||
* If you want to handle the shift and caps lock buttons
|
||||
*/
|
||||
if (button === "{shift}" || button === "{lock}") this.handleShift();
|
||||
}
|
||||
|
||||
handleShift() {
|
||||
const currentLayout = this.keyboard.options.layoutName;
|
||||
const shiftToggle = currentLayout === "default" ? "shift" : "default";
|
||||
|
||||
this.keyboard.setOptions({
|
||||
layoutName: shiftToggle
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Demo;
|
12
src/demo/css/CandidateBoxDemo.css
Normal file
12
src/demo/css/CandidateBoxDemo.css
Normal file
@ -0,0 +1,12 @@
|
||||
input {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
padding: 20px;
|
||||
font-size: 20px;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.simple-keyboard {
|
||||
max-width: 850px;
|
||||
}
|
@ -9,6 +9,7 @@ import BasicDemo from "./BasicDemo";
|
||||
//import DOMElementDemo from "./DOMElementDemo";
|
||||
//import FullKeyboardDemo from "./FullKeyboardDemo";
|
||||
//import MultipleKeyboardsDemo from "./MultipleKeyboardsDestroyDemo";
|
||||
//import CandidateBoxDemo from "./CandidateBoxDemo";
|
||||
|
||||
/**
|
||||
* Selected demo
|
||||
|
36
src/demo/tests/CandidateBoxDemo.test.js
Normal file
36
src/demo/tests/CandidateBoxDemo.test.js
Normal file
@ -0,0 +1,36 @@
|
||||
import { setDOM } from '../../utils/TestUtility';
|
||||
import CandidateBoxDemo from '../CandidateBoxDemo';
|
||||
|
||||
it('Demo will load', () => {
|
||||
setDOM();
|
||||
|
||||
new CandidateBoxDemo();
|
||||
});
|
||||
|
||||
it('Demo caret positioning will adjust accordingly', () => {
|
||||
setDOM();
|
||||
|
||||
const demo = new CandidateBoxDemo();
|
||||
|
||||
demo.keyboard.setCaretPosition(0);
|
||||
|
||||
demo.keyboard.getButtonElement("n").click();
|
||||
demo.keyboard.getButtonElement("h").click();
|
||||
demo.keyboard.getButtonElement("a").click();
|
||||
demo.keyboard.getButtonElement("o").click();
|
||||
expect(demo.keyboard.getCaretPosition()).toBe(4);
|
||||
|
||||
demo.keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
|
||||
|
||||
expect(demo.keyboard.getCaretPosition()).toBe(2);
|
||||
|
||||
demo.keyboard.setCaretPosition(1);
|
||||
demo.keyboard.getButtonElement("i").click();
|
||||
|
||||
expect(demo.keyboard.getCaretPosition()).toBe(2);
|
||||
|
||||
demo.keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
|
||||
|
||||
expect(demo.keyboard.getCaretPosition()).toBe(1);
|
||||
expect(demo.keyboard.getInput()).toBe("你好");
|
||||
});
|
@ -45,8 +45,8 @@ class CandidateBox {
|
||||
targetElement,
|
||||
pageIndex: this.pageIndex,
|
||||
nbPages: candidateListPages.length,
|
||||
onItemSelected: (selectedCandidate: string) => {
|
||||
onSelect(selectedCandidate);
|
||||
onItemSelected: (selectedCandidate: string, e: MouseEvent) => {
|
||||
onSelect(selectedCandidate, e);
|
||||
this.destroy();
|
||||
},
|
||||
});
|
||||
@ -73,9 +73,18 @@ class CandidateBox {
|
||||
// Create Candidate box list items
|
||||
candidateListPages[pageIndex].forEach((candidateListItem) => {
|
||||
const candidateListLIElement = document.createElement("li");
|
||||
const getMouseEvent = () => {
|
||||
const mouseEvent = new MouseEvent("click");
|
||||
Object.defineProperty(mouseEvent, "target", {
|
||||
value: candidateListLIElement,
|
||||
});
|
||||
return mouseEvent;
|
||||
};
|
||||
|
||||
candidateListLIElement.className = "hg-candidate-box-list-item";
|
||||
candidateListLIElement.textContent = candidateListItem;
|
||||
candidateListLIElement.onclick = () => onItemSelected(candidateListItem);
|
||||
candidateListLIElement.onclick = (e = getMouseEvent()) =>
|
||||
onItemSelected(candidateListItem, e);
|
||||
|
||||
// Append list item to ul
|
||||
candidateListULElement.appendChild(candidateListLIElement);
|
||||
|
@ -320,8 +320,10 @@ class SimpleKeyboard {
|
||||
|
||||
const layoutCandidates = Object.keys(layoutCandidatesObj).filter(
|
||||
(layoutCandidate: string) => {
|
||||
const inputSubstr =
|
||||
input.substring(0, this.getCaretPositionEnd() || 0) || input;
|
||||
const regexp = new RegExp(`${layoutCandidate}$`, "g");
|
||||
const matches = [...input.matchAll(regexp)];
|
||||
const matches = [...inputSubstr.matchAll(regexp)];
|
||||
return !!matches.length;
|
||||
}
|
||||
);
|
||||
@ -359,21 +361,37 @@ class SimpleKeyboard {
|
||||
this.candidateBox.show({
|
||||
candidateValue,
|
||||
targetElement,
|
||||
onSelect: (selectedCandidate: string) => {
|
||||
onSelect: (selectedCandidate: string, e: MouseEvent) => {
|
||||
const currentInput = this.getInput(this.options.inputName, true);
|
||||
const initialCaretPosition = this.getCaretPositionEnd() || 0;
|
||||
const inputSubstr =
|
||||
currentInput.substring(0, initialCaretPosition || 0) ||
|
||||
currentInput;
|
||||
|
||||
const regexp = new RegExp(`${candidateKey}$`, "g");
|
||||
const newInput = currentInput.replace(regexp, selectedCandidate);
|
||||
const newInputSubstr = inputSubstr.replace(regexp, selectedCandidate);
|
||||
const newInput = currentInput.replace(inputSubstr, newInputSubstr);
|
||||
|
||||
const caretPositionDiff = newInputSubstr.length - inputSubstr.length;
|
||||
let newCaretPosition =
|
||||
(initialCaretPosition || currentInput.length) + caretPositionDiff;
|
||||
|
||||
if (newCaretPosition < 0) newCaretPosition = 0;
|
||||
|
||||
this.setInput(newInput, this.options.inputName, true);
|
||||
this.setCaretPosition(newCaretPosition);
|
||||
|
||||
if (typeof this.options.onChange === "function")
|
||||
this.options.onChange(this.getInput(this.options.inputName, true));
|
||||
this.options.onChange(
|
||||
this.getInput(this.options.inputName, true),
|
||||
e
|
||||
);
|
||||
|
||||
/**
|
||||
* Calling onChangeAll
|
||||
*/
|
||||
if (typeof this.options.onChangeAll === "function")
|
||||
this.options.onChangeAll(this.getAllInputs());
|
||||
this.options.onChangeAll(this.getAllInputs(), e);
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -429,7 +447,7 @@ class SimpleKeyboard {
|
||||
* Calling onKeyPress
|
||||
*/
|
||||
if (typeof this.options.onKeyPress === "function")
|
||||
this.options.onKeyPress(button);
|
||||
this.options.onKeyPress(button, e);
|
||||
|
||||
if (
|
||||
// If input will change as a result of this button press
|
||||
@ -483,13 +501,13 @@ class SimpleKeyboard {
|
||||
* Calling onChange
|
||||
*/
|
||||
if (typeof this.options.onChange === "function")
|
||||
this.options.onChange(this.getInput(this.options.inputName, true));
|
||||
this.options.onChange(this.getInput(this.options.inputName, true), e);
|
||||
|
||||
/**
|
||||
* Calling onChangeAll
|
||||
*/
|
||||
if (typeof this.options.onChangeAll === "function")
|
||||
this.options.onChangeAll(this.getAllInputs());
|
||||
this.options.onChangeAll(this.getAllInputs(), e);
|
||||
|
||||
/**
|
||||
* Check if this new input has candidates (suggested words)
|
||||
|
@ -216,7 +216,7 @@ it('CandidateBox select candidate will work', () => {
|
||||
keyboard.getButtonElement("a").click();
|
||||
keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
|
||||
|
||||
expect(onSelect).toBeCalledWith("1");
|
||||
expect(onSelect).toBeCalledWith("1", expect.anything());
|
||||
keyboard.destroy();
|
||||
});
|
||||
|
||||
@ -353,6 +353,7 @@ it('CandidateBox selection should trigger onChange', () => {
|
||||
});
|
||||
|
||||
const candidateBoxRenderFn = keyboard.candidateBox.renderPage;
|
||||
|
||||
jest.spyOn(keyboard.candidateBox, "renderPage").mockImplementation((params) => {
|
||||
candidateBoxOnItemSelected = params.onItemSelected;
|
||||
params.onItemSelected = onSelect;
|
||||
@ -362,10 +363,10 @@ it('CandidateBox selection should trigger onChange', () => {
|
||||
keyboard.getButtonElement("a").click();
|
||||
keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
|
||||
|
||||
expect(keyboard.options.onChange).toBeCalledWith("a");
|
||||
expect(keyboard.options.onChangeAll).toBeCalledWith({"default": "a"});
|
||||
expect(keyboard.options.onChange.mock.calls[0][0]).toBe("a");
|
||||
expect(keyboard.options.onChangeAll.mock.calls[0][0]).toMatchObject({"default": "a"});
|
||||
|
||||
expect(keyboard.options.onChange).toBeCalledWith("1");
|
||||
expect(keyboard.options.onChangeAll).toBeCalledWith({"default": "1"});
|
||||
expect(keyboard.options.onChange.mock.calls[1][0]).toBe("1");
|
||||
expect(keyboard.options.onChangeAll.mock.calls[1][0]).toMatchObject({"default": "1"});
|
||||
keyboard.destroy();
|
||||
});
|
@ -31,7 +31,7 @@ export type CandidateBoxShowParams = {
|
||||
candidateValue: string,
|
||||
targetElement: KeyboardElement,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onSelect: (selectedCandidate: string) => void
|
||||
onSelect: (selectedCandidate: string, e: MouseEvent) => void
|
||||
}
|
||||
|
||||
export type CandidateBoxRenderParams = {
|
||||
@ -40,7 +40,7 @@ export type CandidateBoxRenderParams = {
|
||||
pageIndex: number;
|
||||
nbPages: number;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onItemSelected: (selectedCandidate: string) => void
|
||||
onItemSelected: (selectedCandidate: string, e: MouseEvent) => void
|
||||
}
|
||||
|
||||
export type KeyboardElement = HTMLDivElement | HTMLButtonElement;
|
||||
@ -240,6 +240,16 @@ export interface KeyboardOptions {
|
||||
*/
|
||||
onInit?: (instance?: SimpleKeyboard) => void;
|
||||
|
||||
/**
|
||||
* Retrieves the current input
|
||||
*/
|
||||
onChange?: (input: string, e?: MouseEvent) => any;
|
||||
|
||||
/**
|
||||
* Retrieves all inputs
|
||||
*/
|
||||
onChangeAll?: (inputObj: KeyboardInput, e?: MouseEvent) => any;
|
||||
|
||||
/**
|
||||
* Module options can have any format
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user