diff --git a/src/lib/components/Keyboard.ts b/src/lib/components/Keyboard.ts index cc5479dd..76abcfdd 100644 --- a/src/lib/components/Keyboard.ts +++ b/src/lib/components/Keyboard.ts @@ -118,6 +118,7 @@ class SimpleKeyboard { * @property {function(input: string):string} onChange Executes the callback function on input change. Returns the current input’s 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 */ diff --git a/src/lib/components/tests/CandidateBox.test.js b/src/lib/components/tests/CandidateBox.test.js index 390991a0..98e89d1b 100644 --- a/src/lib/components/tests/CandidateBox.test.js +++ b/src/lib/components/tests/CandidateBox.test.js @@ -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(); - }); \ No newline at end of file + 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(); +}); \ No newline at end of file diff --git a/src/lib/components/tests/Keyboard.test.js b/src/lib/components/tests/Keyboard.test.js index bd372dff..ece2828b 100644 --- a/src/lib/components/tests/Keyboard.test.js +++ b/src/lib/components/tests/Keyboard.test.js @@ -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; diff --git a/src/lib/interfaces.ts b/src/lib/interfaces.ts index cba5aa9f..a7b735d0 100644 --- a/src/lib/interfaces.ts +++ b/src/lib/interfaces.ts @@ -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 */