Merge pull request #2277 from XcrossD/patch-1

Expose a method to change keyboard instance internal state before input value change
This commit is contained in:
Francisco Hodge 2024-05-20 17:32:22 -04:00 committed by GitHub
commit 20b4fd5ca3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 151 additions and 83 deletions

View File

@ -118,6 +118,7 @@ class SimpleKeyboard {
* @property {function(input: string):string} onChange Executes the callback function on input change. Returns the current inputs string.
* @property {function} onRender Executes the callback function every time simple-keyboard is rendered (e.g: when you change layouts).
* @property {function} onInit Executes the callback function once simple-keyboard is rendered for the first time (on initialization).
* @property {function(keyboard: Keyboard):void} beforeInputUpdate Perform an action before any input change
* @property {function(inputs: object):object} onChangeAll Executes the callback function on input change. Returns the input object with all defined inputs.
* @property {boolean} useButtonTag Render buttons as a button element instead of a div element.
* @property {boolean} disableCaretPositioning A prop to ensure characters are always be added/removed at the end of the string.
@ -393,6 +394,13 @@ class SimpleKeyboard {
candidateStr = selectedCandidate.normalize("NFD");
}
/**
* Perform an action before any input change
*/
if (typeof this.options.beforeInputUpdate === "function") {
this.options.beforeInputUpdate(this);
}
const currentInput = this.getInput(this.options.inputName, true);
const initialCaretPosition = this.getCaretPositionEnd() || 0;
const inputSubstr =
@ -458,6 +466,13 @@ class SimpleKeyboard {
*/
if (!this.input[inputName]) this.input[inputName] = "";
/**
* Perform an action before any input change
*/
if (typeof this.options.beforeInputUpdate === "function") {
this.options.beforeInputUpdate(this);
}
/**
* Calculating new input
*/

View File

@ -332,98 +332,133 @@ it('CandidateBox show not be called if keyboard.candidateBox is undefined upon s
});
it('CandidateBox selection should trigger onChange', () => {
const keyboard = new Keyboard({
layout: {
default: [
"a b {bksp}"
]
},
layoutCandidates: {
a: "1 2 3 4 5 6"
},
onChange: jest.fn(),
onChangeAll: jest.fn()
});
let candidateBoxOnItemSelected;
const onSelect = jest.fn().mockImplementation((selectedCandidate) => {
candidateBoxOnItemSelected(selectedCandidate);
keyboard.candidateBox.destroy();
});
const candidateBoxRenderFn = keyboard.candidateBox.renderPage;
jest.spyOn(keyboard.candidateBox, "renderPage").mockImplementation((params) => {
candidateBoxOnItemSelected = params.onItemSelected;
params.onItemSelected = onSelect;
candidateBoxRenderFn(params);
});
keyboard.getButtonElement("a").click();
keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
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.mock.calls[1][0]).toBe("1");
expect(keyboard.options.onChangeAll.mock.calls[1][0]).toMatchObject({"default": "1"});
keyboard.destroy();
const keyboard = new Keyboard({
layout: {
default: [
"a b {bksp}"
]
},
layoutCandidates: {
a: "1 2 3 4 5 6"
},
onChange: jest.fn(),
onChangeAll: jest.fn()
});
it('CandidateBox normalization will work', () => {
const keyboard = new Keyboard({
layout: {
default: [
"a b {bksp}"
]
},
layoutCandidates: {
a: "신"
},
onChange: jest.fn(),
onChangeAll: jest.fn()
});
let candidateBoxOnItemSelected;
let candidateBoxOnItemSelected;
const onSelect = jest.fn().mockImplementation((selectedCandidate) => {
candidateBoxOnItemSelected(selectedCandidate);
keyboard.candidateBox.destroy();
});
const onSelect = jest.fn().mockImplementation((selectedCandidate) => {
candidateBoxOnItemSelected(selectedCandidate);
keyboard.candidateBox.destroy();
});
const candidateBoxRenderFn = keyboard.candidateBox.renderPage;
const candidateBoxRenderFn = keyboard.candidateBox.renderPage;
jest.spyOn(keyboard.candidateBox, "renderPage").mockImplementation((params) => {
candidateBoxOnItemSelected = params.onItemSelected;
params.onItemSelected = onSelect;
candidateBoxRenderFn(params);
});
jest.spyOn(keyboard.candidateBox, "renderPage").mockImplementation((params) => {
candidateBoxOnItemSelected = params.onItemSelected;
params.onItemSelected = onSelect;
candidateBoxRenderFn(params);
});
keyboard.getButtonElement("a").click();
keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
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.mock.calls[1][0]).toBe("1");
expect(keyboard.options.onChangeAll.mock.calls[1][0]).toMatchObject({"default": "1"});
keyboard.destroy();
});
it('CandidateBox selection should trigger beforeInputChange', () => {
const keyboard = new Keyboard({
layout: {
default: [
"a b {bksp}"
]
},
layoutCandidates: {
a: "1 2 3 4 5 6"
},
beforeInputUpdate: jest.fn(),
});
let candidateBoxOnItemSelected;
keyboard.getButtonElement("a").click();
keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
const onSelect = jest.fn().mockImplementation((selectedCandidate) => {
candidateBoxOnItemSelected(selectedCandidate);
keyboard.candidateBox.destroy();
});
const candidateBoxRenderFn = keyboard.candidateBox.renderPage;
expect(keyboard.options.onChange.mock.calls[0][0]).toBe("a");
expect(keyboard.options.onChangeAll.mock.calls[0][0]).toMatchObject({"default": "a"});
jest.spyOn(keyboard.candidateBox, "renderPage").mockImplementation((params) => {
candidateBoxOnItemSelected = params.onItemSelected;
params.onItemSelected = onSelect;
candidateBoxRenderFn(params);
});
// Selected candidate will be normalized
expect(keyboard.options.onChange.mock.calls[1][0]).toBe("신");
expect(keyboard.options.onChange.mock.calls[1][0].length).toBe(3);
expect(keyboard.options.onChangeAll.mock.calls[1][0]).toMatchObject({"default": "신"});
keyboard.getButtonElement("a").click();
keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
// Selected candidate will not be normalized
keyboard.clearInput();
keyboard.setOptions({ disableCandidateNormalization: true });
expect(keyboard.options.beforeInputUpdate.mock.calls[0][0]).toMatchObject(keyboard);
keyboard.destroy();
});
keyboard.getButtonElement("a").click();
keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
it('CandidateBox normalization will work', () => {
const keyboard = new Keyboard({
layout: {
default: [
"a b {bksp}"
]
},
layoutCandidates: {
a: "신"
},
onChange: jest.fn(),
onChangeAll: jest.fn()
});
let candidateBoxOnItemSelected;
expect(keyboard.options.onChange.mock.calls[2][0]).toBe("a");
expect(keyboard.options.onChangeAll.mock.calls[2][0]).toMatchObject({"default": "a"});
const onSelect = jest.fn().mockImplementation((selectedCandidate) => {
candidateBoxOnItemSelected(selectedCandidate);
keyboard.candidateBox.destroy();
});
expect(keyboard.options.onChange.mock.calls[3][0]).toBe("신");
expect(keyboard.options.onChange.mock.calls[3][0].length).toBe(1);
expect(keyboard.options.onChangeAll.mock.calls[3][0]).toMatchObject({"default": "신"});
const candidateBoxRenderFn = keyboard.candidateBox.renderPage;
jest.spyOn(keyboard.candidateBox, "renderPage").mockImplementation((params) => {
candidateBoxOnItemSelected = params.onItemSelected;
params.onItemSelected = onSelect;
candidateBoxRenderFn(params);
});
keyboard.destroy();
});
keyboard.getButtonElement("a").click();
keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
expect(keyboard.options.onChange.mock.calls[0][0]).toBe("a");
expect(keyboard.options.onChangeAll.mock.calls[0][0]).toMatchObject({"default": "a"});
// Selected candidate will be normalized
expect(keyboard.options.onChange.mock.calls[1][0]).toBe("신");
expect(keyboard.options.onChange.mock.calls[1][0].length).toBe(3);
expect(keyboard.options.onChangeAll.mock.calls[1][0]).toMatchObject({"default": "신"});
// Selected candidate will not be normalized
keyboard.clearInput();
keyboard.setOptions({ disableCandidateNormalization: true });
keyboard.getButtonElement("a").click();
keyboard.candidateBox.candidateBoxElement.querySelector("li").click();
expect(keyboard.options.onChange.mock.calls[2][0]).toBe("a");
expect(keyboard.options.onChangeAll.mock.calls[2][0]).toMatchObject({"default": "a"});
expect(keyboard.options.onChange.mock.calls[3][0]).toBe("신");
expect(keyboard.options.onChange.mock.calls[3][0].length).toBe(1);
expect(keyboard.options.onChangeAll.mock.calls[3][0]).toMatchObject({"default": "신"});
keyboard.destroy();
});

View File

@ -218,6 +218,19 @@ it('Keyboard onChange will work', () => {
expect(output).toBe("q");
});
it('Keyboard beforeInputChange will work', () => {
const mockBeforeInputUpdate = jest.fn();
const keyboard = new Keyboard({
beforeInputUpdate: mockBeforeInputUpdate,
useMouseEvents: true
});
keyboard.getButtonElement("q").onclick();
expect(mockBeforeInputUpdate).toHaveBeenCalledWith(keyboard);
});
it('Keyboard onChangeAll will work', () => {
let output;

View File

@ -291,6 +291,11 @@ export interface KeyboardOptions {
*/
onKeyReleased?: (button: string, e?: MouseEvent) => any;
/**
* Executes the callback function before an input is about to be updated
*/
beforeInputUpdate?: (instance: SimpleKeyboard) => void;
/**
* Module options can have any format
*/