From 2dc87f2e373ec35ceb4deaa057f15fac5ebac347 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 4 Nov 2025 10:06:42 +0100 Subject: [PATCH 01/17] refactor(web): move `isEmptyTransform` to @keymanapp/web-utils --- web/src/app/browser/src/configuration.ts | 2 +- web/src/app/webview/src/contextManager.ts | 4 ++-- web/src/engine/common/web-utils/build.sh | 1 + web/src/engine/common/web-utils/package.json | 3 +++ web/src/engine/common/web-utils/src/index.ts | 2 ++ .../engine/common/web-utils/src/isEmptyTransform.ts | 10 ++++++++++ web/src/engine/js-processor/src/outputTargetBase.ts | 9 --------- web/src/engine/main/src/headless/inputProcessor.ts | 3 +-- web/src/engine/osk/src/visualKeyboard.ts | 2 +- 9 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 web/src/engine/common/web-utils/src/isEmptyTransform.ts diff --git a/web/src/app/browser/src/configuration.ts b/web/src/app/browser/src/configuration.ts index 0b01bbe130..f76184dbd3 100644 --- a/web/src/app/browser/src/configuration.ts +++ b/web/src/app/browser/src/configuration.ts @@ -2,7 +2,7 @@ import { EngineConfiguration, InitOptionSpec, InitOptionDefaults } from "keyman/ import { OutputTargetElementWrapper as DOMOutputTarget } from 'keyman/engine/element-wrappers'; import { OutputTargetInterface, ProcessorAction } from 'keyman/engine/keyboard'; -import { isEmptyTransform } from 'keyman/engine/js-processor'; +import { isEmptyTransform } from '@keymanapp/web-utils'; import { AlertHost } from "./utils/alertHost.js"; import { whenDocumentReady } from "./utils/documentReady.js"; diff --git a/web/src/app/webview/src/contextManager.ts b/web/src/app/webview/src/contextManager.ts index b2c52de00e..bb8253587c 100644 --- a/web/src/app/webview/src/contextManager.ts +++ b/web/src/app/webview/src/contextManager.ts @@ -1,11 +1,11 @@ import { JSKeyboard, Keyboard, OutputTargetInterface, Transcription, TextTransform } from 'keyman/engine/keyboard'; // TODO-web-core: remove usage of OutputTargetBase, use OutputTargetInterface instead -import { Mock, findCommonSubstringEndIndex, isEmptyTransform, OutputTargetBase } from 'keyman/engine/js-processor'; +import { Mock, findCommonSubstringEndIndex, OutputTargetBase } from 'keyman/engine/js-processor'; import { KeyboardStub } from 'keyman/engine/keyboard-storage'; import { ContextManagerBase } from 'keyman/engine/main'; import { WebviewConfiguration } from './configuration.js'; import { LexicalModelTypes } from '@keymanapp/common-types'; -import { KMWString } from '@keymanapp/web-utils'; +import { KMWString, isEmptyTransform } from '@keymanapp/web-utils'; export type OnInsertTextFunc = (deleteLeft: number, text: string, deleteRight: number) => void; diff --git a/web/src/engine/common/web-utils/build.sh b/web/src/engine/common/web-utils/build.sh index 23d38cb952..69b08cb275 100755 --- a/web/src/engine/common/web-utils/build.sh +++ b/web/src/engine/common/web-utils/build.sh @@ -22,6 +22,7 @@ BUNDLE_CMD="node ${KEYMAN_ROOT}/web/src/tools/es-bundling/build/common-bundle.mj builder_describe \ "Compiles the web-oriented utility function module." \ "@/common/web/keyman-version" \ + "@/common/web/types" \ "@/web/src/tools/es-bundling" \ clean configure build test diff --git a/web/src/engine/common/web-utils/package.json b/web/src/engine/common/web-utils/package.json index 557878d43e..1df1836f75 100644 --- a/web/src/engine/common/web-utils/package.json +++ b/web/src/engine/common/web-utils/package.json @@ -29,6 +29,9 @@ "c8": "^7.12.0", "typescript": "^5.4.5" }, + "dependencies": { + "@keymanapp/common-types": "*" + }, "type": "module", "paths": { "@keymanapp/keyman-version": "*" diff --git a/web/src/engine/common/web-utils/src/index.ts b/web/src/engine/common/web-utils/src/index.ts index 30f4821590..343e1486e6 100644 --- a/web/src/engine/common/web-utils/src/index.ts +++ b/web/src/engine/common/web-utils/src/index.ts @@ -23,6 +23,8 @@ export { default as TimeoutPromise, timedPromise } from "./timeoutPromise.js"; export { default as PriorityQueue, QueueComparator } from "./priority-queue.js" +export { isEmptyTransform } from './isEmptyTransform.js'; + // // Uncomment the following line and run the bundled output to verify successful // // esbuild bundling of this submodule: // console.log(Version.CURRENT.toString()); diff --git a/web/src/engine/common/web-utils/src/isEmptyTransform.ts b/web/src/engine/common/web-utils/src/isEmptyTransform.ts new file mode 100644 index 0000000000..db48d1d7c5 --- /dev/null +++ b/web/src/engine/common/web-utils/src/isEmptyTransform.ts @@ -0,0 +1,10 @@ +import { LexicalModelTypes } from '@keymanapp/common-types'; + +// Also relies on string-extensions provided by the web-utils package. + +export function isEmptyTransform(transform: LexicalModelTypes.Transform) { + if (!transform) { + return true; + } + return transform.insert === '' && transform.deleteLeft === 0 && (transform.deleteRight ?? 0) === 0; +} diff --git a/web/src/engine/js-processor/src/outputTargetBase.ts b/web/src/engine/js-processor/src/outputTargetBase.ts index eca7c6766a..a5ed2c2227 100644 --- a/web/src/engine/js-processor/src/outputTargetBase.ts +++ b/web/src/engine/js-processor/src/outputTargetBase.ts @@ -8,15 +8,6 @@ import { type KeyEvent } from 'keyman/engine/keyboard'; import { Deadkey, DeadkeyTracker } from "./deadkeys.js"; import { LexicalModelTypes } from '@keymanapp/common-types'; -// Also relies on string-extensions provided by the web-utils package. - -export function isEmptyTransform(transform: LexicalModelTypes.Transform) { - if(!transform) { - return true; - } - return transform.insert === '' && transform.deleteLeft === 0 && (transform.deleteRight ?? 0) === 0; -} - export abstract class OutputTargetBase implements OutputTargetInterface { private _dks: DeadkeyTracker; diff --git a/web/src/engine/main/src/headless/inputProcessor.ts b/web/src/engine/main/src/headless/inputProcessor.ts index 90ce1787b2..70a0f8105f 100644 --- a/web/src/engine/main/src/headless/inputProcessor.ts +++ b/web/src/engine/main/src/headless/inputProcessor.ts @@ -3,7 +3,7 @@ import ContextWindow from "./contextWindow.js"; import { LanguageProcessor } from "./languageProcessor.js"; import type { ModelSpec, PathConfiguration } from "keyman/engine/interfaces"; -import { globalObject, DeviceSpec } from "@keymanapp/web-utils"; +import { globalObject, DeviceSpec, isEmptyTransform } from "@keymanapp/web-utils"; import { KM_Core } from 'keyman/engine/core-processor'; @@ -20,7 +20,6 @@ import { } from "keyman/engine/keyboard"; // TODO-web-core: remove usage of OutputTargetBase import { - isEmptyTransform, JSKeyboardProcessor, Mock, type ProcessorInitOptions, diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 6985edf192..124fedfcfc 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -15,7 +15,7 @@ import { timedPromise, ActiveKeyBase } from 'keyman/engine/keyboard'; -import { isEmptyTransform } from 'keyman/engine/js-processor'; +import { isEmptyTransform } from '@keymanapp/web-utils'; import { buildCorrectiveLayout } from './correctionLayout.js'; import { distributionFromDistanceMaps, keyTouchDistances } from './corrections.js'; From 71ed528fc829566ff439cd4c0b38d3b9de061bd0 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 4 Nov 2025 10:34:56 +0100 Subject: [PATCH 02/17] refactor(web): move files from js-processor to keyboard This change moves - deadkeys.ts - mock.ts - outputTargetBase.ts - stringDivergence.ts OutputTarget is the same regardless of whether it's a JS or KMX keyboard. --- web/src/app/webview/src/contextManager.ts | 3 +- .../src/outputTargetElementWrapper.ts | 2 +- web/src/engine/js-processor/src/index.ts | 5 -- .../js-processor/src/jsKeyboardInterface.ts | 8 ++-- .../js-processor/src/jsKeyboardProcessor.ts | 4 +- .../src/deadkeys.ts | 0 web/src/engine/keyboard/src/index.ts | 5 ++ .../{js-processor => keyboard}/src/mock.ts | 0 .../src/outputTargetBase.ts | 0 .../src/stringDivergence.ts | 0 .../engine/main/src/headless/contextWindow.ts | 2 +- .../main/src/headless/inputProcessor.ts | 10 ++-- .../main/src/headless/languageProcessor.ts | 3 +- web/src/engine/main/src/keymanEngineBase.ts | 4 +- .../element_interfaces.tests.ts | 3 +- .../element-wrappers/target_mocks.tests.ts | 3 +- .../cases/keyboard/domKeyboardLoader.tests.ts | 4 +- .../prediction/predictionContext.tests.js | 3 +- .../js-processor/bundled-module.tests.js | 47 ++++++++++--------- .../js-processor/engine/context.tests.js | 8 ++-- .../engine/notany_context.tests.js | 4 +- .../engine/js-processor/kbdInterface.tests.ts | 4 +- .../non-positional-rules.tests.js | 4 +- .../specialized-backspace.tests.js | 4 +- .../js-processor/transcriptions.tests.js | 2 +- .../engine/keyboard/keyboard-loading.tests.js | 4 +- .../keyboard/keyboardLoaderBase.tests.ts | 4 +- .../{js-processor => keyboard}/mocks.tests.js | 2 +- .../main/headless/inputProcessor.tests.js | 4 +- .../main/headless/languageProcessor.tests.js | 2 +- .../tools/testing/recorder-core/src/index.ts | 3 +- .../testing/recorder-core/src/nodeProctor.ts | 4 +- 32 files changed, 73 insertions(+), 82 deletions(-) rename web/src/engine/{js-processor => keyboard}/src/deadkeys.ts (100%) rename web/src/engine/{js-processor => keyboard}/src/mock.ts (100%) rename web/src/engine/{js-processor => keyboard}/src/outputTargetBase.ts (100%) rename web/src/engine/{js-processor => keyboard}/src/stringDivergence.ts (100%) rename web/src/test/auto/headless/engine/{js-processor => keyboard}/mocks.tests.js (98%) diff --git a/web/src/app/webview/src/contextManager.ts b/web/src/app/webview/src/contextManager.ts index bb8253587c..41623cecd5 100644 --- a/web/src/app/webview/src/contextManager.ts +++ b/web/src/app/webview/src/contextManager.ts @@ -1,6 +1,5 @@ -import { JSKeyboard, Keyboard, OutputTargetInterface, Transcription, TextTransform } from 'keyman/engine/keyboard'; +import { JSKeyboard, Keyboard, OutputTargetInterface, Transcription, TextTransform, Mock, findCommonSubstringEndIndex, OutputTargetBase } from 'keyman/engine/keyboard'; // TODO-web-core: remove usage of OutputTargetBase, use OutputTargetInterface instead -import { Mock, findCommonSubstringEndIndex, OutputTargetBase } from 'keyman/engine/js-processor'; import { KeyboardStub } from 'keyman/engine/keyboard-storage'; import { ContextManagerBase } from 'keyman/engine/main'; import { WebviewConfiguration } from './configuration.js'; diff --git a/web/src/engine/element-wrappers/src/outputTargetElementWrapper.ts b/web/src/engine/element-wrappers/src/outputTargetElementWrapper.ts index 4fd4771a60..59104f41e2 100644 --- a/web/src/engine/element-wrappers/src/outputTargetElementWrapper.ts +++ b/web/src/engine/element-wrappers/src/outputTargetElementWrapper.ts @@ -1,4 +1,4 @@ -import { OutputTargetBase } from "keyman/engine/js-processor"; +import { OutputTargetBase } from "keyman/engine/keyboard"; import { EventEmitter } from 'eventemitter3'; export abstract class OutputTargetElementWrapper extends OutputTargetBase { diff --git a/web/src/engine/js-processor/src/index.ts b/web/src/engine/js-processor/src/index.ts index 8968212c2f..67443d44bf 100644 --- a/web/src/engine/js-processor/src/index.ts +++ b/web/src/engine/js-processor/src/index.ts @@ -1,8 +1,3 @@ export { BeepHandler, JSKeyboardProcessor, LogMessageHandler, ProcessorInitOptions } from "./jsKeyboardProcessor.js"; export { JSKeyboardInterface, KeyInformation, StoreNonCharEntry } from "./jsKeyboardInterface.js"; -export * from "./deadkeys.js"; export { type ComplexKeyboardStore } from "./stores.js"; -export { OutputTargetBase } from "./outputTargetBase.js"; -export * from "./outputTargetBase.js"; -export { Mock } from "./mock.js"; -export * from "./stringDivergence.js"; diff --git a/web/src/engine/js-processor/src/jsKeyboardInterface.ts b/web/src/engine/js-processor/src/jsKeyboardInterface.ts index b365ecf4bc..8eeeee6d87 100644 --- a/web/src/engine/js-processor/src/jsKeyboardInterface.ts +++ b/web/src/engine/js-processor/src/jsKeyboardInterface.ts @@ -13,19 +13,19 @@ import { KeyboardHarness, KeyboardKeymanGlobal, KeyMapping, + Mock, MutableSystemStore, + ProcessorAction, SystemStore, SystemStoreIDs, + type Deadkey, type KeyEvent, + type OutputTargetBase, type OutputTargetInterface, - ProcessorAction, VariableStore, VariableStoreDictionary, VariableStoreSerializer, } from "keyman/engine/keyboard"; -import { type OutputTargetBase } from './outputTargetBase.js'; -import { type Deadkey } from './deadkeys.js'; -import { Mock } from "./mock.js"; import { PlatformSystemStore } from './platformSystemStore.js'; import { ComplexKeyboardStore, type KeyboardStore, KeyboardStoreElement } from "./stores.js"; diff --git a/web/src/engine/js-processor/src/jsKeyboardProcessor.ts b/web/src/engine/js-processor/src/jsKeyboardProcessor.ts index a3cac3aec9..8650114c47 100644 --- a/web/src/engine/js-processor/src/jsKeyboardProcessor.ts +++ b/web/src/engine/js-processor/src/jsKeyboardProcessor.ts @@ -11,10 +11,8 @@ import { ModifierKeyConstants } from '@keymanapp/common-types'; import { Codes, type JSKeyboard, MinimalKeymanGlobal, KeyEvent, Layouts, DefaultRules, EmulationKeystrokes, type MutableSystemStore, - OutputTargetInterface, ProcessorAction, SystemStoreIDs + OutputTargetInterface, ProcessorAction, SystemStoreIDs, Mock, type OutputTargetBase } from "keyman/engine/keyboard"; -import { Mock } from "./mock.js"; -import { type OutputTargetBase } from "./outputTargetBase.js"; import { JSKeyboardInterface } from './jsKeyboardInterface.js'; import { DeviceSpec, globalObject, KMWString } from "@keymanapp/web-utils"; diff --git a/web/src/engine/js-processor/src/deadkeys.ts b/web/src/engine/keyboard/src/deadkeys.ts similarity index 100% rename from web/src/engine/js-processor/src/deadkeys.ts rename to web/src/engine/keyboard/src/deadkeys.ts diff --git a/web/src/engine/keyboard/src/index.ts b/web/src/engine/keyboard/src/index.ts index 03987bceaf..2c43192690 100644 --- a/web/src/engine/keyboard/src/index.ts +++ b/web/src/engine/keyboard/src/index.ts @@ -33,6 +33,11 @@ export { OutputTargetInterface } from "./outputTargetInterface.js"; export { type SystemStoreMutationHandler, MutableSystemStore, SystemStore, SystemStoreIDs, type SystemStoreDictionary } from "./systemStore.js"; export { type VariableStore, VariableStoreSerializer, VariableStoreDictionary } from "./variableStore.js"; +export { Mock } from "./mock.js"; +export { OutputTargetBase } from "./outputTargetBase.js"; +export { findCommonSubstringEndIndex } from "./stringDivergence.js"; +export { Deadkey } from "./deadkeys.js"; + export * from "@keymanapp/web-utils"; // At the top level, there should be no default export. diff --git a/web/src/engine/js-processor/src/mock.ts b/web/src/engine/keyboard/src/mock.ts similarity index 100% rename from web/src/engine/js-processor/src/mock.ts rename to web/src/engine/keyboard/src/mock.ts diff --git a/web/src/engine/js-processor/src/outputTargetBase.ts b/web/src/engine/keyboard/src/outputTargetBase.ts similarity index 100% rename from web/src/engine/js-processor/src/outputTargetBase.ts rename to web/src/engine/keyboard/src/outputTargetBase.ts diff --git a/web/src/engine/js-processor/src/stringDivergence.ts b/web/src/engine/keyboard/src/stringDivergence.ts similarity index 100% rename from web/src/engine/js-processor/src/stringDivergence.ts rename to web/src/engine/keyboard/src/stringDivergence.ts diff --git a/web/src/engine/main/src/headless/contextWindow.ts b/web/src/engine/main/src/headless/contextWindow.ts index 4829263c7a..964fea5e20 100644 --- a/web/src/engine/main/src/headless/contextWindow.ts +++ b/web/src/engine/main/src/headless/contextWindow.ts @@ -1,5 +1,5 @@ import { LexicalModelTypes } from '@keymanapp/common-types'; -import { Mock } from "keyman/engine/js-processor"; +import { Mock } from "keyman/engine/keyboard"; import { KMWString } from '@keymanapp/web-utils'; export default class ContextWindow implements LexicalModelTypes.Context { diff --git a/web/src/engine/main/src/headless/inputProcessor.ts b/web/src/engine/main/src/headless/inputProcessor.ts index 70a0f8105f..2fd151e0ee 100644 --- a/web/src/engine/main/src/headless/inputProcessor.ts +++ b/web/src/engine/main/src/headless/inputProcessor.ts @@ -8,22 +8,22 @@ import { globalObject, DeviceSpec, isEmptyTransform } from "@keymanapp/web-utils import { KM_Core } from 'keyman/engine/core-processor'; import { - type Alternate, Codes, JSKeyboard, KeyboardMinimalInterface, + Mock, + OutputTargetBase, + ProcessorAction, + SystemStoreIDs, + type Alternate, type Keyboard, type KeyEvent, type OutputTargetInterface, - ProcessorAction, - SystemStoreIDs } from "keyman/engine/keyboard"; // TODO-web-core: remove usage of OutputTargetBase import { JSKeyboardProcessor, - Mock, type ProcessorInitOptions, - OutputTargetBase } from 'keyman/engine/js-processor'; import { TranscriptionCache } from "./transcriptionCache.js"; diff --git a/web/src/engine/main/src/headless/languageProcessor.ts b/web/src/engine/main/src/headless/languageProcessor.ts index f3029adf64..0751da2789 100644 --- a/web/src/engine/main/src/headless/languageProcessor.ts +++ b/web/src/engine/main/src/headless/languageProcessor.ts @@ -1,8 +1,7 @@ import { EventEmitter } from "eventemitter3"; import { LMLayer, WorkerFactory } from "@keymanapp/lexical-model-layer/web"; // TODO-web-core: remove use of OutputTargetBase -import { Mock, OutputTargetBase } from "keyman/engine/js-processor"; -import { Transcription, OutputTargetInterface } from 'keyman/engine/keyboard'; +import { Transcription, OutputTargetInterface, Mock, OutputTargetBase } from 'keyman/engine/keyboard'; import { LanguageProcessorEventMap, ModelSpec, StateChangeEnum, ReadySuggestions } from 'keyman/engine/interfaces'; import ContextWindow from "./contextWindow.js"; import { TranscriptionCache } from "./transcriptionCache.js"; diff --git a/web/src/engine/main/src/keymanEngineBase.ts b/web/src/engine/main/src/keymanEngineBase.ts index 37acf96f2a..3c88582daa 100644 --- a/web/src/engine/main/src/keymanEngineBase.ts +++ b/web/src/engine/main/src/keymanEngineBase.ts @@ -1,6 +1,6 @@ -import { type KeyEvent, JSKeyboard, Keyboard, KeyboardProperties, KeyboardKeymanGlobal, ProcessorAction } from "keyman/engine/keyboard"; +import { type KeyEvent, JSKeyboard, Keyboard, KeyboardProperties, KeyboardKeymanGlobal, ProcessorAction, OutputTargetBase } from "keyman/engine/keyboard"; // TODO-web-core: remove usage of OutputTargetBase -import { OutputTargetBase, ProcessorInitOptions } from 'keyman/engine/js-processor'; +import { ProcessorInitOptions } from 'keyman/engine/js-processor'; import { DOMKeyboardLoader as KeyboardLoader } from "keyman/engine/keyboard/dom-keyboard-loader"; import { WorkerFactory } from "@keymanapp/lexical-model-layer/web" import { InputProcessor } from './headless/inputProcessor.js'; diff --git a/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.tests.ts b/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.tests.ts index 47c82d37d9..99997f14d4 100644 --- a/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.tests.ts +++ b/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.tests.ts @@ -1,7 +1,6 @@ import { assert } from 'chai'; -import { KMWString } from 'keyman/engine/keyboard'; -import { Mock } from 'keyman/engine/js-processor'; +import { KMWString, Mock } from 'keyman/engine/keyboard'; import * as wrappers from 'keyman/engine/element-wrappers'; import { DynamicElements } from '../../test_utils.js'; diff --git a/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts b/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts index 8e13bd9495..5dc46da422 100644 --- a/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts +++ b/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts @@ -1,7 +1,6 @@ import { assert } from 'chai'; -import { KMWString } from 'keyman/engine/keyboard'; -import { Mock } from 'keyman/engine/js-processor'; +import { KMWString, Mock } from 'keyman/engine/keyboard'; import { Input } from 'keyman/engine/element-wrappers'; import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs'; diff --git a/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.tests.ts b/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.tests.ts index 38db889f61..8cc5493d4e 100644 --- a/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.tests.ts +++ b/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.tests.ts @@ -1,8 +1,8 @@ import { assert } from 'chai'; import { DOMKeyboardLoader } from 'keyman/engine/keyboard/dom-keyboard-loader'; -import { KeyboardHarness, JSKeyboard, MinimalKeymanGlobal, DeviceSpec, KeyboardKeymanGlobal, KeyboardDownloadError, KeyboardScriptError, Keyboard } from 'keyman/engine/keyboard'; -import { JSKeyboardInterface, Mock } from 'keyman/engine/js-processor'; +import { KeyboardHarness, JSKeyboard, MinimalKeymanGlobal, DeviceSpec, KeyboardKeymanGlobal, KeyboardDownloadError, KeyboardScriptError, Keyboard, Mock } from 'keyman/engine/keyboard'; +import { JSKeyboardInterface } from 'keyman/engine/js-processor'; import { assertThrowsAsync } from 'keyman/tools/testing/test-utils'; declare let window: typeof globalThis; diff --git a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.tests.js b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.tests.js index 6ec7a0bbd9..96b8d98a26 100644 --- a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.tests.js +++ b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.tests.js @@ -4,8 +4,7 @@ import sinon from 'sinon'; import { LanguageProcessor, TranscriptionCache } from 'keyman/engine/main'; import { PredictionContext } from 'keyman/engine/interfaces'; import { Worker as LMWorker } from "@keymanapp/lexical-model-layer/node"; -import { DeviceSpec } from 'keyman/engine/keyboard'; -import { Mock } from 'keyman/engine/js-processor'; +import { DeviceSpec, Mock } from 'keyman/engine/keyboard'; function compileDummyModel(suggestionSets) { return ` diff --git a/web/src/test/auto/headless/engine/js-processor/bundled-module.tests.js b/web/src/test/auto/headless/engine/js-processor/bundled-module.tests.js index 8b7c4d9588..0eceada34e 100644 --- a/web/src/test/auto/headless/engine/js-processor/bundled-module.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/bundled-module.tests.js @@ -1,7 +1,7 @@ import { assert } from "chai"; -import * as Package from "keyman/engine/js-processor"; -import * as Package2 from "keyman/engine/keyboard"; -const KMWString = Package2.KMWString; +import * as JSProcessorPackage from "keyman/engine/js-processor"; +import * as KeyboardPackage from "keyman/engine/keyboard"; +const KMWString = KeyboardPackage.KMWString; // A few small tests to ensure that the ES Module bundle was successfully constructed and is usable. @@ -17,14 +17,31 @@ let u = toSupplementaryPairString; describe('Bundled ES Module for js-processor', function() { describe('JSKeyboardProcessor', function () { it('should initialize without errors', function () { - let kp = new Package.JSKeyboardProcessor(); + let kp = new JSProcessorPackage.JSKeyboardProcessor(); assert.isNotNull(kp); }); }); +}); + +describe('Bundled ES Module for keyboard', function () { + describe('Keyboard', function () { + it('should initialize without errors', function () { + let kp = new KeyboardPackage.JSKeyboard(); + assert.isNotNull(kp); + }); + }); + + describe("Imported `utils`", function () { + it("should include `utils` package's Version class", () => { + let v16 = new KeyboardPackage.Version([16, 1]); + assert.equal(v16.toString(), "16.1"); + }); + }); + describe('Mock', () => { it('basic functionality test', () => { - let target = new Package.Mock("aple", 2); // ap | le + let target = new KeyboardPackage.Mock("aple", 2); // ap | le target.insertTextBeforeCaret('p'); assert.equal(target.getText(), "apple"); }); @@ -32,28 +49,12 @@ describe('Bundled ES Module for js-processor', function() { it('smp test', () => { KMWString.enableSupplementaryPlane(true); // Declared & defined in web-utils. try { - let target = new Package.Mock(u(0x1d5ba)+u(0x1d5c9)+u(0x1d5c5)+u(0x1d5be), 2); // ap | le + let target = new KeyboardPackage.Mock(u(0x1d5ba) + u(0x1d5c9) + u(0x1d5c5) + u(0x1d5be), 2); // ap | le target.insertTextBeforeCaret(u(0x1d5c9)); - assert.equal(target.getText(), u(0x1d5ba)+u(0x1d5c9)+u(0x1d5c9)+u(0x1d5c5)+u(0x1d5be)); + assert.equal(target.getText(), u(0x1d5ba) + u(0x1d5c9) + u(0x1d5c9) + u(0x1d5c5) + u(0x1d5be)); } finally { KMWString.enableSupplementaryPlane(false); } }); }); }); - -describe('Bundled ES Module for keyboard', function () { - describe('Keyboard', function () { - it('should initialize without errors', function () { - let kp = new Package2.JSKeyboard(); - assert.isNotNull(kp); - }); - }); - - describe("Imported `utils`", function () { - it("should include `utils` package's Version class", () => { - let v16 = new Package2.Version([16, 1]); - assert.equal(v16.toString(), "16.1"); - }); - }); -}); diff --git a/web/src/test/auto/headless/engine/js-processor/engine/context.tests.js b/web/src/test/auto/headless/engine/js-processor/engine/context.tests.js index 4469194a6d..813f699b1e 100644 --- a/web/src/test/auto/headless/engine/js-processor/engine/context.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/engine/context.tests.js @@ -3,8 +3,8 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; -import { JSKeyboardInterface, JSKeyboardProcessor, Mock } from 'keyman/engine/js-processor'; +import { MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { JSKeyboardInterface, JSKeyboardProcessor } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { NodeProctor, RecordedKeystrokeSequence } from '@keymanapp/recorder-core'; @@ -882,7 +882,7 @@ var NUL_TEST_2 = { /* Keyman language equivalent: * * nul nul any(abc) context(3) > 'success' - * + * * This one may... "stretch" what's actually allowed by Keyman language rules, * but we wish to ensure that the actual context management is capable of * handling this. @@ -925,7 +925,7 @@ var NUL_TEST_3 = { /* Keyman language equivalent: * * nul nul dk(1) any(abc) > 'success' - * + * * This may also "stretch" what's actually allowed by Keyman language rules, * but we wish to ensure that the actual context management is capable of * handling this. diff --git a/web/src/test/auto/headless/engine/js-processor/engine/notany_context.tests.js b/web/src/test/auto/headless/engine/js-processor/engine/notany_context.tests.js index 8264de4efe..840de72cb0 100644 --- a/web/src/test/auto/headless/engine/js-processor/engine/notany_context.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/engine/notany_context.tests.js @@ -3,8 +3,8 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; -import { JSKeyboardInterface, Mock } from 'keyman/engine/js-processor'; +import { MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { JSKeyboardInterface } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { NodeProctor, RecordedKeystrokeSequence } from '@keymanapp/recorder-core'; diff --git a/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.ts b/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.ts index b8a68dd55c..d3f75964c5 100644 --- a/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.ts +++ b/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.ts @@ -3,8 +3,8 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { DeviceSpec, JSKeyboard, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; -import { JSKeyboardInterface, Mock } from 'keyman/engine/js-processor'; +import { DeviceSpec, JSKeyboard, MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { JSKeyboardInterface } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; describe('Headless keyboard loading', function () { diff --git a/web/src/test/auto/headless/engine/js-processor/non-positional-rules.tests.js b/web/src/test/auto/headless/engine/js-processor/non-positional-rules.tests.js index 1d320e0b93..a041880bb0 100644 --- a/web/src/test/auto/headless/engine/js-processor/non-positional-rules.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/non-positional-rules.tests.js @@ -4,8 +4,8 @@ import { ModifierKeyConstants } from '@keymanapp/common-types'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { Codes, KeyEvent, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; -import { JSKeyboardInterface, Mock } from 'keyman/engine/js-processor'; +import { Codes, KeyEvent, MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { JSKeyboardInterface } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; // Compare and contrast the unit tests here with those for app/browser key-event unit testing diff --git a/web/src/test/auto/headless/engine/js-processor/specialized-backspace.tests.js b/web/src/test/auto/headless/engine/js-processor/specialized-backspace.tests.js index 0d573ad370..a1a2d1b229 100644 --- a/web/src/test/auto/headless/engine/js-processor/specialized-backspace.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/specialized-backspace.tests.js @@ -5,8 +5,8 @@ import { createRequire } from 'module'; const require = createRequire(import.meta.url); import { KMWString } from '@keymanapp/web-utils'; -import { Codes, KeyEvent, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; -import { JSKeyboardInterface, JSKeyboardProcessor, Mock } from 'keyman/engine/js-processor'; +import { Codes, KeyEvent, MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { JSKeyboardInterface, JSKeyboardProcessor } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { ModifierKeyConstants } from '@keymanapp/common-types'; diff --git a/web/src/test/auto/headless/engine/js-processor/transcriptions.tests.js b/web/src/test/auto/headless/engine/js-processor/transcriptions.tests.js index d21cc54c44..8f81f0d362 100644 --- a/web/src/test/auto/headless/engine/js-processor/transcriptions.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/transcriptions.tests.js @@ -1,6 +1,6 @@ import { assert } from 'chai'; -import { Mock, findCommonSubstringEndIndex } from 'keyman/engine/js-processor'; +import { Mock, findCommonSubstringEndIndex } from 'keyman/engine/keyboard'; import { KMWString } from '@keymanapp/web-utils'; // A unicode-coding like alias for use in constructing non-BMP strings. diff --git a/web/src/test/auto/headless/engine/keyboard/keyboard-loading.tests.js b/web/src/test/auto/headless/engine/keyboard/keyboard-loading.tests.js index f319b20731..e789f6bf51 100644 --- a/web/src/test/auto/headless/engine/keyboard/keyboard-loading.tests.js +++ b/web/src/test/auto/headless/engine/keyboard/keyboard-loading.tests.js @@ -3,8 +3,8 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardHarness, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; -import { JSKeyboardInterface, Mock } from 'keyman/engine/js-processor'; +import { KeyboardHarness, MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { JSKeyboardInterface } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; describe('Headless keyboard loading', function() { diff --git a/web/src/test/auto/headless/engine/keyboard/keyboardLoaderBase.tests.ts b/web/src/test/auto/headless/engine/keyboard/keyboardLoaderBase.tests.ts index 76d5fdc1b1..232293fbc2 100644 --- a/web/src/test/auto/headless/engine/keyboard/keyboardLoaderBase.tests.ts +++ b/web/src/test/auto/headless/engine/keyboard/keyboardLoaderBase.tests.ts @@ -3,8 +3,8 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardHarness, MinimalKeymanGlobal, KeyboardDownloadError, DeviceSpec, InvalidKeyboardError, JSKeyboard } from 'keyman/engine/keyboard'; -import { JSKeyboardInterface, Mock } from 'keyman/engine/js-processor'; +import { KeyboardHarness, MinimalKeymanGlobal, KeyboardDownloadError, DeviceSpec, InvalidKeyboardError, JSKeyboard, Mock } from 'keyman/engine/keyboard'; +import { JSKeyboardInterface } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { assertThrowsAsync, assertThrows } from 'keyman/tools/testing/test-utils'; diff --git a/web/src/test/auto/headless/engine/js-processor/mocks.tests.js b/web/src/test/auto/headless/engine/keyboard/mocks.tests.js similarity index 98% rename from web/src/test/auto/headless/engine/js-processor/mocks.tests.js rename to web/src/test/auto/headless/engine/keyboard/mocks.tests.js index bad81bfcca..b02f259886 100644 --- a/web/src/test/auto/headless/engine/js-processor/mocks.tests.js +++ b/web/src/test/auto/headless/engine/keyboard/mocks.tests.js @@ -1,5 +1,5 @@ import { assert } from 'chai'; -import { Mock } from 'keyman/engine/js-processor'; +import { Mock } from 'keyman/engine/keyboard'; describe('Mocks', function() { describe('app|les', () => { diff --git a/web/src/test/auto/headless/engine/main/headless/inputProcessor.tests.js b/web/src/test/auto/headless/engine/main/headless/inputProcessor.tests.js index eafd740ced..d5b8eb1d86 100644 --- a/web/src/test/auto/headless/engine/main/headless/inputProcessor.tests.js +++ b/web/src/test/auto/headless/engine/main/headless/inputProcessor.tests.js @@ -5,8 +5,8 @@ import { createRequire } from 'module'; const require = createRequire(import.meta.url); import { InputProcessor } from 'keyman/engine/main'; -import { JSKeyboardInterface, Mock } from 'keyman/engine/js-processor'; -import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { JSKeyboardInterface } from 'keyman/engine/js-processor'; +import { MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { KeyboardTest } from '@keymanapp/recorder-core'; diff --git a/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js b/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js index 5707fb31f7..4fc2b0d9bd 100644 --- a/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js +++ b/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js @@ -2,7 +2,7 @@ import { assert } from 'chai'; import { LanguageProcessor, TranscriptionCache } from 'keyman/engine/main'; import { SourcemappedWorker as LMWorker } from "@keymanapp/lexical-model-layer/node"; -import { Mock } from 'keyman/engine/js-processor'; +import { Mock } from 'keyman/engine/keyboard'; /* * Unit tests for the Dummy prediction model. diff --git a/web/src/tools/testing/recorder-core/src/index.ts b/web/src/tools/testing/recorder-core/src/index.ts index 60ab11a843..131621fc1d 100644 --- a/web/src/tools/testing/recorder-core/src/index.ts +++ b/web/src/tools/testing/recorder-core/src/index.ts @@ -1,5 +1,4 @@ -import { Mock } from "keyman/engine/js-processor"; -import { KeyDistribution, KeyEvent, type OutputTargetInterface } from "keyman/engine/keyboard"; +import { KeyDistribution, KeyEvent, type OutputTargetInterface, Mock } from "keyman/engine/keyboard"; import Proctor from "./proctor.js"; diff --git a/web/src/tools/testing/recorder-core/src/nodeProctor.ts b/web/src/tools/testing/recorder-core/src/nodeProctor.ts index 72980a528f..52882e95a6 100644 --- a/web/src/tools/testing/recorder-core/src/nodeProctor.ts +++ b/web/src/tools/testing/recorder-core/src/nodeProctor.ts @@ -8,9 +8,7 @@ import { RecordedSyntheticKeystroke } from "./index.js"; -import { KeyEvent, KeyEventSpec, KeyboardHarness, type OutputTargetInterface } from "keyman/engine/keyboard"; -// TODO-web-core: remove usage of OutputTargetBase, use OutputTargetInterface instead -import { Mock, OutputTargetBase } from 'keyman/engine/js-processor'; +import { KeyEvent, KeyEventSpec, KeyboardHarness, type OutputTargetInterface, Mock, OutputTargetBase } from "keyman/engine/keyboard"; import { DeviceSpec } from "@keymanapp/web-utils"; import { JSKeyboardInterface, JSKeyboardProcessor } from 'keyman/engine/js-processor'; From dce103bed2bac029ff456338aef63160b9bf3187 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 4 Nov 2025 10:58:12 +0100 Subject: [PATCH 03/17] =?UTF-8?q?refactor(web):=20rename=20`OutputTargetIn?= =?UTF-8?q?terface`=20=E2=86=92=20`OutputTargetBase`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/app/browser/src/configuration.ts | 4 +- .../app/browser/src/defaultBrowserRules.ts | 4 +- web/src/app/webview/src/contextManager.ts | 7 +- .../attachment/src/outputTargetForElement.ts | 6 +- .../prediction/languageProcessor.interface.ts | 12 +- .../src/prediction/predictionContext.ts | 8 +- .../js-processor/src/jsKeyboardInterface.ts | 39 +++---- .../js-processor/src/jsKeyboardProcessor.ts | 6 +- web/src/engine/keyboard/src/defaultRules.ts | 4 +- web/src/engine/keyboard/src/index.ts | 1 - .../keyboard/src/keyboards/jsKeyboard.ts | 12 +- .../keyboard/src/keyboards/transcription.ts | 6 +- web/src/engine/keyboard/src/mock.ts | 5 +- .../engine/keyboard/src/outputTargetBase.ts | 9 +- .../keyboard/src/outputTargetInterface.ts | 108 ------------------ web/src/engine/main/src/contextManagerBase.ts | 20 ++-- .../engine/main/src/engineConfiguration.ts | 4 +- .../main/src/headless/inputProcessor.ts | 8 +- .../main/src/headless/languageProcessor.ts | 17 ++- web/src/engine/main/src/keymanEngineBase.ts | 1 - .../tools/testing/recorder-core/src/index.ts | 4 +- .../testing/recorder-core/src/nodeProctor.ts | 4 +- .../testing/recorder-core/src/proctor.ts | 4 +- .../tools/testing/recorder/browserProctor.ts | 4 +- 24 files changed, 91 insertions(+), 206 deletions(-) delete mode 100644 web/src/engine/keyboard/src/outputTargetInterface.ts diff --git a/web/src/app/browser/src/configuration.ts b/web/src/app/browser/src/configuration.ts index f76184dbd3..faa24c577c 100644 --- a/web/src/app/browser/src/configuration.ts +++ b/web/src/app/browser/src/configuration.ts @@ -1,7 +1,7 @@ import { EngineConfiguration, InitOptionSpec, InitOptionDefaults } from "keyman/engine/main"; import { OutputTargetElementWrapper as DOMOutputTarget } from 'keyman/engine/element-wrappers'; -import { OutputTargetInterface, ProcessorAction } from 'keyman/engine/keyboard'; +import { OutputTargetBase, ProcessorAction } from 'keyman/engine/keyboard'; import { isEmptyTransform } from '@keymanapp/web-utils'; import { AlertHost } from "./utils/alertHost.js"; import { whenDocumentReady } from "./utils/documentReady.js"; @@ -66,7 +66,7 @@ export class BrowserConfiguration extends EngineConfiguration { return baseReport; } - onRuleFinalization(ruleBehavior: ProcessorAction, outputTarget: OutputTargetInterface) { + onRuleFinalization(ruleBehavior: ProcessorAction, outputTarget: OutputTargetBase) { // TODO: Patch up to modularized form. But that doesn't exist yet for some of these... // If the transform isn't empty, we've changed text - which should produce a 'changed' event in the DOM. diff --git a/web/src/app/browser/src/defaultBrowserRules.ts b/web/src/app/browser/src/defaultBrowserRules.ts index ec87f459d4..fd068e90c1 100644 --- a/web/src/app/browser/src/defaultBrowserRules.ts +++ b/web/src/app/browser/src/defaultBrowserRules.ts @@ -3,7 +3,7 @@ import { Codes, DefaultRules, type KeyEvent, - type OutputTargetInterface + type OutputTargetBase } from 'keyman/engine/keyboard'; import ContextManager from './contextManager.js'; @@ -32,7 +32,7 @@ export default class DefaultBrowserRules extends DefaultRules { /** * applyCommand - used when a ProcessorAction represents a non-text "command" within the Engine. */ - applyCommand(Lkc: KeyEvent, outputTarget: OutputTargetInterface): void { + applyCommand(Lkc: KeyEvent, outputTarget: OutputTargetBase): void { const code = this.codeForEvent(Lkc); const moveToNext = (back: boolean) => { diff --git a/web/src/app/webview/src/contextManager.ts b/web/src/app/webview/src/contextManager.ts index 41623cecd5..1c5e14094a 100644 --- a/web/src/app/webview/src/contextManager.ts +++ b/web/src/app/webview/src/contextManager.ts @@ -1,5 +1,4 @@ -import { JSKeyboard, Keyboard, OutputTargetInterface, Transcription, TextTransform, Mock, findCommonSubstringEndIndex, OutputTargetBase } from 'keyman/engine/keyboard'; -// TODO-web-core: remove usage of OutputTargetBase, use OutputTargetInterface instead +import { JSKeyboard, Keyboard, OutputTargetBase, Transcription, TextTransform, Mock, findCommonSubstringEndIndex } from 'keyman/engine/keyboard'; import { KeyboardStub } from 'keyman/engine/keyboard-storage'; import { ContextManagerBase } from 'keyman/engine/main'; import { WebviewConfiguration } from './configuration.js'; @@ -59,7 +58,7 @@ export class ContextHost extends Mock { this.savedState = Mock.from(this); } - restoreTo(original: OutputTargetInterface): void { + restoreTo(original: OutputTargetBase): void { this.savedState = Mock.from(this); // TODO-web-core super.restoreTo(original as OutputTargetBase); @@ -139,7 +138,7 @@ export default class ContextManager extends ContextManagerBase boolean + 'suggestionapplied': (outputTarget: OutputTargetBase) => boolean } @@ -56,7 +56,7 @@ export interface LanguageProcessorSpec extends EventEmitter; + invalidateContext(outputTarget: OutputTargetBase, layerId: string): Promise; /** * @@ -66,9 +66,9 @@ export interface LanguageProcessorSpec extends EventEmitter string): Promise; + applySuggestion(suggestion: LexicalModelTypes.Suggestion, outputTarget: OutputTargetBase, getLayerId: () => string): Promise; - applyReversion(reversion: LexicalModelTypes.Reversion, outputTarget: OutputTargetInterface): Promise; + applyReversion(reversion: LexicalModelTypes.Reversion, outputTarget: OutputTargetBase): Promise; get wordbreaksAfterSuggestions(): boolean; diff --git a/web/src/engine/interfaces/src/prediction/predictionContext.ts b/web/src/engine/interfaces/src/prediction/predictionContext.ts index 1efe3b5615..ae2d097aa4 100644 --- a/web/src/engine/interfaces/src/prediction/predictionContext.ts +++ b/web/src/engine/interfaces/src/prediction/predictionContext.ts @@ -4,7 +4,7 @@ import Keep = LexicalModelTypes.Keep; import Reversion = LexicalModelTypes.Reversion; import Suggestion = LexicalModelTypes.Suggestion; import { type LanguageProcessorSpec , ReadySuggestions, type InvalidateSourceEnum, StateChangeHandler } from './languageProcessor.interface.js'; -import { type OutputTargetInterface } from "keyman/engine/keyboard"; +import { type OutputTargetBase } from "keyman/engine/keyboard"; interface PredictionContextEventMap { update: (suggestions: Suggestion[]) => void; @@ -41,13 +41,13 @@ export default class PredictionContext extends EventEmitter { + public setCurrentTarget(target: OutputTargetBase): Promise { const originalTarget = this._currentTarget; this._currentTarget = target; diff --git a/web/src/engine/js-processor/src/jsKeyboardInterface.ts b/web/src/engine/js-processor/src/jsKeyboardInterface.ts index 8eeeee6d87..1bfa90ef60 100644 --- a/web/src/engine/js-processor/src/jsKeyboardInterface.ts +++ b/web/src/engine/js-processor/src/jsKeyboardInterface.ts @@ -21,7 +21,6 @@ import { type Deadkey, type KeyEvent, type OutputTargetBase, - type OutputTargetInterface, VariableStore, VariableStoreDictionary, VariableStoreSerializer, @@ -187,7 +186,7 @@ export class JSKeyboardInterface extends KeyboardHarness { cachedContextEx: CachedContextEx = new CachedContextEx(); ruleContextEx: CachedContextEx; - activeTargetOutput: OutputTargetInterface; + activeTargetOutput: OutputTargetBase; ruleBehavior: ProcessorAction; systemStores: {[storeID: number]: SystemStore}; @@ -264,7 +263,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * KC(10,10,Pelem) == "abcdef" i.e. return as much as possible of the requested string */ - context(n: number, ln: number, outputTarget: OutputTargetInterface): string { + context(n: number, ln: number, outputTarget: OutputTargetBase): string { const v = this.cachedContext.get(n, ln); if(v !== null) { return v; @@ -288,7 +287,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * KC(3,3,Pelem) == "def" * KC(10,10,Pelem) == "XXXXabcdef" i.e. return as much as possible of the requested string, where X = \uFFFE */ - private KC_(n: number, ln: number, outputTarget: OutputTargetInterface): string { + private KC_(n: number, ln: number, outputTarget: OutputTargetBase): string { let tempContext = ''; // If we have a selection, we have an empty context @@ -314,7 +313,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * KN(2,Pelem) == FALSE * KN(4,Pelem) == TRUE */ - nul(n: number, outputTarget: OutputTargetInterface): boolean { + nul(n: number, outputTarget: OutputTargetBase): boolean { const cx=this.context(n+1, 1, outputTarget); // With #31, the result will be a replacement character if context is empty. @@ -331,7 +330,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * @return {boolean} True if selected context matches val * Description Test keyboard context for match */ - contextMatch(n: number, outputTarget: OutputTargetInterface, val: string, ln: number): boolean { + contextMatch(n: number, outputTarget: OutputTargetBase, val: string, ln: number): boolean { const cx=this.context(n, ln, outputTarget); if(cx === val) { return true; // I3318 @@ -629,7 +628,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * @return {boolean} True if deadkey found selected context matches val * Description Match deadkey at current cursor position */ - deadkeyMatch(n: number, outputTarget: OutputTargetInterface, d: number): boolean { + deadkeyMatch(n: number, outputTarget: OutputTargetBase, d: number): boolean { return outputTarget.hasDeadkeyMatch(n, d); } @@ -639,7 +638,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * @param {Object} outputTarget element to flash * Description Flash body as substitute for audible beep; notify embedded device to vibrate */ - beep(outputTarget: OutputTargetInterface): void { + beep(outputTarget: OutputTargetBase): void { this.resetContextCache(); // Denote as part of the matched rule's behavior. @@ -732,7 +731,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * @param {Object} outputTarget element to output to * Description Output a character selected from the string according to the offset in the index array */ - indexOutput(Pdn: number, Ps: KeyboardStore, Pn: number, outputTarget: OutputTargetInterface): void { + indexOutput(Pdn: number, Ps: KeyboardStore, Pn: number, outputTarget: OutputTargetBase): void { this.resetContextCache(); const assertNever = function(x: never): never { @@ -769,7 +768,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * @param {Object} outputTarget element to output to * Description Keyboard output */ - deleteContext(dn: number, outputTarget: OutputTargetInterface): void { + deleteContext(dn: number, outputTarget: OutputTargetBase): void { let context: CachedExEntry; // We want to control exactly which deadkeys get removed. @@ -815,7 +814,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * @param {string} s string to output * Description Keyboard output */ - output(dn: number, outputTarget: OutputTargetInterface, s:string): void { + output(dn: number, outputTarget: OutputTargetBase, s:string): void { this.resetContextCache(); outputTarget.saveProperties(); @@ -838,11 +837,11 @@ export class JSKeyboardInterface extends KeyboardHarness { * @alias KCXO * @public * @param {number} Pdn number of characters to delete left of cursor - * @param {OutputTargetInterface} outputTarget target to output to + * @param {OutputTargetBase} outputTarget target to output to * @param {number} contextLength length of current rule context to retrieve * @param {number} contextOffset offset from start of current rule context, 1-based */ - contextExOutput(Pdn: number, outputTarget: OutputTargetInterface, contextLength: number, contextOffset: number): void { + contextExOutput(Pdn: number, outputTarget: OutputTargetBase, contextLength: number, contextOffset: number): void { this.resetContextCache(); if(Pdn >= 0) { @@ -864,11 +863,11 @@ export class JSKeyboardInterface extends KeyboardHarness { * Function deadkeyOutput KDO * Scope Public * @param {number} Pdn no of character to overwrite (delete) - * @param {OutputTargetInterface} outputTarget element to output to + * @param {OutputTargetBase} outputTarget element to output to * @param {number} Pd deadkey id * Description Record a deadkey at current cursor position, deleting Pdn characters first */ - deadkeyOutput(Pdn: number, outputTarget: OutputTargetInterface, Pd: number): void { + deadkeyOutput(Pdn: number, outputTarget: OutputTargetBase, Pd: number): void { this.resetContextCache(); if(Pdn >= 0) { @@ -884,10 +883,10 @@ export class JSKeyboardInterface extends KeyboardHarness { * * @param {number} systemId ID of the system store to test (only TSS_LAYER currently supported) * @param {string} strValue String value to compare to - * @param {OutputTargetInterface} outputTarget Currently active element (may be needed by future tests) + * @param {OutputTargetBase} outputTarget Currently active element (may be needed by future tests) * @return {boolean} True if the test succeeds */ - ifStore(systemId: number, strValue: string, outputTarget: OutputTargetInterface): boolean { + ifStore(systemId: number, strValue: string, outputTarget: OutputTargetBase): boolean { let result=true; const store = this.systemStores[systemId]; if(store) { @@ -901,14 +900,14 @@ export class JSKeyboardInterface extends KeyboardHarness { * * @param {number} systemId ID of the system store to set (only TSS_LAYER currently supported) * @param {string} strValue String to set as the system store content - * @param {OutputTargetInterface} outputTarget Currently active element (may be needed in future tests) + * @param {OutputTargetBase} outputTarget Currently active element (may be needed in future tests) * @return {boolean} True if command succeeds * (i.e. for TSS_LAYER, if the layer is successfully selected) * * Note that option/variable stores are instead set within keyboard script code, as they only * affect keyboard behavior. */ - setStore(systemId: number, strValue: string, outputTarget: OutputTargetInterface): boolean { + setStore(systemId: number, strValue: string, outputTarget: OutputTargetBase): boolean { this.resetContextCache(); // Unique case: we only allow set(&layer) ops from keyboard rules triggered by touch OSKs. if(systemId == SystemStoreIDs.TSS_LAYER && this.activeDevice.touchable) { @@ -978,7 +977,7 @@ export class JSKeyboardInterface extends KeyboardHarness { this.cachedContextEx.reset(); } - defaultBackspace(outputTarget: OutputTargetInterface) { + defaultBackspace(outputTarget: OutputTargetBase) { if(outputTarget.isSelectionEmpty()) { // Delete the character left of the caret this.output(1, outputTarget, ""); diff --git a/web/src/engine/js-processor/src/jsKeyboardProcessor.ts b/web/src/engine/js-processor/src/jsKeyboardProcessor.ts index 8650114c47..52510cda65 100644 --- a/web/src/engine/js-processor/src/jsKeyboardProcessor.ts +++ b/web/src/engine/js-processor/src/jsKeyboardProcessor.ts @@ -11,7 +11,7 @@ import { ModifierKeyConstants } from '@keymanapp/common-types'; import { Codes, type JSKeyboard, MinimalKeymanGlobal, KeyEvent, Layouts, DefaultRules, EmulationKeystrokes, type MutableSystemStore, - OutputTargetInterface, ProcessorAction, SystemStoreIDs, Mock, type OutputTargetBase + OutputTargetBase, ProcessorAction, SystemStoreIDs, Mock } from "keyman/engine/keyboard"; import { JSKeyboardInterface } from './jsKeyboardInterface.js'; import { DeviceSpec, globalObject, KMWString } from "@keymanapp/web-utils"; @@ -20,7 +20,7 @@ import { DeviceSpec, globalObject, KMWString } from "@keymanapp/web-utils"; // Also relies on @keymanapp/web-utils, which is included via tsconfig.json. -export type BeepHandler = (outputTarget: OutputTargetInterface) => void; +export type BeepHandler = (outputTarget: OutputTargetBase) => void; export type LogMessageHandler = (str: string) => void; export interface ProcessorInitOptions { @@ -608,7 +608,7 @@ export class JSKeyboardProcessor extends EventEmitter { } }; - public finalizeProcessorAction(data: ProcessorAction, outputTarget: OutputTargetInterface): void { + public finalizeProcessorAction(data: ProcessorAction, outputTarget: OutputTargetBase): void { if (!data.transcription) { throw "Cannot finalize a ProcessorAction with no transcription."; } diff --git a/web/src/engine/keyboard/src/defaultRules.ts b/web/src/engine/keyboard/src/defaultRules.ts index 98279ee94f..1c32056e14 100644 --- a/web/src/engine/keyboard/src/defaultRules.ts +++ b/web/src/engine/keyboard/src/defaultRules.ts @@ -7,7 +7,7 @@ import { ModifierKeyConstants } from '@keymanapp/common-types'; import { Codes } from './codes.js'; import { type KeyEvent } from './keyEvent.js'; -import { type OutputTargetInterface } from './outputTargetInterface.js'; +import { type OutputTargetBase } from './outputTargetBase.js'; export enum EmulationKeystrokes { Enter = '\n', @@ -83,7 +83,7 @@ export default class DefaultRules { * * Note: is extended by DOM-aware KeymanWeb code. */ - public applyCommand(Lkc: KeyEvent, outputTarget: OutputTargetInterface): void { + public applyCommand(Lkc: KeyEvent, outputTarget: OutputTargetBase): void { // Notes for potential default-handling extensions: // // switch(code) { diff --git a/web/src/engine/keyboard/src/index.ts b/web/src/engine/keyboard/src/index.ts index 2c43192690..eaf175c31b 100644 --- a/web/src/engine/keyboard/src/index.ts +++ b/web/src/engine/keyboard/src/index.ts @@ -29,7 +29,6 @@ export { default as DefaultRules } from "./defaultRules.js"; export * from "./defaultRules.js"; export { type KeyDistribution, KeyEventSpec, KeyEvent } from "./keyEvent.js"; export { default as KeyMapping } from "./keyMapping.js"; -export { OutputTargetInterface } from "./outputTargetInterface.js"; export { type SystemStoreMutationHandler, MutableSystemStore, SystemStore, SystemStoreIDs, type SystemStoreDictionary } from "./systemStore.js"; export { type VariableStore, VariableStoreSerializer, VariableStoreDictionary } from "./variableStore.js"; diff --git a/web/src/engine/keyboard/src/keyboards/jsKeyboard.ts b/web/src/engine/keyboard/src/keyboards/jsKeyboard.ts index 3bdcb0ed2f..22400dc01b 100644 --- a/web/src/engine/keyboard/src/keyboards/jsKeyboard.ts +++ b/web/src/engine/keyboard/src/keyboards/jsKeyboard.ts @@ -2,7 +2,7 @@ import { Codes } from "../codes.js"; import { Layouts } from "./defaultLayouts.js"; import { ActiveKey, ActiveLayout, ActiveSubKey } from "./activeLayout.js"; import { KeyEvent } from "../keyEvent.js"; -import { type OutputTargetInterface } from "../outputTargetInterface.js"; +import { type OutputTargetBase } from "../outputTargetBase.js"; import { KeymanWebKeyboard, ModifierKeyConstants, TouchLayout } from "@keymanapp/common-types"; import { VariableStoreDictionary } from "../variableStore.js"; @@ -50,7 +50,7 @@ type KmwKeyboardObject = KeyboardObject & { */ export class JSKeyboard { public static DEFAULT_SCRIPT_OBJECT: KmwKeyboardObject = { - 'gs': function(outputTarget: OutputTargetInterface, keystroke: KeyEvent) { return false; }, // no matching rules; rely on defaultRuleOutput entirely + 'gs': function(outputTarget: OutputTargetBase, keystroke: KeyEvent) { return false; }, // no matching rules; rely on defaultRuleOutput entirely 'KI': '', // The currently-existing default keyboard ID; we already have checks that focus against this. 'KN': '', 'KV': Layouts.DEFAULT_RAW_SPEC, @@ -78,21 +78,21 @@ export class JSKeyboard { /** * Calls the keyboard's `gs` function, which represents the keyboard source's begin Unicode group. */ - process(outputTarget: OutputTargetInterface, keystroke: KeyEvent): boolean { + process(outputTarget: OutputTargetBase, keystroke: KeyEvent): boolean { return this.scriptObject['gs'](outputTarget, keystroke); } /** * Calls the keyboard's `gn` function, which represents the keyboard source's begin newContext group. */ - processNewContextEvent(outputTarget: OutputTargetInterface, keystroke: KeyEvent): boolean { + processNewContextEvent(outputTarget: OutputTargetBase, keystroke: KeyEvent): boolean { return this.scriptObject['gn'] ? this.scriptObject['gn'](outputTarget, keystroke) : false; } /** * Calls the keyboard's `gpk` function, which represents the keyboard source's begin postKeystroke group. */ - processPostKeystroke(outputTarget: OutputTargetInterface, keystroke: KeyEvent): boolean { + processPostKeystroke(outputTarget: OutputTargetBase, keystroke: KeyEvent): boolean { return this.scriptObject['gpk'] ? this.scriptObject['gpk'](outputTarget, keystroke) : false; } @@ -359,7 +359,7 @@ export class JSKeyboard { * @param {number} _PData 1 or 0 * Notifies keyboard of keystroke or other event */ - notify(_PCommand: number, _PTarget: OutputTargetInterface, _PData: number) { // I2187 + notify(_PCommand: number, _PTarget: OutputTargetBase, _PData: number) { // I2187 // Good example use case - the Japanese CJK-picker keyboard if(typeof(this.scriptObject['KNS']) == 'function') { this.scriptObject['KNS'](_PCommand, _PTarget, _PData); diff --git a/web/src/engine/keyboard/src/keyboards/transcription.ts b/web/src/engine/keyboard/src/keyboards/transcription.ts index 8263e0c804..a0c8b759a2 100644 --- a/web/src/engine/keyboard/src/keyboards/transcription.ts +++ b/web/src/engine/keyboard/src/keyboards/transcription.ts @@ -1,7 +1,7 @@ /* * Keyman is copyright (C) SIL Global. MIT License. */ -import { OutputTargetInterface } from '../outputTargetInterface.js'; +import { OutputTargetBase } from '../outputTargetBase.js'; import { KeyEvent } from '../keyEvent.js'; import { Alternate, TextTransform } from './textTransform.js'; @@ -10,11 +10,11 @@ export class Transcription { readonly keystroke: KeyEvent; readonly transform: TextTransform; alternates: Alternate[]; // constructed after the rest of the transcription. - readonly preInput: OutputTargetInterface; + readonly preInput: OutputTargetBase; private static tokenSeed: number = 0; - constructor(keystroke: KeyEvent, transform: TextTransform, preInput: OutputTargetInterface, alternates?: Alternate[]) { + constructor(keystroke: KeyEvent, transform: TextTransform, preInput: OutputTargetBase, alternates?: Alternate[]) { const token = this.token = Transcription.tokenSeed++; this.keystroke = keystroke; diff --git a/web/src/engine/keyboard/src/mock.ts b/web/src/engine/keyboard/src/mock.ts index fb8cc607c7..8d042dc071 100644 --- a/web/src/engine/keyboard/src/mock.ts +++ b/web/src/engine/keyboard/src/mock.ts @@ -1,4 +1,3 @@ -import { OutputTargetInterface } from 'keyman/engine/keyboard'; import { OutputTargetBase } from './outputTargetBase.js'; import { KMWString } from '@keymanapp/web-utils'; @@ -26,14 +25,14 @@ export class Mock extends OutputTargetBase { this.selForward = this.selEnd >= this.selStart; } - static assertIsOutputTargetBase(outputTarget: OutputTargetInterface): asserts outputTarget is OutputTargetBase { + static assertIsOutputTargetBase(outputTarget: OutputTargetBase): asserts outputTarget is OutputTargetBase { if (!(outputTarget instanceof OutputTargetBase)) { throw new TypeError("outputTarget is not a OutputTargetBase"); } } // Clones the state of an existing EditableElement, creating a Mock version of its state. - static from(outputTarget: OutputTargetInterface, readonly?: boolean): Mock { + static from(outputTarget: OutputTargetBase, readonly?: boolean): Mock { let clone: Mock; this.assertIsOutputTargetBase(outputTarget); diff --git a/web/src/engine/keyboard/src/outputTargetBase.ts b/web/src/engine/keyboard/src/outputTargetBase.ts index a5ed2c2227..cf82e10702 100644 --- a/web/src/engine/keyboard/src/outputTargetBase.ts +++ b/web/src/engine/keyboard/src/outputTargetBase.ts @@ -1,5 +1,6 @@ import { KMWString } from "@keymanapp/web-utils"; -import { Alternate, OutputTargetInterface, TextTransform, Transcription } from 'keyman/engine/keyboard'; +import { Alternate, TextTransform } from "./keyboards/textTransform.js"; +import { Transcription } from "./keyboards/transcription.js"; import { findCommonSubstringEndIndex } from "./stringDivergence.js"; import { Mock } from "./mock.js"; @@ -8,7 +9,7 @@ import { type KeyEvent } from 'keyman/engine/keyboard'; import { Deadkey, DeadkeyTracker } from "./deadkeys.js"; import { LexicalModelTypes } from '@keymanapp/common-types'; -export abstract class OutputTargetBase implements OutputTargetInterface { +export abstract class OutputTargetBase { private _dks: DeadkeyTracker; constructor() { @@ -66,7 +67,7 @@ export abstract class OutputTargetBase implements OutputTargetInterface { * As such, it assumes that the caret is immediately after any inserted text. * @param from An output target (preferably a Mock) representing the prior state of the input/output system. */ - buildTransformFrom(original: OutputTargetInterface): TextTransform { + buildTransformFrom(original: OutputTargetBase): TextTransform { const toLeft = this.getTextBeforeCaret(); const fromLeft = original.getTextBeforeCaret(); @@ -87,7 +88,7 @@ export abstract class OutputTargetBase implements OutputTargetInterface { return new TextTransform(insertedText, deletedLeft, deletedRight, original.getSelectedText() && !this.getSelectedText()); } - buildTranscriptionFrom(original: OutputTargetInterface, keyEvent: KeyEvent, readonly: boolean, alternates?: Alternate[]): Transcription { + buildTranscriptionFrom(original: OutputTargetBase, keyEvent: KeyEvent, readonly: boolean, alternates?: Alternate[]): Transcription { const transform = this.buildTransformFrom(original); // If we ever decide to re-add deadkey tracking, this is the place for it. diff --git a/web/src/engine/keyboard/src/outputTargetInterface.ts b/web/src/engine/keyboard/src/outputTargetInterface.ts deleted file mode 100644 index 5dac4eb2cd..0000000000 --- a/web/src/engine/keyboard/src/outputTargetInterface.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Keyman is copyright (C) SIL Global. MIT License. - */ -export interface OutputTargetInterface { - /** - * Signifies that this OutputTargetInterface has no default key processing behaviors. - * This should be false for OutputTargets backed by web elements like HTMLInputElement - * or HTMLTextAreaElement. - */ - get isSynthetic(): boolean; - - resetContext(): void; - - hasDeadkeyMatch(n: number, d: number): boolean; - - insertDeadkeyBeforeCaret(d: number): void; - - /** - * Clears any selected text within the wrapper's element(s). - * Silently does nothing if no such text exists. - */ - clearSelection(): void; - - /** - * Clears any cached selection-related state values. - */ - invalidateSelection(): void; - - /** - * Indicates whether or not the underlying element has its own selection (input, textarea) - * or is part of (or possesses) the DOM's active selection. Don't confuse with isSelectionEmpty(). - * - * TODO: rename to supportsOwnSelection - */ - hasSelection(): boolean; - - /** - * Returns true if there is no current selection -- that is, the selection range is empty - */ - isSelectionEmpty(): boolean; - - /** - * Returns an index corresponding to the caret's position for use with deadkeys. - */ - getDeadkeyCaret(): number; - - /** - * Relative to the caret, gets the current context within the wrapper's element. - */ - getTextBeforeCaret(): string; - - /** - * Gets the element's-currently selected text. - */ - getSelectedText(): string; - - /** - * Relative to the caret (and/or active selection), gets the element's text after the caret, - * excluding any actively selected text that would be immediately replaced upon text entry. - */ - getTextAfterCaret(): string; - - /** - * Gets the element's full text, including any text that is actively selected. - */ - getText(): string; - - /** - * Performs context deletions (from the left of the caret) as needed by the KeymanWeb engine and - * corrects the location of any affected deadkeys. - * - * Does not delete deadkeys (b/c KMW 1 & 2 behavior maintenance). - * @param dn The number of characters to delete. If negative, context will be left unchanged. - */ - deleteCharsBeforeCaret(dn: number): void; - - /** - * Inserts text immediately before the caret's current position, moving the caret after the - * newly inserted text in the process along with any affected deadkeys. - * - * @param s Text to insert before the caret's current position. - */ - insertTextBeforeCaret(s: string): void; - - /** - * Allows element-specific handling for ENTER key inputs. Conceptually, this should usually - * correspond to `insertTextBeforeCaret('\n'), but actual implementation will vary greatly among - * elements. - */ - handleNewlineAtCaret(): void; - - /** - * Saves element-specific state properties prone to mutation, enabling restoration after - * text-output operations. - */ - saveProperties(): void; - - /** - * Restores previously-saved element-specific state properties. Designed for use after text-output - * ops to facilitate more-seamless web-dev and user interactions. - */ - restoreProperties(): void; - - /** - * Generates a synthetic event on the underlying element, signalling that its value has changed. - */ - doInputEvent(): void; -} diff --git a/web/src/engine/main/src/contextManagerBase.ts b/web/src/engine/main/src/contextManagerBase.ts index 8aa7319c83..979fcfc7ed 100644 --- a/web/src/engine/main/src/contextManagerBase.ts +++ b/web/src/engine/main/src/contextManagerBase.ts @@ -1,5 +1,5 @@ import { EventEmitter } from 'eventemitter3'; -import { ManagedPromise, type Keyboard, type OutputTargetInterface } from 'keyman/engine/keyboard'; +import { ManagedPromise, type Keyboard, type OutputTargetBase } from 'keyman/engine/keyboard'; import { type JSKeyboardInterface } from 'keyman/engine/js-processor'; import { StubAndKeyboardCache, type KeyboardStub } from 'keyman/engine/keyboard-storage'; import { PredictionContext } from 'keyman/engine/interfaces'; @@ -7,7 +7,7 @@ import { EngineConfiguration } from './engineConfiguration.js'; interface EventMap { // target, then keyboard. - 'targetchange': (target: OutputTargetInterface) => boolean; + 'targetchange': (target: OutputTargetBase) => boolean; /** * This event is raised whenever a keyboard change is requested. @@ -48,7 +48,7 @@ export interface ContextManagerConfiguration { * * Does not reset option-stores, variable-stores, etc. */ - readonly resetContext: (outputTarget?: OutputTargetInterface) => void; + readonly resetContext: (outputTarget?: OutputTargetBase) => void; /** * A predictive-state management object that interfaces the predictive-text banner @@ -64,7 +64,7 @@ export interface ContextManagerConfiguration { } interface PendingActivation { - target: OutputTargetInterface, + target: OutputTargetBase, keyboard: Promise, stub: KeyboardStub; } @@ -74,11 +74,11 @@ export abstract class ContextManagerBase abstract initialize(): void; - abstract get activeTarget(): OutputTargetInterface; + abstract get activeTarget(): OutputTargetBase; private _predictionContext: PredictionContext; protected keyboardCache: StubAndKeyboardCache; - private _resetContext: (outputTarget?: OutputTargetInterface) => void; + private _resetContext: (outputTarget?: OutputTargetBase) => void; private pendingActivations: PendingActivation[] = []; protected engineConfig: MainConfig; @@ -135,7 +135,7 @@ export abstract class ContextManagerBase * attached elements within the app/browser target. For `app/webview`, this should * always return a consistent value - likely, `null`. */ - protected abstract currentKeyboardSrcTarget(): OutputTargetInterface; + protected abstract currentKeyboardSrcTarget(): OutputTargetBase; /** * Ensures that newly activated keyboards are set correctly within managed context, possibly @@ -143,7 +143,7 @@ export abstract class ContextManagerBase * @param kbd * @param target */ - protected abstract activateKeyboardForTarget(kbd: { keyboard: Keyboard, metadata: KeyboardStub }, target: OutputTargetInterface): void; + protected abstract activateKeyboardForTarget(kbd: { keyboard: Keyboard, metadata: KeyboardStub }, target: OutputTargetBase): void; /** * Checks the pending keyboard-activation array for an entry corresponding to the specified @@ -152,7 +152,7 @@ export abstract class ContextManagerBase * May be `null`, which corresponds to the global default Keyboard. * @returns `true` if pending activation is still valid, `false` otherwise. */ - private findAndPopActivation(target: OutputTargetInterface): PendingActivation { + private findAndPopActivation(target: OutputTargetBase): PendingActivation { // Array.findIndex requires Chrome 45+. :( let activationIndex; for(activationIndex = 0; activationIndex < this.pendingActivations.length; activationIndex++) { @@ -180,7 +180,7 @@ export abstract class ContextManagerBase protected async deferredKeyboardActivation( kbdPromise: Promise, metadata: KeyboardStub, - target: OutputTargetInterface + target: OutputTargetBase ): Promise { const activation: PendingActivation = { target: target, diff --git a/web/src/engine/main/src/engineConfiguration.ts b/web/src/engine/main/src/engineConfiguration.ts index db4a0c99fd..4c172aa00a 100644 --- a/web/src/engine/main/src/engineConfiguration.ts +++ b/web/src/engine/main/src/engineConfiguration.ts @@ -1,7 +1,7 @@ import { EventEmitter } from "eventemitter3"; import { - DeviceSpec, KeyboardProperties, ManagedPromise, OutputTargetInterface, + DeviceSpec, KeyboardProperties, ManagedPromise, OutputTargetBase, ProcessorAction, physicalKeyDeviceAlias, SpacebarText } from "keyman/engine/keyboard"; import { PathConfiguration, PathOptionDefaults, PathOptionSpec } from "keyman/engine/interfaces"; @@ -112,7 +112,7 @@ export class EngineConfiguration extends EventEmitter { * @param ruleBehavior The full effects of keystroke + postkeystroke rules from a processed keystroke. * @param outputTarget The engine's current source for context */ - onRuleFinalization(ruleBehavior: ProcessorAction, outputTarget: OutputTargetInterface) {}; + onRuleFinalization(ruleBehavior: ProcessorAction, outputTarget: OutputTargetBase) {}; } export interface InitOptionSpec extends PathOptionSpec { diff --git a/web/src/engine/main/src/headless/inputProcessor.ts b/web/src/engine/main/src/headless/inputProcessor.ts index 2fd151e0ee..76c90f919e 100644 --- a/web/src/engine/main/src/headless/inputProcessor.ts +++ b/web/src/engine/main/src/headless/inputProcessor.ts @@ -18,9 +18,7 @@ import { type Alternate, type Keyboard, type KeyEvent, - type OutputTargetInterface, } from "keyman/engine/keyboard"; -// TODO-web-core: remove usage of OutputTargetBase import { JSKeyboardProcessor, type ProcessorInitOptions, @@ -103,7 +101,7 @@ export class InputProcessor { * @returns {Object} A ProcessorAction object describing the cumulative effects of * all matched keyboard rules. */ - processKeyEvent(keyEvent: KeyEvent, outputTarget: OutputTargetInterface): ProcessorAction { + processKeyEvent(keyEvent: KeyEvent, outputTarget: OutputTargetBase): ProcessorAction { const kbdMismatch = keyEvent.srcKeyboard && this.activeKeyboard != keyEvent.srcKeyboard; const trueActiveKeyboard = this.activeKeyboard; @@ -157,7 +155,7 @@ export class InputProcessor { * @param outputTarget * @returns */ - private _processKeyEvent(keyEvent: KeyEvent, outputTarget: OutputTargetInterface): ProcessorAction { + private _processKeyEvent(keyEvent: KeyEvent, outputTarget: OutputTargetBase): ProcessorAction { const formFactor = keyEvent.device.formFactor; const fromOSK = keyEvent.isSynthetic; @@ -400,7 +398,7 @@ export class InputProcessor { return alternates; } - public resetContext(outputTarget?: OutputTargetInterface) { + public resetContext(outputTarget?: OutputTargetBase) { // Also handles new-context events, which may modify the layer // TODO-web-core this.keyboardProcessor.resetContext(outputTarget as OutputTargetBase); diff --git a/web/src/engine/main/src/headless/languageProcessor.ts b/web/src/engine/main/src/headless/languageProcessor.ts index 0751da2789..913562ef72 100644 --- a/web/src/engine/main/src/headless/languageProcessor.ts +++ b/web/src/engine/main/src/headless/languageProcessor.ts @@ -1,7 +1,6 @@ import { EventEmitter } from "eventemitter3"; import { LMLayer, WorkerFactory } from "@keymanapp/lexical-model-layer/web"; -// TODO-web-core: remove use of OutputTargetBase -import { Transcription, OutputTargetInterface, Mock, OutputTargetBase } from 'keyman/engine/keyboard'; +import { Transcription, OutputTargetBase, Mock } from 'keyman/engine/keyboard'; import { LanguageProcessorEventMap, ModelSpec, StateChangeEnum, ReadySuggestions } from 'keyman/engine/interfaces'; import ContextWindow from "./contextWindow.js"; import { TranscriptionCache } from "./transcriptionCache.js"; @@ -126,7 +125,7 @@ export class LanguageProcessor extends EventEmitter { }); } - public invalidateContext(outputTarget: OutputTargetInterface, layerId: string): Promise { + public invalidateContext(outputTarget: OutputTargetBase, layerId: string): Promise { // If there's no active model, there can be no predictions. // We'll also be missing important data needed to even properly REQUEST the predictions. if(!this.currentModel || !this.configuration) { @@ -155,7 +154,7 @@ export class LanguageProcessor extends EventEmitter { } } - public wordbreak(target: OutputTargetInterface, layerId: string): Promise { + public wordbreak(target: OutputTargetBase, layerId: string): Promise { if(!this.isActive) { return null; } @@ -191,9 +190,9 @@ export class LanguageProcessor extends EventEmitter { * required because layerid can be changed by PostKeystroke * @returns */ - public applySuggestion(suggestion: Suggestion, outputTarget: OutputTargetInterface, getLayerId: ()=>string): Promise { + public applySuggestion(suggestion: Suggestion, outputTarget: OutputTargetBase, getLayerId: ()=>string): Promise { if(!outputTarget) { - throw "Accepting suggestions requires a destination OutputTargetInterface instance." + throw "Accepting suggestions requires a destination OutputTargetBase instance." } if(!this.isActive) { @@ -268,9 +267,9 @@ export class LanguageProcessor extends EventEmitter { } } - public applyReversion(reversion: Reversion, outputTarget: OutputTargetInterface) { + public applyReversion(reversion: Reversion, outputTarget: OutputTargetBase) { if(!outputTarget) { - throw "Accepting suggestions requires a destination OutputTargetInterface instance." + throw "Accepting suggestions requires a destination OutputTargetBase instance." } if(!this.isActive) { @@ -312,7 +311,7 @@ export class LanguageProcessor extends EventEmitter { return promise; } - public predictFromTarget(outputTarget: OutputTargetInterface, layerId: string): Promise { + public predictFromTarget(outputTarget: OutputTargetBase, layerId: string): Promise { if(!this.isActive || !outputTarget) { return null; } diff --git a/web/src/engine/main/src/keymanEngineBase.ts b/web/src/engine/main/src/keymanEngineBase.ts index 3c88582daa..25fcca5845 100644 --- a/web/src/engine/main/src/keymanEngineBase.ts +++ b/web/src/engine/main/src/keymanEngineBase.ts @@ -1,5 +1,4 @@ import { type KeyEvent, JSKeyboard, Keyboard, KeyboardProperties, KeyboardKeymanGlobal, ProcessorAction, OutputTargetBase } from "keyman/engine/keyboard"; -// TODO-web-core: remove usage of OutputTargetBase import { ProcessorInitOptions } from 'keyman/engine/js-processor'; import { DOMKeyboardLoader as KeyboardLoader } from "keyman/engine/keyboard/dom-keyboard-loader"; import { WorkerFactory } from "@keymanapp/lexical-model-layer/web" diff --git a/web/src/tools/testing/recorder-core/src/index.ts b/web/src/tools/testing/recorder-core/src/index.ts index 131621fc1d..07e94d33b6 100644 --- a/web/src/tools/testing/recorder-core/src/index.ts +++ b/web/src/tools/testing/recorder-core/src/index.ts @@ -1,4 +1,4 @@ -import { KeyDistribution, KeyEvent, type OutputTargetInterface, Mock } from "keyman/engine/keyboard"; +import { KeyDistribution, KeyEvent, type OutputTargetBase, Mock } from "keyman/engine/keyboard"; import Proctor from "./proctor.js"; @@ -216,7 +216,7 @@ export abstract class TestSequence { + async test(proctor: Proctor, target?: OutputTargetBase): Promise<{success: boolean, result: string}> { // Start with an empty OutputTarget and a fresh KeyboardProcessor. if(!target) { target = new Mock(); diff --git a/web/src/tools/testing/recorder-core/src/nodeProctor.ts b/web/src/tools/testing/recorder-core/src/nodeProctor.ts index 52882e95a6..f70828ea8f 100644 --- a/web/src/tools/testing/recorder-core/src/nodeProctor.ts +++ b/web/src/tools/testing/recorder-core/src/nodeProctor.ts @@ -8,7 +8,7 @@ import { RecordedSyntheticKeystroke } from "./index.js"; -import { KeyEvent, KeyEventSpec, KeyboardHarness, type OutputTargetInterface, Mock, OutputTargetBase } from "keyman/engine/keyboard"; +import { KeyEvent, KeyEventSpec, KeyboardHarness, Mock, OutputTargetBase } from "keyman/engine/keyboard"; import { DeviceSpec } from "@keymanapp/web-utils"; import { JSKeyboardInterface, JSKeyboardProcessor } from 'keyman/engine/js-processor'; @@ -48,7 +48,7 @@ export default class NodeProctor extends Proctor { return true; } - async simulateSequence(sequence: TestSequence, target?: OutputTargetInterface): Promise { + async simulateSequence(sequence: TestSequence, target?: OutputTargetBase): Promise { // Start with an empty OutputTarget and a fresh KeyboardProcessor. if(!target) { target = new Mock(); diff --git a/web/src/tools/testing/recorder-core/src/proctor.ts b/web/src/tools/testing/recorder-core/src/proctor.ts index 533648e88c..db1f36110e 100644 --- a/web/src/tools/testing/recorder-core/src/proctor.ts +++ b/web/src/tools/testing/recorder-core/src/proctor.ts @@ -1,5 +1,5 @@ import { type DeviceSpec } from "@keymanapp/web-utils"; -import { type OutputTargetInterface } from "keyman/engine/keyboard"; +import { type OutputTargetBase } from "keyman/engine/keyboard"; import type { KeyboardTest, TestSet, TestSequence } from "./index.js"; @@ -49,5 +49,5 @@ export default abstract class Proctor { * Simulates the specified test sequence for use in testing. * @param sequence The recorded sequence, generally provided by a test set. */ - abstract simulateSequence(sequence: TestSequence, target?: OutputTargetInterface): Promise; + abstract simulateSequence(sequence: TestSequence, target?: OutputTargetBase): Promise; } \ No newline at end of file diff --git a/web/src/tools/testing/recorder/browserProctor.ts b/web/src/tools/testing/recorder/browserProctor.ts index cc81b89cea..822f802459 100644 --- a/web/src/tools/testing/recorder/browserProctor.ts +++ b/web/src/tools/testing/recorder/browserProctor.ts @@ -4,7 +4,7 @@ import { type DeviceSpec } from "@keymanapp/web-utils"; -import { type OutputTargetInterface } from "keyman/engine/keyboard"; +import { type OutputTargetBase } from "keyman/engine/keyboard"; import { type KeymanEngine } from 'keyman/app/browser'; @@ -81,7 +81,7 @@ export class BrowserProctor extends Proctor { // Execution of a test sequence depends on the testing environment; this handles // the browser-specific aspects. - async simulateSequence(sequence: TestSequence, outputTarget?: OutputTargetInterface): Promise { + async simulateSequence(sequence: TestSequence, outputTarget?: OutputTargetBase): Promise { const driver = new BrowserDriver(this.target); // For the version 10.0 spec From c9fa02f21c48b6f53f5e21cf049dc34b9b4feb0e Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 4 Nov 2025 11:08:41 +0100 Subject: [PATCH 04/17] =?UTF-8?q?refactor(web):=20rename=20`OutputTargetBa?= =?UTF-8?q?se`=20=E2=86=92=20`TextStore`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/web/types/package.json | 1 - common/web/types/src/keyboard-object.ts | 14 +- .../reference/interface/contextExOutput.md | 8 +- web/docs/internal/context-state-management.md | 163 +++++++++++++---- web/src/app/browser/src/beepHandler.ts | 10 +- web/src/app/browser/src/configuration.ts | 8 +- .../app/browser/src/context/focusAssistant.ts | 12 +- web/src/app/browser/src/contextManager.ts | 22 +-- .../app/browser/src/defaultBrowserRules.ts | 6 +- .../app/browser/src/hardwareEventKeyboard.ts | 4 +- web/src/app/webview/src/contextManager.ts | 8 +- .../attachment/src/outputTargetForElement.ts | 6 +- web/src/engine/element-wrappers/build.sh | 2 +- .../src/outputTargetElementWrapper.ts | 4 +- .../prediction/languageProcessor.interface.ts | 14 +- .../src/prediction/predictionContext.ts | 8 +- .../js-processor/src/jsKeyboardInterface.ts | 172 +++++++++--------- .../js-processor/src/jsKeyboardProcessor.ts | 64 +++---- web/src/engine/keyboard/src/defaultRules.ts | 8 +- web/src/engine/keyboard/src/index.ts | 4 +- .../keyboard/src/keyboards/jsKeyboard.ts | 18 +- .../keyboard/src/keyboards/transcription.ts | 6 +- web/src/engine/keyboard/src/mock.ts | 28 +-- .../src/{outputTargetBase.ts => textStore.ts} | 22 +-- web/src/engine/main/src/contextManagerBase.ts | 36 ++-- .../engine/main/src/engineConfiguration.ts | 6 +- .../main/src/headless/inputProcessor.ts | 40 ++-- .../main/src/headless/languageProcessor.ts | 44 ++--- .../engine/main/src/keyboardInterfaceBase.ts | 2 +- web/src/engine/main/src/keymanEngineBase.ts | 16 +- .../attachment/outputTargetForElement.def.ts | 2 +- .../dom/cases/browser/contextManager.tests.ts | 32 ++-- .../element-wrappers/target_mocks.tests.ts | 10 +- .../manual/web/osk/scratchspace/index.html | 4 +- .../tools/testing/recorder-core/src/index.ts | 6 +- .../testing/recorder-core/src/nodeProctor.ts | 8 +- .../testing/recorder-core/src/proctor.ts | 4 +- .../tools/testing/recorder/browserProctor.ts | 4 +- web/src/tools/testing/recorder/scribe.ts | 2 +- 39 files changed, 461 insertions(+), 367 deletions(-) rename web/src/engine/keyboard/src/{outputTargetBase.ts => textStore.ts} (92%) diff --git a/common/web/types/package.json b/common/web/types/package.json index 7823f0fa87..d29766e567 100644 --- a/common/web/types/package.json +++ b/common/web/types/package.json @@ -78,7 +78,6 @@ "src/schemas/*", "tests/", "src/keyboard-object.ts", - "src/outputTarget.interface.ts", "src/*.d.ts", "src/main.ts", "src/schema-validators.ts", diff --git a/common/web/types/src/keyboard-object.ts b/common/web/types/src/keyboard-object.ts index afd0029af0..96992e203e 100644 --- a/common/web/types/src/keyboard-object.ts +++ b/common/web/types/src/keyboard-object.ts @@ -8,7 +8,7 @@ export type ComplexKeyboardStore = (string | { t: 'd', d: number } | { ['t']: 'b // A stub for KeyEvent which is properly defined in KeymanWeb type KeyEventStub = {}; -// A stub for OutputTarget which is properly defined in KeymanWeb +// A stub for TextStore which is properly defined in KeymanWeb type OutputTargetStub = {}; export interface EncodedVisualKeyboard { @@ -43,31 +43,31 @@ export type KeyboardObject = { * group-start: the function triggering processing for the keyboard's * "Unicode" start group, corresponding to `begin Unicode > use(_____)` in * Keyman keyboard language. - * @param outputTarget The context to which the keystroke applies + * @param textStore The context to which the keystroke applies * @param keystroke The full, pre-processed keystroke triggering * keyboard-rule application. */ - gs(outputTarget: OutputTargetStub, keystroke: KeyEventStub): boolean; + gs(textStore: OutputTargetStub, keystroke: KeyEventStub): boolean; /** * group-newcontext: the function triggering processing for the keyboard's * "NewContext" start group, corresponding to `begin NewContext > use(_____)` * in Keyman keyboard language. - * @param outputTarget The new context to be used with future keystrokes + * @param textStore The new context to be used with future keystrokes * @param keystroke A 'null' `KeyEvent` providing current modifier + state information. */ - gn?(outputTarget: OutputTargetStub, keystroke: KeyEventStub): boolean; + gn?(textStore: OutputTargetStub, keystroke: KeyEventStub): boolean; /** * group-postkeystroke: the function triggering processing for the keyboard's * "PostKeystroke" start group, corresponding to `begin PostKeystroke > * use(_____)` in Keyman keyboard language. - * @param outputTarget The context altered by a recent keystroke. As a + * @param textStore The context altered by a recent keystroke. As a * precondition, all changes due to `gs` / `begin Unicode` should already be * applied. * @param keystroke A 'null' `KeyEvent` providing current modifier + state information. */ - gpk?(outputTarget: OutputTargetStub, keystroke: KeyEventStub): boolean; + gpk?(textStore: OutputTargetStub, keystroke: KeyEventStub): boolean; /** * Keyboard ID: the uniquely-identifying name for this keyboard. Includes the standard diff --git a/web/docs/engine/reference/interface/contextExOutput.md b/web/docs/engine/reference/interface/contextExOutput.md index 35a1801088..73a1a97c8a 100644 --- a/web/docs/engine/reference/interface/contextExOutput.md +++ b/web/docs/engine/reference/interface/contextExOutput.md @@ -11,13 +11,13 @@ gap between desktop and web core functionality for `context(n)` matching on `not ## Syntax ```js - keyman.interface.contextExOutput(dn, outputTarget, contextLength, contextOffset); + keyman.interface.contextExOutput(dn, textStore, contextLength, contextOffset); ``` or ```js - KeymanWeb.KCXO(dn, outputTarget, contextLength, contextOffset); // Shorthand + KeymanWeb.KCXO(dn, textStore, contextLength, contextOffset); // Shorthand ``` ## Parameters @@ -26,8 +26,8 @@ or : Type: `number` : number of characters to delete left of cursor -`outputTarget` -: Type: `OutputTarget` +`textStore` +: Type: `TextStore` : target to output to `contextLength` diff --git a/web/docs/internal/context-state-management.md b/web/docs/internal/context-state-management.md index 910129ac0b..4d776defbf 100644 --- a/web/docs/internal/context-state-management.md +++ b/web/docs/internal/context-state-management.md @@ -1,72 +1,167 @@ # Context State Management -## The `OutputTarget` Abstraction +## The `TextStore` Abstraction -The `OutputTarget` abstraction and its associated types and classes exist to facilitate handling different types of context sources within Keyman Engine for Web through a common interface. In essence, any implementing type is valid within the engine as a "target" for "output" from any existing keyboard supported by the engine. Through 18.0, only JS-based keyboards were supported due to lack of implementation of alternate keystroke-processing engines. +The `TextStore` abstraction and its associated types and classes exist +to facilitate handling different types of context sources within Keyman +Engine for Web through a common interface. In essence, any implementing +type is valid within the engine as a "target" for "output" from any +existing keyboard supported by the engine. Through 18.0, only JS-based +keyboards were supported due to lack of implementation of alternate +keystroke-processing engines. -At the most basic level, the abstraction is defined at [web/src/engine/keyboard/outputTarget.interface.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/keyboard/src/outputTarget.interface.ts). The methods defined therein support properties and methods for fetching, setting, and manipulating deadkey markers, text-selection, and text within whatever context source it represents. JS-keyboard keystroke processing directly uses these methods during operation. -- `epic/web-core` note: moved to `outputTarget.ts` in the same folder, dropping the `.interface` component. +The base implmentation may be found at +[web/src/engine/keyboard/textStore.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/keyboard/src/textStore.ts). +The methods defined therein support properties and methods for fetching, +setting, and manipulating deadkey markers, text-selection, and text +within whatever context source it represents. JS-keyboard keystroke +processing directly uses these methods during operation. -The base implementation for this type may be found at [web/src/engine/js-processor/src/outputTarget.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/js-processor/src/outputTarget.ts). This implementation provides common support for deadkey tracking, matching, and handling - the same strategy is used regardless of context-source type. A few additional methods are provided to assist with comparison and contrast between two different context states and restoration of a prior context state. -- `epic/web-core` note: renamed `OutputTargetBase`, moved to `outputTargetBase.ts` in the same folder +Since the context sources are the same for all keyboard processors, the +all use the same base implementation, although not all funcitonality is +needed by all keyboard processors (e.g. deadkey tracking functionality +is only needed by the JS-keyboard processor). This implementation +provides common support for deadkey tracking, matching, and handling - +the same strategy is used regardless of context-source type. A few +additional methods are provided to assist with comparison and contrast +between two different context states and restoration of a prior context +state. + +- note: previously called `OutputTarget` ### Deadkey management -Specifics for the implementation of JS-keyboard deadkeys can be found here: https://github.com/keymanapp/keyman/blob/b4df4ab80862bc90da42bcdbd333df0a14da01ca/web/src/engine/js-processor/src/deadkeys.ts#L2-L6 -- `epic/web-core` note: unaltered. +Specifics for the implementation of JS-keyboard deadkeys can be found +here: [web/src/engine/keyboard/src/deadkeys.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/keyboard/src/deadkeys.ts) -`ordinal`: if two deadkeys are in the same "virtual" position, this field resolves which came first. -`matched`: used during keyboard-rule processing. +`ordinal`: if two deadkeys are in the same "virtual" position, this +field resolves which came first. `matched`: used during keyboard-rule +processing. -Note that we do NOT actually insert the deadkeys into the raw text-context! Web's pattern here is different than that of Keyman Core. +Note that we do NOT actually insert the deadkeys into the raw +text-context! Web's pattern here is different than that of Keyman Core. ## Manipulating context-states -Initially used to support predictive-text, the `Transform` type aims to encapsulate the minimal information necessary to transition from one context-state to another. It is both selection-agnostic and deadkey-agnostic. `Transform`s are used both by predictive-text and by the webview-embedded build of the engine in order to succinctly communicate the data needed to update context upon receiving keystrokes. -- `Transform`'s specification may be found at [common/web/types/src/lexical-model-types.ts](https://github.com/keymanapp/keyman/blob/master/common/web/types/src/lexical-model-types.ts). +Initially used to support predictive-text, the `Transform` type aims to +encapsulate the minimal information necessary to transition from one +context-state to another. It is both selection-agnostic and +deadkey-agnostic. `Transform`s are used both by predictive-text and by +the webview-embedded build of the engine in order to succinctly +communicate the data needed to update context upon receiving keystrokes. + +- `Transform`'s specification may be found at + [common/web/types/src/lexical-model-types.ts](https://github.com/keymanapp/keyman/blob/master/common/web/types/src/lexical-model-types.ts). `Transform`s consist of three values: -- `deleteLeft` - the number of codepoints prior to the caret/selection that should be deleted -- `insert` - the text to insert at the caret and/or replace currently-selected text -- `deleteRight` - the number of codepoints _after_ the caret/selection that should be deleted - - Note that `deleteRight` does not currently see actual use due to iOS platform limitations. -This type may then be used as an argument to `OutputTarget.apply()` (defined on `OutputTarget` (`epic/web-core`: `OutputTargetBase`)) to update any context source accordingly. +- `deleteLeft` - the number of codepoints prior to the caret/selection + that should be deleted +- `insert` - the text to insert at the caret and/or replace + currently-selected text +- `deleteRight` - the number of codepoints _after_ the caret/selection + that should be deleted + - Note that `deleteRight` does not currently see actual use due to iOS + platform limitations. -It is possible to determine the `Transform` needed to transition from one `OutputTarget` to another using `OutputTarget.buildTransformFrom` (defined on `OutputTarget` (`epic/web-core`: `OutputTargetBase`)). +This type may then be used as an argument to `TextStore.apply()` +(defined on `TextStore`) to update any context source accordingly. + +It is possible to determine the `Transform` needed to transition from +one `TextStore` to another using `TextStore.buildTransformFrom` +(defined on `TextStore`). ### The `Mock` - representing context-state -The comparison and contrast methods mentioned above for `OutputTarget` are of particular use for predictive text, which usually operates with a headless implementation of the type, termed a `Mock`. This class may be found in [web/src/engine/js-processor/src/mock.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/js-processor/src/mock.ts). It is a fully-featured implementation of the `OutputTarget` interface. -- `epic/web-core` note: unaltered. +The comparison and contrast methods mentioned above for `TextStore` +are of particular use for predictive text, which usually operates with a +headless implementation of the type, termed a `Mock`. This class may be +found in +[web/src/engine/js-processor/src/mock.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/js-processor/src/mock.ts). +It is a fully-featured implementation of the `TextStore` interface. -It is possible to make a `Mock`-based clone of any `OutputTarget`-derived type - a feature leveraged significantly within the inner workings of Keyman Engine for Web. As JS keyboards can have side effects beyond text-manipulation, predictive text generally operates by first _cloning_ the "true" context source. `Mock`s are also used when saving context states within the engine for later reference and/or reuse - a feature also utilized significantly for multitap support. +It is possible to make a `Mock`-based clone of any +`TextStore`-derived type - a feature leveraged significantly within +the inner workings of Keyman Engine for Web. As JS keyboards can have +side effects beyond text-manipulation, predictive text generally +operates by first _cloning_ the "true" context source. `Mock`s are +also used when saving context states within the engine for later +reference and/or reuse - a feature also utilized significantly for +multitap support. -`Mock`s can also easily be constructed from scratch for a simple string. Optionally, caret position or selection data may be specified at construction time as well. `epic/web-core`: in theory, this should make them easy to utilize for integration with Keyman Core. +`Mock`s can also easily be constructed from scratch for a simple string. +Optionally, caret position or selection data may be specified at +construction time as well. `epic/web-core`: in theory, this should make +them easy to utilize for integration with Keyman Core. ### The `Transcription` - representing context-state transitions -The `Transcription` class (defined within [web/src/engine/js-processor/src/outputTarget.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/js-processor/src/outputTarget.ts)) is the type within the engine that most closely matches a sense of transition in context state. These are generally constructed by comparing two `OutputTarget` instances to each other via `OutputTarget.buildTranscriptionFrom`, with the base instance corresponding to the "new" state and the first parameter matching the original state before transition. -- `epic/web-core` note: moved to `outputTargetBase.ts` in the same folder; the method is on `OutputTargetBase`. +The `Transcription` class (defined in +[web/src/engine/js-processor/src/transcription.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/js-processor/src/transcription.ts)) +is the type within the engine that most closely matches a sense of +transition in context state. These are generally constructed by +comparing two `TextStore` instances to each other via +`TextStore.buildTranscriptionFrom`, with the base instance +corresponding to the "new" state and the first parameter matching the +original state before transition. -`Transcription`s are granted unique identifiers and are used within the engine for preservation of recent context states. These identifiers are currently generated within the class's constructor and are internally set. When predictive-text generates new suggestions or a multitap needs to revert to a prior context, both will use the `Transcription`'s unique identifier in order to find the corresponding context state and leverage it as needed for their operations. +`Transcription`s are granted unique identifiers and are used within the +engine for preservation of recent context states. These identifiers are +currently generated within the class's constructor and are internally +set. When predictive-text generates new suggestions or a multitap needs +to revert to a prior context, both will use the `Transcription`'s unique +identifier in order to find the corresponding context state and leverage +it as needed for their operations. Important fields: -- `keystroke` - the keystroke that triggered the context change corresponding to this `Transcription` + +- `keystroke` - the keystroke that triggered the context change + corresponding to this `Transcription` - `transform` - the direct effects of the keystroke - - This uses a specialized variant that also notes if the transition destroyed previously-existing selected text. -- `preInput` - the state of the context immediately before the keystroke was processed -Note that the transition metadata does not include deadkeys generated by its triggering `keystroke`. The decision was made long ago to forgo directly recording deadkey changes when recording `Transcriptions`, as any operation that restores an old context also seeks to apply deadkey-destroying operations immediately afterward. Should we ever need to do so, PR #1611 contains code that was originally designed for actively detecting and recording deadkey transition data in `Transcription`s. + - This uses a specialized variant that also notes if the transition + destroyed previously-existing selected text. -A cache of recent context-state transitions is stored at `keyman.core.contextCache`, with `keyman.core` being an instance of `InputProcessor` ([web/src/engine/main/src/headless/inputProcessor.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/main/src/headless/inputProcessor.ts)), the component responsible for linking keystroke processing with predictive-text support and restoration of context-state for multitap-generated keystrokes. +- `preInput` - the state of the context immediately before the keystroke + was processed + +Note that the transition metadata does not include deadkeys generated by +its triggering `keystroke`. The decision was made long ago to forgo +directly recording deadkey changes when recording `Transcriptions`, as +any operation that restores an old context also seeks to apply +deadkey-destroying operations immediately afterward. Should we ever +need to do so, PR #1611 contains code that was originally designed for +actively detecting and recording deadkey transition data in +`Transcription`s. + +A cache of recent context-state transitions is stored at +`keyman.core.contextCache`, with `keyman.core` being an instance of +`InputProcessor` +([web/src/engine/main/src/headless/inputProcessor.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/main/src/headless/inputProcessor.ts)), +the component responsible for linking keystroke processing with +predictive-text support and restoration of context-state for +multitap-generated keystrokes. ### JS-keyboard keystroke processing -For JS-keyboard keystroke processing, a `Mock` clone of the context is generated before any actual keyboard rule checks are applied. This provides a clear "before" state (eventually saved at `Transcription.preInput`) useful for determining the scope of the keystroke's changes once processing is completed via `buildTranscriptionFrom`. +For JS-keyboard keystroke processing, a `Mock` clone of the context is +generated before any actual keyboard rule checks are applied. This +provides a clear "before" state (eventually saved at +`Transcription.preInput`) useful for determining the scope of the +keystroke's changes once processing is completed via +`buildTranscriptionFrom`. -Once keystroke processing is completed by a JS-keyboard, the JS-processor constructs a `ProcessorAction` object describing all primary and side effects of the keystroke. Defined at [web/src/engine/keyboard/src/keyboards/processorAction.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/keyboard/src/keyboards/processorAction.ts), all of its fields aside from `transcription` are specific to JS-keyboard side effects, some of which do need special handling and support outside of the keystroke processor. None of these side effects apply for common-case keystrokes and so have default handling in place within the engine for cases where they are not needed. +Once keystroke processing is completed by a JS-keyboard, the +JS-processor constructs a `ProcessorAction` object describing all +primary and side effects of the keystroke. Defined at +[web/src/engine/keyboard/src/keyboards/processorAction.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/keyboard/src/keyboards/processorAction.ts), +all of its fields aside from `transcription` are specific to JS-keyboard +side effects, some of which do need special handling and support outside +of the keystroke processor. None of these side effects apply for +common-case keystrokes and so have default handling in place within the +engine for cases where they are not needed. --- -In case referenced classes/files have moved: this doc was last updated in 19.0-alpha, based upon PR #14001. +In case referenced classes/files have moved: this doc was last updated +in 19.0-alpha, based upon PR #14001. diff --git a/web/src/app/browser/src/beepHandler.ts b/web/src/app/browser/src/beepHandler.ts index 9d1896b10a..05fb6516bb 100644 --- a/web/src/app/browser/src/beepHandler.ts +++ b/web/src/app/browser/src/beepHandler.ts @@ -33,15 +33,15 @@ export class BeepHandler { * @param {Object} Pelem element to flash * Description Flash body as substitute for audible beep; notify embedded device to vibrate */ - beep(outputTarget: OutputTargetElementWrapper) { - if (!(outputTarget instanceof OutputTargetElementWrapper)) { + beep(textStore: OutputTargetElementWrapper) { + if (!(textStore instanceof OutputTargetElementWrapper)) { return; } // All code after this point is DOM-based, triggered by the beep. - let Pelem: HTMLElement = outputTarget.getElement(); - if(outputTarget instanceof DesignIFrame) { - Pelem = outputTarget.docRoot; // I1446 - beep sometimes fails to flash when using OSK and rich control + let Pelem: HTMLElement = textStore.getElement(); + if(textStore instanceof DesignIFrame) { + Pelem = textStore.docRoot; // I1446 - beep sometimes fails to flash when using OSK and rich control } if(!Pelem) { diff --git a/web/src/app/browser/src/configuration.ts b/web/src/app/browser/src/configuration.ts index faa24c577c..60f033a358 100644 --- a/web/src/app/browser/src/configuration.ts +++ b/web/src/app/browser/src/configuration.ts @@ -1,7 +1,7 @@ import { EngineConfiguration, InitOptionSpec, InitOptionDefaults } from "keyman/engine/main"; import { OutputTargetElementWrapper as DOMOutputTarget } from 'keyman/engine/element-wrappers'; -import { OutputTargetBase, ProcessorAction } from 'keyman/engine/keyboard'; +import { TextStore, ProcessorAction } from 'keyman/engine/keyboard'; import { isEmptyTransform } from '@keymanapp/web-utils'; import { AlertHost } from "./utils/alertHost.js"; import { whenDocumentReady } from "./utils/documentReady.js"; @@ -66,14 +66,14 @@ export class BrowserConfiguration extends EngineConfiguration { return baseReport; } - onRuleFinalization(ruleBehavior: ProcessorAction, outputTarget: OutputTargetBase) { + onRuleFinalization(ruleBehavior: ProcessorAction, textStore: TextStore) { // TODO: Patch up to modularized form. But that doesn't exist yet for some of these... // If the transform isn't empty, we've changed text - which should produce a 'changed' event in the DOM. const ruleTransform = ruleBehavior.transcription.transform; if(!isEmptyTransform(ruleTransform)) { - if(outputTarget instanceof DOMOutputTarget) { - outputTarget.changed = true; + if(textStore instanceof DOMOutputTarget) { + textStore.changed = true; } } } diff --git a/web/src/app/browser/src/context/focusAssistant.ts b/web/src/app/browser/src/context/focusAssistant.ts index 6a7519eefb..ee67fde283 100644 --- a/web/src/app/browser/src/context/focusAssistant.ts +++ b/web/src/app/browser/src/context/focusAssistant.ts @@ -13,7 +13,7 @@ export class FocusStateAPIObject { activated: boolean; /** - * Indicates that KMW is actively maintaining focus on the currently active OutputTarget control + * Indicates that KMW is actively maintaining focus on the currently active TextStore control * while some UI element (the OSK, a keyboard-change UI) is the current focus of user-interaction. */ activationPending: boolean; @@ -35,7 +35,7 @@ interface EventMap { // Formerly handled under "UIManager". /** * This class provides fields and methods useful for assisting context management. Control focus (and - * thus, activation of the corresponding OutputTarget) should not be lost to non-context components of + * thus, activation of the corresponding TextStore) should not be lost to non-context components of * KMW, such as the OSK or a keyboard selector. */ export class FocusAssistant extends EventEmitter { @@ -57,16 +57,16 @@ export class FocusAssistant extends EventEmitter { * Long-term idea here: about all of the relevant OSK events that would interact with this have "enter" and * "leave" variants - we could take a stack of `Promise`s. On a `Promise` fulfillment, remove it from the * stack. When the last one is removed, the focus-maintenance state would end, allowing further events - * to deactivate the active OutputTarget. + * to deactivate the active TextStore. */ /** - * Indicates that KMW is actively maintaining focus on the currently active OutputTarget control, rather + * Indicates that KMW is actively maintaining focus on the currently active TextStore control, rather * than losing focus while some UI element (the OSK, a keyboard-change UI) is the most direct recipient * of browser focus due to user-interaction - generally, with non-context engine components. * - * While the flag is active, the context-management system should not deactivate an OutputTarget upon - * its element's loss of focus within the page unless setting a different OutputTarget as active. + * While the flag is active, the context-management system should not deactivate an TextStore upon + * its element's loss of focus within the page unless setting a different TextStore as active. * * TODO: (potential) Future enhancement - this should not be possible to set if there is no currently-active * context target to maintain. diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 3826f78bb4..fb7e5ef92c 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -350,13 +350,13 @@ export default class ContextManager extends ContextManagerBase): boolean { + _CommonFocusHelper(textStore: OutputTargetElementWrapper): boolean { const focusAssistant = this.focusAssistant; const activeKeyboard = this.activeKeyboard?.keyboard; if(!focusAssistant.restoringFocus) { - outputTarget?.deadkeys().clear(); - activeKeyboard?.notify(0, outputTarget, 1); // I2187 + textStore?.deadkeys().clear(); + activeKeyboard?.notify(0, textStore, 1); // I2187 } - if(!focusAssistant.restoringFocus && this.mostRecentTarget != outputTarget) { + if(!focusAssistant.restoringFocus && this.mostRecentTarget != textStore) { focusAssistant.maintainingFocus = false; } focusAssistant.restoringFocus = false; @@ -642,7 +642,7 @@ export default class ContextManager extends ContextManagerBase { - // Step 1: determine the corresponding OutputTarget instance. + // Step 1: determine the corresponding TextStore instance. const target = eventOutputTarget(e); if(!target) { // Probably should also make a warning or error? @@ -654,7 +654,7 @@ export default class ContextManager extends ContextManagerBase { @@ -56,6 +56,6 @@ export default class DefaultBrowserRules extends DefaultRules { break; } - super.applyCommand(Lkc, outputTarget); + super.applyCommand(Lkc, textStore); } } \ No newline at end of file diff --git a/web/src/app/browser/src/hardwareEventKeyboard.ts b/web/src/app/browser/src/hardwareEventKeyboard.ts index fde312baef..c25497aae3 100644 --- a/web/src/app/browser/src/hardwareEventKeyboard.ts +++ b/web/src/app/browser/src/hardwareEventKeyboard.ts @@ -399,8 +399,8 @@ export default class HardwareEventKeyboard extends HardKeyboardBase { return true; } - const outputTarget = eventOutputTarget(e); - return this.processor.doModifierPress(Levent, outputTarget, false); + const textStore = eventOutputTarget(e); + return this.processor.doModifierPress(Levent, textStore, false); } private keyPress(e: KeyboardEvent): boolean { diff --git a/web/src/app/webview/src/contextManager.ts b/web/src/app/webview/src/contextManager.ts index 1c5e14094a..63d28cf850 100644 --- a/web/src/app/webview/src/contextManager.ts +++ b/web/src/app/webview/src/contextManager.ts @@ -1,4 +1,4 @@ -import { JSKeyboard, Keyboard, OutputTargetBase, Transcription, TextTransform, Mock, findCommonSubstringEndIndex } from 'keyman/engine/keyboard'; +import { JSKeyboard, Keyboard, TextStore, Transcription, TextTransform, Mock, findCommonSubstringEndIndex } from 'keyman/engine/keyboard'; import { KeyboardStub } from 'keyman/engine/keyboard-storage'; import { ContextManagerBase } from 'keyman/engine/main'; import { WebviewConfiguration } from './configuration.js'; @@ -58,10 +58,10 @@ export class ContextHost extends Mock { this.savedState = Mock.from(this); } - restoreTo(original: OutputTargetBase): void { + restoreTo(original: TextStore): void { this.savedState = Mock.from(this); // TODO-web-core - super.restoreTo(original as OutputTargetBase); + super.restoreTo(original as TextStore); } updateContext(text: string, selStart: number, selEnd: number): boolean { @@ -138,7 +138,7 @@ export default class ContextManager extends ContextManagerBase extends OutputTargetBase { +export abstract class OutputTargetElementWrapper extends TextStore { // JS/TS can't do true multiple inheritance, so we maintain class events on a readonly field. public readonly events: EventEmitter = new EventEmitter(); diff --git a/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts b/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts index 5d9745726b..d15898078d 100644 --- a/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts +++ b/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts @@ -1,6 +1,6 @@ import { LexicalModelTypes } from '@keymanapp/common-types'; import { EventEmitter } from "eventemitter3"; -import { OutputTargetBase } from "keyman/engine/keyboard"; +import { TextStore } from "keyman/engine/keyboard"; export class ReadySuggestions { suggestions: LexicalModelTypes.Suggestion[]; @@ -45,10 +45,10 @@ export interface LanguageProcessorEventMap { /** * Is called synchronously once suggestion application is successful and the context has been updated. * - * @param outputTarget The `OutputTargetBase` representation of the context the suggestion was applied to. + * @param textStore The `TextStore` representation of the context the suggestion was applied to. * @returns */ - 'suggestionapplied': (outputTarget: OutputTargetBase) => boolean + 'suggestionapplied': (textStore: TextStore) => boolean } @@ -56,19 +56,19 @@ export interface LanguageProcessorSpec extends EventEmitter; + invalidateContext(textStore: TextStore, layerId: string): Promise; /** * * @param suggestion - * @param outputTarget + * @param textStore * @param getLayerId a function that returns the current layerId, * required because layerid can be changed by PostKeystroke * @returns */ - applySuggestion(suggestion: LexicalModelTypes.Suggestion, outputTarget: OutputTargetBase, getLayerId: () => string): Promise; + applySuggestion(suggestion: LexicalModelTypes.Suggestion, textStore: TextStore, getLayerId: () => string): Promise; - applyReversion(reversion: LexicalModelTypes.Reversion, outputTarget: OutputTargetBase): Promise; + applyReversion(reversion: LexicalModelTypes.Reversion, textStore: TextStore): Promise; get wordbreaksAfterSuggestions(): boolean; diff --git a/web/src/engine/interfaces/src/prediction/predictionContext.ts b/web/src/engine/interfaces/src/prediction/predictionContext.ts index ae2d097aa4..7ee27de9bf 100644 --- a/web/src/engine/interfaces/src/prediction/predictionContext.ts +++ b/web/src/engine/interfaces/src/prediction/predictionContext.ts @@ -4,7 +4,7 @@ import Keep = LexicalModelTypes.Keep; import Reversion = LexicalModelTypes.Reversion; import Suggestion = LexicalModelTypes.Suggestion; import { type LanguageProcessorSpec , ReadySuggestions, type InvalidateSourceEnum, StateChangeHandler } from './languageProcessor.interface.js'; -import { type OutputTargetBase } from "keyman/engine/keyboard"; +import { type TextStore } from "keyman/engine/keyboard"; interface PredictionContextEventMap { update: (suggestions: Suggestion[]) => void; @@ -41,13 +41,13 @@ export default class PredictionContext extends EventEmitter { + public setCurrentTarget(target: TextStore): Promise { const originalTarget = this._currentTarget; this._currentTarget = target; diff --git a/web/src/engine/js-processor/src/jsKeyboardInterface.ts b/web/src/engine/js-processor/src/jsKeyboardInterface.ts index 1bfa90ef60..3a45cbaa6b 100644 --- a/web/src/engine/js-processor/src/jsKeyboardInterface.ts +++ b/web/src/engine/js-processor/src/jsKeyboardInterface.ts @@ -20,7 +20,7 @@ import { SystemStoreIDs, type Deadkey, type KeyEvent, - type OutputTargetBase, + type TextStore, VariableStore, VariableStoreDictionary, VariableStoreSerializer, @@ -186,7 +186,7 @@ export class JSKeyboardInterface extends KeyboardHarness { cachedContextEx: CachedContextEx = new CachedContextEx(); ruleContextEx: CachedContextEx; - activeTargetOutput: OutputTargetBase; + activeTargetOutput: TextStore; ruleBehavior: ProcessorAction; systemStores: {[storeID: number]: SystemStore}; @@ -229,7 +229,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * A text-insertion method used by custom OSKs for helpHTML interaction, like with sil_euro_latin. * * This function currently bypasses web-core's standard text handling control path and all predictive text processing. - * It also has DOM-dependencies that help ensure KMW's active OutputTarget retains focus during use. + * It also has DOM-dependencies that help ensure KMW's active TextStore retains focus during use. */ insertText?: (Ptext: string, PdeadKey: number) => void; @@ -254,7 +254,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * * @param {number} n Number of characters to move back from caret * @param {number} ln Number of characters to return - * @param {Object} outputTarget Element to work with (must be currently focused element) + * @param {Object} textStore Element to work with (must be currently focused element) * @return {string} Context string * * Example [abcdef|ghi] as INPUT, with the caret position marked by |: @@ -263,13 +263,13 @@ export class JSKeyboardInterface extends KeyboardHarness { * KC(10,10,Pelem) == "abcdef" i.e. return as much as possible of the requested string */ - context(n: number, ln: number, outputTarget: OutputTargetBase): string { + context(n: number, ln: number, textStore: TextStore): string { const v = this.cachedContext.get(n, ln); if(v !== null) { return v; } - const r = this.KC_(n, ln, outputTarget); + const r = this.KC_(n, ln, textStore); this.cachedContext.set(n, ln, r); return r; } @@ -279,7 +279,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * * @param {number} n Number of characters to move back from caret * @param {number} ln Number of characters to return - * @param {Object} outputTarget Element to work with (must be currently focused element) + * @param {Object} textStore Element to work with (must be currently focused element) * @return {string} Context string * * Example [abcdef|ghi] as INPUT, with the caret position marked by |: @@ -287,11 +287,11 @@ export class JSKeyboardInterface extends KeyboardHarness { * KC(3,3,Pelem) == "def" * KC(10,10,Pelem) == "XXXXabcdef" i.e. return as much as possible of the requested string, where X = \uFFFE */ - private KC_(n: number, ln: number, outputTarget: OutputTargetBase): string { + private KC_(n: number, ln: number, textStore: TextStore): string { let tempContext = ''; // If we have a selection, we have an empty context - tempContext = outputTarget.isSelectionEmpty() ? outputTarget.getTextBeforeCaret() : ""; + tempContext = textStore.isSelectionEmpty() ? textStore.getTextBeforeCaret() : ""; if(KMWString.length(tempContext) < n) { tempContext = Array(n-KMWString.length(tempContext)+1).join("\uFFFE") + tempContext; @@ -304,7 +304,7 @@ export class JSKeyboardInterface extends KeyboardHarness { * Function nul KN * Scope Public * @param {number} n Length of context to check - * @param {Object} outputTarget Element to work with (must be currently focused element) + * @param {Object} textStore Element to work with (must be currently focused element) * @return {boolean} True if length of context is less than or equal to n * Description Test length of context, return true if the length of the context is less than or equal to n * @@ -313,8 +313,8 @@ export class JSKeyboardInterface extends KeyboardHarness { * KN(2,Pelem) == FALSE * KN(4,Pelem) == TRUE */ - nul(n: number, outputTarget: OutputTargetBase): boolean { - const cx=this.context(n+1, 1, outputTarget); + nul(n: number, textStore: TextStore): boolean { + const cx=this.context(n+1, 1, textStore); // With #31, the result will be a replacement character if context is empty. return cx === "\uFFFE"; @@ -324,18 +324,18 @@ export class JSKeyboardInterface extends KeyboardHarness { * Function contextMatch KCM * Scope Public * @param {number} n Number of characters to move back from caret - * @param {Object} outputTarget Focused element + * @param {Object} textStore Focused element * @param {string} val String to match * @param {number} ln Number of characters to return * @return {boolean} True if selected context matches val * Description Test keyboard context for match */ - contextMatch(n: number, outputTarget: OutputTargetBase, val: string, ln: number): boolean { - const cx=this.context(n, ln, outputTarget); + contextMatch(n: number, textStore: TextStore, val: string, ln: number): boolean { + const cx=this.context(n, ln, textStore); if(cx === val) { return true; // I3318 } - (outputTarget as OutputTargetBase).deadkeys().resetMatched(); // I3318 + (textStore as TextStore).deadkeys().resetMatched(); // I3318 return false; } @@ -344,10 +344,10 @@ export class JSKeyboardInterface extends KeyboardHarness { * * @param {number} n Number of characters to move back from caret * @param {number} ln Number of characters to return - * @param {Object} outputTarget Element to work with (must be currently focused element) + * @param {Object} textStore Element to work with (must be currently focused element) * @return {Array} Context array (of strings and numbers) */ - private _BuildExtendedContext(n: number, ln: number, outputTarget: OutputTargetBase): CachedExEntry { + private _BuildExtendedContext(n: number, ln: number, textStore: TextStore): CachedExEntry { let cache: CachedExEntry = this.cachedContextEx.get(n, ln); if(cache !== null) { return cache; @@ -357,14 +357,14 @@ export class JSKeyboardInterface extends KeyboardHarness { cache = this.cachedContextEx.get(n, n); if(cache === null) { // First, let's make sure we have a cloned, sorted copy of the deadkey array. - const unmatchedDeadkeys = outputTarget.deadkeys().toSortedArray(); // Is reverse-order sorted for us already. + const unmatchedDeadkeys = textStore.deadkeys().toSortedArray(); // Is reverse-order sorted for us already. // Time to build from scratch! let index = 0; cache = { valContext: [], deadContext: []}; while(cache.valContext.length < n) { // As adapted from `deadkeyMatch`. - const sp = outputTarget.getDeadkeyCaret(); + const sp = textStore.getDeadkeyCaret(); const deadPos = sp - index; if(unmatchedDeadkeys.length > 0 && unmatchedDeadkeys[0].p > deadPos) { // We have deadkeys at the right-hand side of the caret! They don't belong in the context, so pop 'em off. @@ -377,7 +377,7 @@ export class JSKeyboardInterface extends KeyboardHarness { unmatchedDeadkeys.splice(0, 1); } else { // Take the character. We get "\ufffe" if it doesn't exist. - const kc = this.context(++index, 1, outputTarget); + const kc = this.context(++index, 1, textStore); cache.valContext = ([kc] as (string|number)[]).concat(cache.valContext); } } @@ -397,16 +397,16 @@ export class JSKeyboardInterface extends KeyboardHarness { * Function fullContextMatch KFCM * Scope Private * @param {number} n Number of characters to move back from caret - * @param {Object} outputTarget Focused element + * @param {Object} textStore Focused element * @param {Array} rule An array of ContextEntries to match. * @return {boolean} True if the fully-specified rule context matches the current KMW state. * * A KMW 10+ function designed to bring KMW closer to Keyman Desktop functionality, * near-directly modeling (externally) the compiled form of Desktop rules' context section. */ - fullContextMatch(n: number, outputTarget: OutputTargetBase, rule: ContextEntry[]): boolean { + fullContextMatch(n: number, textStore: TextStore, rule: ContextEntry[]): boolean { // Stage one: build the context index map. - const fullContext = this._BuildExtendedContext(n, rule.length, outputTarget); + const fullContext = this._BuildExtendedContext(n, rule.length, textStore); this.ruleContextEx = this.cachedContextEx.clone(); const context = fullContext.valContext; const deadContext = fullContext.deadContext; @@ -497,7 +497,7 @@ export class JSKeyboardInterface extends KeyboardHarness { if(mismatch) { // Reset the matched 'any' indices, if any. - outputTarget.deadkeys().resetMatched(); + textStore.deadkeys().resetMatched(); this._AnyIndices = []; } @@ -588,7 +588,7 @@ export class JSKeyboardInterface extends KeyboardHarness { retVal = (keyCode == Lrulekey); // I3318, I3555 } if(!retVal) { - (this.activeTargetOutput as OutputTargetBase).deadkeys().resetMatched(); // I3318 + (this.activeTargetOutput as TextStore).deadkeys().resetMatched(); // I3318 } return retVal; // I3318 }; @@ -623,22 +623,22 @@ export class JSKeyboardInterface extends KeyboardHarness { * Function deadkeyMatch KDM * Scope Public * @param {number} n offset from current cursor position - * @param {Object} outputTarget target element + * @param {Object} textStore target element * @param {number} d deadkey * @return {boolean} True if deadkey found selected context matches val * Description Match deadkey at current cursor position */ - deadkeyMatch(n: number, outputTarget: OutputTargetBase, d: number): boolean { - return outputTarget.hasDeadkeyMatch(n, d); + deadkeyMatch(n: number, textStore: TextStore, d: number): boolean { + return textStore.hasDeadkeyMatch(n, d); } /** * Function beep KB * Scope Public - * @param {Object} outputTarget element to flash + * @param {Object} textStore element to flash * Description Flash body as substitute for audible beep; notify embedded device to vibrate */ - beep(outputTarget: OutputTargetBase): void { + beep(textStore: TextStore): void { this.resetContextCache(); // Denote as part of the matched rule's behavior. @@ -728,10 +728,10 @@ export class JSKeyboardInterface extends KeyboardHarness { * @param {number} Pdn no of character to overwrite (delete) * @param {string} Ps string * @param {number} Pn index - * @param {Object} outputTarget element to output to + * @param {Object} textStore element to output to * Description Output a character selected from the string according to the offset in the index array */ - indexOutput(Pdn: number, Ps: KeyboardStore, Pn: number, outputTarget: OutputTargetBase): void { + indexOutput(Pdn: number, Ps: KeyboardStore, Pn: number, textStore: TextStore): void { this.resetContextCache(); const assertNever = function(x: never): never { @@ -742,20 +742,20 @@ export class JSKeyboardInterface extends KeyboardHarness { const indexChar = this._Index(Ps, Pn); if(indexChar !== "") { if(typeof indexChar == 'string' ) { - this.output(Pdn, outputTarget, indexChar); //I3319 + this.output(Pdn, textStore, indexChar); //I3319 } else if(indexChar.t) { switch(indexChar.t) { case 'b': // Beep commands may appear within stores. - this.beep(outputTarget); + this.beep(textStore); break; case 'd': - this.deadkeyOutput(Pdn, outputTarget, indexChar.d); + this.deadkeyOutput(Pdn, textStore, indexChar.d); break; default: assertNever(indexChar); } } else { // For keyboards developed during 10.0's alpha phase - t:'d' was assumed. - this.deadkeyOutput(Pdn, outputTarget, (indexChar as any).d); + this.deadkeyOutput(Pdn, textStore, (indexChar as any).d); } } } @@ -765,15 +765,15 @@ export class JSKeyboardInterface extends KeyboardHarness { * Function deleteContext KDC * Scope Public * @param {number} dn number of context entries to overwrite - * @param {Object} outputTarget element to output to + * @param {Object} textStore element to output to * Description Keyboard output */ - deleteContext(dn: number, outputTarget: OutputTargetBase): void { + deleteContext(dn: number, textStore: TextStore): void { let context: CachedExEntry; // We want to control exactly which deadkeys get removed. if(dn > 0) { - context = this._BuildExtendedContext(dn, dn, (outputTarget as OutputTargetBase)); + context = this._BuildExtendedContext(dn, dn, (textStore as TextStore)); let nulCount = 0; for(let i=0; i < context.valContext.length; i++) { @@ -781,7 +781,7 @@ export class JSKeyboardInterface extends KeyboardHarness { if(dk) { // Remove deadkey in context. - (outputTarget as OutputTargetBase).deadkeys().remove(dk); + (textStore as TextStore).deadkeys().remove(dk); // Reduce our reported context size. dn--; @@ -800,33 +800,33 @@ export class JSKeyboardInterface extends KeyboardHarness { } // If a matched deadkey hasn't been deleted, we don't WANT to delete it. - (outputTarget as OutputTargetBase).deadkeys().resetMatched(); + (textStore as TextStore).deadkeys().resetMatched(); // Why reinvent the wheel? Delete the remaining characters by 'inserting a blank string'. - this.output(dn, outputTarget, ''); + this.output(dn, textStore, ''); } /** * Function output KO * Scope Public * @param {number} dn number of characters to overwrite - * @param {Object} outputTarget element to output to + * @param {Object} textStore element to output to * @param {string} s string to output * Description Keyboard output */ - output(dn: number, outputTarget: OutputTargetBase, s:string): void { + output(dn: number, textStore: TextStore, s:string): void { this.resetContextCache(); - outputTarget.saveProperties(); - outputTarget.clearSelection(); - (outputTarget as OutputTargetBase).deadkeys().deleteMatched(); // I3318 + textStore.saveProperties(); + textStore.clearSelection(); + (textStore as TextStore).deadkeys().deleteMatched(); // I3318 if(dn >= 0) { // Automatically manages affected deadkey positions. Does not delete deadkeys b/c legacy behavior support. - outputTarget.deleteCharsBeforeCaret(dn); + textStore.deleteCharsBeforeCaret(dn); } // Automatically manages affected deadkey positions. - outputTarget.insertTextBeforeCaret(s); - outputTarget.restoreProperties(); + textStore.insertTextBeforeCaret(s); + textStore.restoreProperties(); } /** @@ -837,23 +837,23 @@ export class JSKeyboardInterface extends KeyboardHarness { * @alias KCXO * @public * @param {number} Pdn number of characters to delete left of cursor - * @param {OutputTargetBase} outputTarget target to output to + * @param {TextStore} textStore target to output to * @param {number} contextLength length of current rule context to retrieve * @param {number} contextOffset offset from start of current rule context, 1-based */ - contextExOutput(Pdn: number, outputTarget: OutputTargetBase, contextLength: number, contextOffset: number): void { + contextExOutput(Pdn: number, textStore: TextStore, contextLength: number, contextOffset: number): void { this.resetContextCache(); if(Pdn >= 0) { - this.output(Pdn, outputTarget, ""); + this.output(Pdn, textStore, ""); } const context = this.ruleContextEx.get(contextLength, contextLength); const dk = context.deadContext[contextOffset-1], vc = context.valContext[contextOffset-1]; if(dk) { - outputTarget.insertDeadkeyBeforeCaret(dk.d); + textStore.insertDeadkeyBeforeCaret(dk.d); } else if(typeof vc == 'string') { - this.output(-1, outputTarget, vc); + this.output(-1, textStore, vc); } else { throw new Error("contextExOutput: should never be a numeric valContext with no corresponding deadContext"); } @@ -863,18 +863,18 @@ export class JSKeyboardInterface extends KeyboardHarness { * Function deadkeyOutput KDO * Scope Public * @param {number} Pdn no of character to overwrite (delete) - * @param {OutputTargetBase} outputTarget element to output to + * @param {TextStore} textStore element to output to * @param {number} Pd deadkey id * Description Record a deadkey at current cursor position, deleting Pdn characters first */ - deadkeyOutput(Pdn: number, outputTarget: OutputTargetBase, Pd: number): void { + deadkeyOutput(Pdn: number, textStore: TextStore, Pd: number): void { this.resetContextCache(); if(Pdn >= 0) { - this.output(Pdn, outputTarget,""); //I3318 corrected to >= + this.output(Pdn, textStore,""); //I3318 corrected to >= } - outputTarget.insertDeadkeyBeforeCaret(Pd); + textStore.insertDeadkeyBeforeCaret(Pd); // _DebugDeadKeys(Pelem, 'KDeadKeyOutput: dn='+Pdn+'; deadKey='+Pd); } @@ -883,10 +883,10 @@ export class JSKeyboardInterface extends KeyboardHarness { * * @param {number} systemId ID of the system store to test (only TSS_LAYER currently supported) * @param {string} strValue String value to compare to - * @param {OutputTargetBase} outputTarget Currently active element (may be needed by future tests) + * @param {TextStore} textStore Currently active element (may be needed by future tests) * @return {boolean} True if the test succeeds */ - ifStore(systemId: number, strValue: string, outputTarget: OutputTargetBase): boolean { + ifStore(systemId: number, strValue: string, textStore: TextStore): boolean { let result=true; const store = this.systemStores[systemId]; if(store) { @@ -900,14 +900,14 @@ export class JSKeyboardInterface extends KeyboardHarness { * * @param {number} systemId ID of the system store to set (only TSS_LAYER currently supported) * @param {string} strValue String to set as the system store content - * @param {OutputTargetBase} outputTarget Currently active element (may be needed in future tests) + * @param {TextStore} textStore Currently active element (may be needed in future tests) * @return {boolean} True if command succeeds * (i.e. for TSS_LAYER, if the layer is successfully selected) * * Note that option/variable stores are instead set within keyboard script code, as they only * affect keyboard behavior. */ - setStore(systemId: number, strValue: string, outputTarget: OutputTargetBase): boolean { + setStore(systemId: number, strValue: string, textStore: TextStore): boolean { this.resetContextCache(); // Unique case: we only allow set(&layer) ops from keyboard rules triggered by touch OSKs. if(systemId == SystemStoreIDs.TSS_LAYER && this.activeDevice.touchable) { @@ -977,64 +977,64 @@ export class JSKeyboardInterface extends KeyboardHarness { this.cachedContextEx.reset(); } - defaultBackspace(outputTarget: OutputTargetBase) { - if(outputTarget.isSelectionEmpty()) { + defaultBackspace(textStore: TextStore) { + if(textStore.isSelectionEmpty()) { // Delete the character left of the caret - this.output(1, outputTarget, ""); + this.output(1, textStore, ""); } else { // Delete just the selection - this.output(0, outputTarget, ""); + this.output(0, textStore, ""); } } /** * Function processNewContextEvent * Scope Private - * @param {Object} outputTarget The target receiving input + * @param {Object} textStore The target receiving input * @param {Object} keystroke The input keystroke (with its properties) to be mapped by the keyboard. * Description Calls the keyboard's `begin newContext` group * @returns {ProcessorAction} Record of commands and state changes that result from executing `begin NewContext` */ - processNewContextEvent(outputTarget: OutputTargetBase, keystroke: KeyEvent): ProcessorAction { + processNewContextEvent(textStore: TextStore, keystroke: KeyEvent): ProcessorAction { if(!this.activeKeyboard) { throw "No active keyboard for keystroke processing!"; } - return this.process(this.activeKeyboard.processNewContextEvent.bind(this.activeKeyboard), outputTarget, keystroke, true); + return this.process(this.activeKeyboard.processNewContextEvent.bind(this.activeKeyboard), textStore, keystroke, true); } /** * Function processPostKeystroke * Scope Private - * @param {Object} outputTarget The target receiving input + * @param {Object} textStore The target receiving input * @param {Object} keystroke The input keystroke with relevant properties to be mapped by the keyboard. * Description Calls the keyboard's `begin postKeystroke` group * @returns {ProcessorAction} Record of commands and state changes that result from executing `begin PostKeystroke` */ - processPostKeystroke(outputTarget: OutputTargetBase, keystroke: KeyEvent): ProcessorAction { + processPostKeystroke(textStore: TextStore, keystroke: KeyEvent): ProcessorAction { if(!this.activeKeyboard) { throw "No active keyboard for keystroke processing!"; } - return this.process(this.activeKeyboard.processPostKeystroke.bind(this.activeKeyboard), outputTarget, keystroke, true); + return this.process(this.activeKeyboard.processPostKeystroke.bind(this.activeKeyboard), textStore, keystroke, true); } /** * Function processKeystroke * Scope Private - * @param {Object} outputTarget The target receiving input + * @param {Object} textStore The target receiving input * @param {Object} keystroke The input keystroke (with its properties) to be mapped by the keyboard. * Description Encapsulates calls to keyboard input processing. * @returns {ProcessorAction} Record of commands and state changes that result from executing `begin Unicode` */ - processKeystroke(outputTarget: OutputTargetBase, keystroke: KeyEvent): ProcessorAction { + processKeystroke(textStore: TextStore, keystroke: KeyEvent): ProcessorAction { if(!this.activeKeyboard) { throw "No active keyboard for keystroke processing!"; } - return this.process(this.activeKeyboard.process.bind(this.activeKeyboard), outputTarget, keystroke, false); + return this.process(this.activeKeyboard.process.bind(this.activeKeyboard), textStore, keystroke, false); } - private process(callee: (outputTarget: OutputTargetBase, keystroke: KeyEvent) => boolean, outputTarget: OutputTargetBase, keystroke: KeyEvent, readonly: boolean): ProcessorAction { + private process(callee: (textStore: TextStore, keystroke: KeyEvent) => boolean, textStore: TextStore, keystroke: KeyEvent, readonly: boolean): ProcessorAction { // Clear internal state tracking data from prior keystrokes. - if(!outputTarget) { + if(!textStore) { throw "No target specified for keyboard output!"; } else if(!this.activeKeyboard) { throw "No active keyboard for keystroke processing!"; @@ -1042,13 +1042,13 @@ export class JSKeyboardInterface extends KeyboardHarness { throw "No callee for keystroke processing!"; } - outputTarget.invalidateSelection(); + textStore.invalidateSelection(); - outputTarget.deadkeys().resetMatched(); // I3318 + textStore.deadkeys().resetMatched(); // I3318 this.resetContextCache(); - // Capture the initial state of the OutputTarget before any rules are matched. - const preInput = Mock.from(outputTarget, true); + // Capture the initial state of the TextStore before any rules are matched. + const preInput = Mock.from(textStore, true); // Capture the initial state of any variable stores const cachedVariableStores = this.activeKeyboard.variableStores; @@ -1061,12 +1061,12 @@ export class JSKeyboardInterface extends KeyboardHarness { this.activeDevice = keystroke.device; // Calls the start-group of the active keyboard. - this.activeTargetOutput = outputTarget; - const matched = callee(outputTarget, keystroke); + this.activeTargetOutput = textStore; + const matched = callee(textStore, keystroke); this.activeTargetOutput = null; // Finalize the rule's results. - this.ruleBehavior.transcription = outputTarget.buildTranscriptionFrom(preInput, keystroke, readonly); + this.ruleBehavior.transcription = textStore.buildTranscriptionFrom(preInput, keystroke, readonly); // We always backup the changes to variable stores to the ProcessorAction, to // be applied during finalization, then restore them to the cached initial diff --git a/web/src/engine/js-processor/src/jsKeyboardProcessor.ts b/web/src/engine/js-processor/src/jsKeyboardProcessor.ts index 52510cda65..14f720b3ce 100644 --- a/web/src/engine/js-processor/src/jsKeyboardProcessor.ts +++ b/web/src/engine/js-processor/src/jsKeyboardProcessor.ts @@ -11,7 +11,7 @@ import { ModifierKeyConstants } from '@keymanapp/common-types'; import { Codes, type JSKeyboard, MinimalKeymanGlobal, KeyEvent, Layouts, DefaultRules, EmulationKeystrokes, type MutableSystemStore, - OutputTargetBase, ProcessorAction, SystemStoreIDs, Mock + TextStore, ProcessorAction, SystemStoreIDs, Mock } from "keyman/engine/keyboard"; import { JSKeyboardInterface } from './jsKeyboardInterface.js'; import { DeviceSpec, globalObject, KMWString } from "@keymanapp/web-utils"; @@ -20,7 +20,7 @@ import { DeviceSpec, globalObject, KMWString } from "@keymanapp/web-utils"; // Also relies on @keymanapp/web-utils, which is included via tsconfig.json. -export type BeepHandler = (outputTarget: OutputTargetBase) => void; +export type BeepHandler = (textStore: TextStore) => void; export type LogMessageHandler = (str: string) => void; export interface ProcessorInitOptions { @@ -122,18 +122,18 @@ export class JSKeyboardProcessor extends EventEmitter { * where and when appropriate. * * @param {object} Lkc The pre-analyzed KeyEvent object - * @param {OutputTargetBase} outputTarget The output target receiving the KeyEvent + * @param {TextStore} textStore The output target receiving the KeyEvent * @param {boolean} readonly True if the target is read-only * @return {string} */ - private defaultRuleBehavior(Lkc: KeyEvent, outputTarget: OutputTargetBase, readonly: boolean): ProcessorAction { - const preInput = Mock.from(outputTarget, readonly); + private defaultRuleBehavior(Lkc: KeyEvent, textStore: TextStore, readonly: boolean): ProcessorAction { + const preInput = Mock.from(textStore, readonly); const ruleBehavior = new ProcessorAction(); let matched = false; let char = ''; let special: EmulationKeystrokes; - if(Lkc.isSynthetic || outputTarget.isSynthetic) { + if(Lkc.isSynthetic || textStore.isSynthetic) { matched = true; // All the conditions below result in matches until the final else, which restores the expected default // if no match occurs. @@ -146,10 +146,10 @@ export class JSKeyboardProcessor extends EventEmitter { } else if((special = this.defaultRules.forSpecialEmulation(Lkc)) != null) { switch(special) { case EmulationKeystrokes.Backspace: - this.keyboardInterface.defaultBackspace(outputTarget); + this.keyboardInterface.defaultBackspace(textStore); break; case EmulationKeystrokes.Enter: - outputTarget.handleNewlineAtCaret(); + textStore.handleNewlineAtCaret(); break; // case '\u007f': // K_DEL // // For (possible) future implementation. @@ -171,13 +171,13 @@ export class JSKeyboardProcessor extends EventEmitter { special = this.defaultRules.forSpecialEmulation(Lkc) if(special == EmulationKeystrokes.Backspace) { // A browser's default backspace may fail to delete both parts of an SMP character. - this.keyboardInterface.defaultBackspace(outputTarget); + this.keyboardInterface.defaultBackspace(textStore); } else if(special || this.defaultRules.isCommand(Lkc)) { // Filters out 'commands' like TAB. // We only do the "for special emulation" cases under the condition above... aside from backspace // Let the browser handle those. return null; } else { - this.keyboardInterface.output(0, outputTarget, char); + this.keyboardInterface.output(0, textStore, char); } } else { // No match, no default ProcessorAction. @@ -190,33 +190,33 @@ export class JSKeyboardProcessor extends EventEmitter { return ruleBehavior; } - const transcription = outputTarget.buildTranscriptionFrom(preInput, Lkc, readonly); + const transcription = textStore.buildTranscriptionFrom(preInput, Lkc, readonly); ruleBehavior.transcription = transcription; return ruleBehavior; } - private processNewContextEvent(device: DeviceSpec, outputTarget: OutputTargetBase): ProcessorAction { + private processNewContextEvent(device: DeviceSpec, textStore: TextStore): ProcessorAction { return this.activeKeyboard ? - this.keyboardInterface.processNewContextEvent(outputTarget, this.activeKeyboard.constructNullKeyEvent(device, this.stateKeys)) : + this.keyboardInterface.processNewContextEvent(textStore, this.activeKeyboard.constructNullKeyEvent(device, this.stateKeys)) : null; } - public processPostKeystroke(device: DeviceSpec, outputTarget: OutputTargetBase): ProcessorAction { + public processPostKeystroke(device: DeviceSpec, textStore: TextStore): ProcessorAction { return this.activeKeyboard ? - this.keyboardInterface.processPostKeystroke(outputTarget, this.activeKeyboard.constructNullKeyEvent(device, this.stateKeys)) : + this.keyboardInterface.processPostKeystroke(textStore, this.activeKeyboard.constructNullKeyEvent(device, this.stateKeys)) : null; } - public processKeystroke(keyEvent: KeyEvent, outputTarget: OutputTargetBase): ProcessorAction { + public processKeystroke(keyEvent: KeyEvent, textStore: TextStore): ProcessorAction { let matchBehavior: ProcessorAction; // Before keyboard rules apply, check if the left-context is empty. - const nothingDeletable = KMWString.length(outputTarget.getTextBeforeCaret()) == 0 && outputTarget.isSelectionEmpty(); + const nothingDeletable = KMWString.length(textStore.getTextBeforeCaret()) == 0 && textStore.isSelectionEmpty(); // Pass this key code and state to the keyboard program if(this.activeKeyboard && keyEvent.Lcode != 0) { - matchBehavior = this.keyboardInterface.processKeystroke(outputTarget, keyEvent); + matchBehavior = this.keyboardInterface.processKeystroke(textStore, keyEvent); } // Final conditional component - if someone actually makes a keyboard rule that blocks output @@ -226,7 +226,7 @@ export class JSKeyboardProcessor extends EventEmitter { // behavior in cases where such rules actually would appear. (Though, _that_ should be caught // in the keyboard-review process and heavily discouraged, so... yeah.) if(nothingDeletable && keyEvent.Lcode == Codes.keyCodes.K_BKSP && matchBehavior.triggerKeyDefault) { - matchBehavior = this.defaultRuleBehavior(keyEvent, outputTarget, false); + matchBehavior = this.defaultRuleBehavior(keyEvent, textStore, false); matchBehavior.triggerKeyDefault = true; // Force a single `deleteLeft`. // @ts-ignore // force value override, because deleteLeft is marked readonly. @@ -238,11 +238,11 @@ export class JSKeyboardProcessor extends EventEmitter { // Handle unmapped keys, including special keys // The following is physical layout dependent, so should be avoided if possible. All keys should be mapped. - this.keyboardInterface.activeTargetOutput = outputTarget; + this.keyboardInterface.activeTargetOutput = textStore; // Match against the 'default keyboard' - rules to mimic the default string output when typing in a browser. // Many keyboards rely upon these 'implied rules'. - const defaultBehavior = this.defaultRuleBehavior(keyEvent, outputTarget, false); + const defaultBehavior = this.defaultRuleBehavior(keyEvent, textStore, false); if(defaultBehavior) { if(!matchBehavior) { matchBehavior = defaultBehavior; @@ -537,13 +537,13 @@ export class JSKeyboardProcessor extends EventEmitter { // Returns true if the key event is a modifier press, allowing keyPress to return selectively // in those cases. - public doModifierPress(Levent: KeyEvent, outputTarget: OutputTargetBase, isKeyDown: boolean): boolean { + public doModifierPress(Levent: KeyEvent, textStore: TextStore, isKeyDown: boolean): boolean { if(!this.activeKeyboard) { return false; } if(Levent.isModifier) { - this.activeKeyboard.notify(Levent.Lcode, outputTarget, isKeyDown ? 1 : 0); + this.activeKeyboard.notify(Levent.Lcode, textStore, isKeyDown ? 1 : 0); // For eventual integration - we bypass an OSK update for physical keystrokes when in touch mode. if(!Levent.device.touchable) { return this._UpdateVKShift(Levent); // I2187 @@ -553,7 +553,7 @@ export class JSKeyboardProcessor extends EventEmitter { } if(Levent.LmodifierChange) { - this.activeKeyboard.notify(0, outputTarget, 1); + this.activeKeyboard.notify(0, textStore, 1); if(!Levent.device.touchable) { this._UpdateVKShift(Levent); } @@ -567,20 +567,20 @@ export class JSKeyboardProcessor extends EventEmitter { * Tell the currently active keyboard that a new context has been selected, * e.g. by focus change, selection change, keyboard change, etc. * - * @param {Object} outputTarget The OutputTarget that has focus + * @param {Object} textStore The TextStore that has focus * @returns {Object} A ProcessorAction object describing the cumulative effects of * all matched keyboard rules */ - private performNewContextEvent(outputTarget: OutputTargetBase): ProcessorAction { - const ruleBehavior = this.processNewContextEvent(this.contextDevice, outputTarget); + private performNewContextEvent(textStore: TextStore): ProcessorAction { + const ruleBehavior = this.processNewContextEvent(this.contextDevice, textStore); if (ruleBehavior) { - this.finalizeProcessorAction(ruleBehavior, outputTarget); + this.finalizeProcessorAction(ruleBehavior, textStore); } return ruleBehavior; } - public resetContext(target?: OutputTargetBase) { + public resetContext(target?: TextStore) { this.layerId = 'default'; // Make sure all deadkeys for the context get cleared properly. @@ -608,13 +608,13 @@ export class JSKeyboardProcessor extends EventEmitter { } }; - public finalizeProcessorAction(data: ProcessorAction, outputTarget: OutputTargetBase): void { + public finalizeProcessorAction(data: ProcessorAction, textStore: TextStore): void { if (!data.transcription) { throw "Cannot finalize a ProcessorAction with no transcription."; } if (this.beepHandler && data.beep) { - this.beepHandler(outputTarget); + this.beepHandler(textStore); } for (const storeID in data.setStore) { @@ -642,7 +642,7 @@ export class JSKeyboardProcessor extends EventEmitter { if (data.triggersDefaultCommand) { const keyEvent = data.transcription.keystroke; - this.defaultRules.applyCommand(keyEvent, outputTarget); + this.defaultRules.applyCommand(keyEvent, textStore); } if (this.warningLogger && data.warningLog) { diff --git a/web/src/engine/keyboard/src/defaultRules.ts b/web/src/engine/keyboard/src/defaultRules.ts index 1c32056e14..d39cdbe34f 100644 --- a/web/src/engine/keyboard/src/defaultRules.ts +++ b/web/src/engine/keyboard/src/defaultRules.ts @@ -7,7 +7,7 @@ import { ModifierKeyConstants } from '@keymanapp/common-types'; import { Codes } from './codes.js'; import { type KeyEvent } from './keyEvent.js'; -import { type OutputTargetBase } from './outputTargetBase.js'; +import { type TextStore } from './textStore.js'; export enum EmulationKeystrokes { Enter = '\n', @@ -78,12 +78,12 @@ export default class DefaultRules { /** * Used when a ProcessorAction represents a non-text "command" within the Engine. This will generally - * trigger events that require context reset - often by moving the caret or by moving what OutputTarget + * trigger events that require context reset - often by moving the caret or by moving what TextStore * the caret is in. However, we let those events perform the actual context reset. * * Note: is extended by DOM-aware KeymanWeb code. */ - public applyCommand(Lkc: KeyEvent, outputTarget: OutputTargetBase): void { + public applyCommand(Lkc: KeyEvent, textStore: TextStore): void { // Notes for potential default-handling extensions: // // switch(code) { @@ -112,7 +112,7 @@ export default class DefaultRules { /** * Codes matched here generally have default implementations when in a browser but require emulation - * for 'synthetic' `OutputTarget`s like `Mock`s, which have no default text handling. + * for 'synthetic' `TextStore`s like `Mock`s, which have no default text handling. */ public forSpecialEmulation(Lkc: KeyEvent): EmulationKeystrokes { let code = this.codeForEvent(Lkc); diff --git a/web/src/engine/keyboard/src/index.ts b/web/src/engine/keyboard/src/index.ts index eaf175c31b..dad2d5f4c5 100644 --- a/web/src/engine/keyboard/src/index.ts +++ b/web/src/engine/keyboard/src/index.ts @@ -33,7 +33,7 @@ export { type SystemStoreMutationHandler, MutableSystemStore, SystemStore, Syste export { type VariableStore, VariableStoreSerializer, VariableStoreDictionary } from "./variableStore.js"; export { Mock } from "./mock.js"; -export { OutputTargetBase } from "./outputTargetBase.js"; +export { TextStore } from "./textStore.js"; export { findCommonSubstringEndIndex } from "./stringDivergence.js"; export { Deadkey } from "./deadkeys.js"; @@ -41,6 +41,6 @@ export * from "@keymanapp/web-utils"; // At the top level, there should be no default export. -// Without the line below... OutputTarget would likely be aliased there, as it's +// Without the line below... TextStore would likely be aliased there, as it's // the last `export { default as _ }` => `export * from` pairing seen above. export default undefined; diff --git a/web/src/engine/keyboard/src/keyboards/jsKeyboard.ts b/web/src/engine/keyboard/src/keyboards/jsKeyboard.ts index 22400dc01b..8305e3ec10 100644 --- a/web/src/engine/keyboard/src/keyboards/jsKeyboard.ts +++ b/web/src/engine/keyboard/src/keyboards/jsKeyboard.ts @@ -2,7 +2,7 @@ import { Codes } from "../codes.js"; import { Layouts } from "./defaultLayouts.js"; import { ActiveKey, ActiveLayout, ActiveSubKey } from "./activeLayout.js"; import { KeyEvent } from "../keyEvent.js"; -import { type OutputTargetBase } from "../outputTargetBase.js"; +import { type TextStore } from "../textStore.js"; import { KeymanWebKeyboard, ModifierKeyConstants, TouchLayout } from "@keymanapp/common-types"; import { VariableStoreDictionary } from "../variableStore.js"; @@ -50,7 +50,7 @@ type KmwKeyboardObject = KeyboardObject & { */ export class JSKeyboard { public static DEFAULT_SCRIPT_OBJECT: KmwKeyboardObject = { - 'gs': function(outputTarget: OutputTargetBase, keystroke: KeyEvent) { return false; }, // no matching rules; rely on defaultRuleOutput entirely + 'gs': function(textStore: TextStore, keystroke: KeyEvent) { return false; }, // no matching rules; rely on defaultRuleOutput entirely 'KI': '', // The currently-existing default keyboard ID; we already have checks that focus against this. 'KN': '', 'KV': Layouts.DEFAULT_RAW_SPEC, @@ -78,22 +78,22 @@ export class JSKeyboard { /** * Calls the keyboard's `gs` function, which represents the keyboard source's begin Unicode group. */ - process(outputTarget: OutputTargetBase, keystroke: KeyEvent): boolean { - return this.scriptObject['gs'](outputTarget, keystroke); + process(textStore: TextStore, keystroke: KeyEvent): boolean { + return this.scriptObject['gs'](textStore, keystroke); } /** * Calls the keyboard's `gn` function, which represents the keyboard source's begin newContext group. */ - processNewContextEvent(outputTarget: OutputTargetBase, keystroke: KeyEvent): boolean { - return this.scriptObject['gn'] ? this.scriptObject['gn'](outputTarget, keystroke) : false; + processNewContextEvent(textStore: TextStore, keystroke: KeyEvent): boolean { + return this.scriptObject['gn'] ? this.scriptObject['gn'](textStore, keystroke) : false; } /** * Calls the keyboard's `gpk` function, which represents the keyboard source's begin postKeystroke group. */ - processPostKeystroke(outputTarget: OutputTargetBase, keystroke: KeyEvent): boolean { - return this.scriptObject['gpk'] ? this.scriptObject['gpk'](outputTarget, keystroke) : false; + processPostKeystroke(textStore: TextStore, keystroke: KeyEvent): boolean { + return this.scriptObject['gpk'] ? this.scriptObject['gpk'](textStore, keystroke) : false; } get isHollow(): boolean { @@ -359,7 +359,7 @@ export class JSKeyboard { * @param {number} _PData 1 or 0 * Notifies keyboard of keystroke or other event */ - notify(_PCommand: number, _PTarget: OutputTargetBase, _PData: number) { // I2187 + notify(_PCommand: number, _PTarget: TextStore, _PData: number) { // I2187 // Good example use case - the Japanese CJK-picker keyboard if(typeof(this.scriptObject['KNS']) == 'function') { this.scriptObject['KNS'](_PCommand, _PTarget, _PData); diff --git a/web/src/engine/keyboard/src/keyboards/transcription.ts b/web/src/engine/keyboard/src/keyboards/transcription.ts index a0c8b759a2..7623a2f0d1 100644 --- a/web/src/engine/keyboard/src/keyboards/transcription.ts +++ b/web/src/engine/keyboard/src/keyboards/transcription.ts @@ -1,7 +1,7 @@ /* * Keyman is copyright (C) SIL Global. MIT License. */ -import { OutputTargetBase } from '../outputTargetBase.js'; +import { TextStore } from '../textStore.js'; import { KeyEvent } from '../keyEvent.js'; import { Alternate, TextTransform } from './textTransform.js'; @@ -10,11 +10,11 @@ export class Transcription { readonly keystroke: KeyEvent; readonly transform: TextTransform; alternates: Alternate[]; // constructed after the rest of the transcription. - readonly preInput: OutputTargetBase; + readonly preInput: TextStore; private static tokenSeed: number = 0; - constructor(keystroke: KeyEvent, transform: TextTransform, preInput: OutputTargetBase, alternates?: Alternate[]) { + constructor(keystroke: KeyEvent, transform: TextTransform, preInput: TextStore, alternates?: Alternate[]) { const token = this.token = Transcription.tokenSeed++; this.keystroke = keystroke; diff --git a/web/src/engine/keyboard/src/mock.ts b/web/src/engine/keyboard/src/mock.ts index 8d042dc071..b426815c51 100644 --- a/web/src/engine/keyboard/src/mock.ts +++ b/web/src/engine/keyboard/src/mock.ts @@ -1,7 +1,7 @@ -import { OutputTargetBase } from './outputTargetBase.js'; +import { TextStore } from './textStore.js'; import { KMWString } from '@keymanapp/web-utils'; -export class Mock extends OutputTargetBase { +export class Mock extends TextStore { text: string; selStart: number; @@ -25,34 +25,34 @@ export class Mock extends OutputTargetBase { this.selForward = this.selEnd >= this.selStart; } - static assertIsOutputTargetBase(outputTarget: OutputTargetBase): asserts outputTarget is OutputTargetBase { - if (!(outputTarget instanceof OutputTargetBase)) { - throw new TypeError("outputTarget is not a OutputTargetBase"); + static assertIsOutputTargetBase(textStore: TextStore): asserts textStore is TextStore { + if (!(textStore instanceof TextStore)) { + throw new TypeError("textStore is not a TextStore"); } } // Clones the state of an existing EditableElement, creating a Mock version of its state. - static from(outputTarget: OutputTargetBase, readonly?: boolean): Mock { + static from(textStore: TextStore, readonly?: boolean): Mock { let clone: Mock; - this.assertIsOutputTargetBase(outputTarget); + this.assertIsOutputTargetBase(textStore); - if (outputTarget instanceof Mock) { + if (textStore instanceof Mock) { // Avoids the need to run expensive kmwstring.ts `length()` // calculations when deep-copying Mock instances. - const priorMock = outputTarget as Mock; + const priorMock = textStore as Mock; clone = new Mock(priorMock.text, priorMock.selStart, priorMock.selEnd); } else { - const text = outputTarget.getText(); + const text = textStore.getText(); const textLen = KMWString.length(text); // If !hasSelection() let selectionStart: number = textLen; let selectionEnd: number = 0; - if (outputTarget.hasSelection()) { - const beforeText = outputTarget.getTextBeforeCaret(); - const afterText = outputTarget.getTextAfterCaret(); + if (textStore.hasSelection()) { + const beforeText = textStore.getTextBeforeCaret(); + const afterText = textStore.getTextAfterCaret(); selectionStart = KMWString.length(beforeText); selectionEnd = textLen - KMWString.length(afterText); } @@ -64,7 +64,7 @@ export class Mock extends OutputTargetBase { } // Also duplicate deadkey state! (Needed for fat-finger ops.) - clone.setDeadkeys((outputTarget as OutputTargetBase).deadkeys()); + clone.setDeadkeys((textStore as TextStore).deadkeys()); return clone; } diff --git a/web/src/engine/keyboard/src/outputTargetBase.ts b/web/src/engine/keyboard/src/textStore.ts similarity index 92% rename from web/src/engine/keyboard/src/outputTargetBase.ts rename to web/src/engine/keyboard/src/textStore.ts index cf82e10702..1e7889f918 100644 --- a/web/src/engine/keyboard/src/outputTargetBase.ts +++ b/web/src/engine/keyboard/src/textStore.ts @@ -9,7 +9,7 @@ import { type KeyEvent } from 'keyman/engine/keyboard'; import { Deadkey, DeadkeyTracker } from "./deadkeys.js"; import { LexicalModelTypes } from '@keymanapp/common-types'; -export abstract class OutputTargetBase { +export abstract class TextStore { private _dks: DeadkeyTracker; constructor() { @@ -17,7 +17,7 @@ export abstract class OutputTargetBase { } /** - * Signifies that this OutputTarget has no default key processing behaviors. This should be false + * Signifies that this TextStore has no default key processing behaviors. This should be false * for OutputTargets backed by web elements like HTMLInputElement or HTMLTextAreaElement. */ get isSynthetic(): boolean { @@ -60,14 +60,14 @@ export abstract class OutputTargetBase { } /** - * Determines the basic operations needed to reconstruct the current OutputTarget's text from the prior state specified - * by another OutputTarget based on their text and caret positions. + * Determines the basic operations needed to reconstruct the current TextStore's text from the prior state specified + * by another TextStore based on their text and caret positions. * * This is designed for use as a "before and after" comparison to determine the effect of a single keyboard rule at a time. * As such, it assumes that the caret is immediately after any inserted text. * @param from An output target (preferably a Mock) representing the prior state of the input/output system. */ - buildTransformFrom(original: OutputTargetBase): TextTransform { + buildTransformFrom(original: TextStore): TextTransform { const toLeft = this.getTextBeforeCaret(); const fromLeft = original.getTextBeforeCaret(); @@ -88,7 +88,7 @@ export abstract class OutputTargetBase { return new TextTransform(insertedText, deletedLeft, deletedRight, original.getSelectedText() && !this.getSelectedText()); } - buildTranscriptionFrom(original: OutputTargetBase, keyEvent: KeyEvent, readonly: boolean, alternates?: Alternate[]): Transcription { + buildTranscriptionFrom(original: TextStore, keyEvent: KeyEvent, readonly: boolean, alternates?: Alternate[]): Transcription { const transform = this.buildTransformFrom(original); // If we ever decide to re-add deadkey tracking, this is the place for it. @@ -97,10 +97,10 @@ export abstract class OutputTargetBase { } /** - * Restores the `OutputTarget` to the indicated state. Designed for use with `Transcription.preInput`. - * @param original An `OutputTarget` (usually a `Mock`). + * Restores the `TextStore` to the indicated state. Designed for use with `Transcription.preInput`. + * @param original An `TextStore` (usually a `Mock`). */ - restoreTo(original: OutputTargetBase) { + restoreTo(original: TextStore) { this.clearSelection(); // We currently do not restore selected text; the mechanism isn't supported at present for // all output target types - especially in regard to re-selecting the text if restored. @@ -140,7 +140,7 @@ export abstract class OutputTargetBase { /** * Helper to `restoreTo` - allows directly setting the 'before' context to that of another - * `OutputTarget`. + * `TextStore`. * @param s */ protected setTextBeforeCaret(s: string): void { @@ -151,7 +151,7 @@ export abstract class OutputTargetBase { /** * Helper to `restoreTo` - allows directly setting the 'after' context to that of another - * `OutputTarget`. + * `TextStore`. * @param s */ protected abstract setTextAfterCaret(s: string): void; diff --git a/web/src/engine/main/src/contextManagerBase.ts b/web/src/engine/main/src/contextManagerBase.ts index 979fcfc7ed..58da68f34b 100644 --- a/web/src/engine/main/src/contextManagerBase.ts +++ b/web/src/engine/main/src/contextManagerBase.ts @@ -1,5 +1,5 @@ import { EventEmitter } from 'eventemitter3'; -import { ManagedPromise, type Keyboard, type OutputTargetBase } from 'keyman/engine/keyboard'; +import { ManagedPromise, type Keyboard, type TextStore } from 'keyman/engine/keyboard'; import { type JSKeyboardInterface } from 'keyman/engine/js-processor'; import { StubAndKeyboardCache, type KeyboardStub } from 'keyman/engine/keyboard-storage'; import { PredictionContext } from 'keyman/engine/interfaces'; @@ -7,7 +7,7 @@ import { EngineConfiguration } from './engineConfiguration.js'; interface EventMap { // target, then keyboard. - 'targetchange': (target: OutputTargetBase) => boolean; + 'targetchange': (target: TextStore) => boolean; /** * This event is raised whenever a keyboard change is requested. @@ -44,11 +44,11 @@ export interface ContextManagerConfiguration { /** * A function that resets any state-dependent keyboard key-state information such as * emulated modifier state and layer id. Also purges the context cache. - * If an `outputTarget` is specified, it will also trigger new-context rule processing. + * If an `textStore` is specified, it will also trigger new-context rule processing. * * Does not reset option-stores, variable-stores, etc. */ - readonly resetContext: (outputTarget?: OutputTargetBase) => void; + readonly resetContext: (textStore?: TextStore) => void; /** * A predictive-state management object that interfaces the predictive-text banner @@ -64,7 +64,7 @@ export interface ContextManagerConfiguration { } interface PendingActivation { - target: OutputTargetBase, + target: TextStore, keyboard: Promise, stub: KeyboardStub; } @@ -74,11 +74,11 @@ export abstract class ContextManagerBase abstract initialize(): void; - abstract get activeTarget(): OutputTargetBase; + abstract get activeTarget(): TextStore; private _predictionContext: PredictionContext; protected keyboardCache: StubAndKeyboardCache; - private _resetContext: (outputTarget?: OutputTargetBase) => void; + private _resetContext: (textStore?: TextStore) => void; private pendingActivations: PendingActivation[] = []; protected engineConfig: MainConfig; @@ -101,18 +101,18 @@ export abstract class ContextManagerBase insertText(kbdInterface: JSKeyboardInterface, Ptext: string, PdeadKey: number) { // Find the correct output target to manipulate. - const outputTarget = this.activeTarget; + const textStore = this.activeTarget; - if(outputTarget != null) { + if(textStore != null) { if(Ptext != null) { - kbdInterface.output(0, outputTarget, Ptext); + kbdInterface.output(0, textStore, Ptext); } if((typeof(PdeadKey)!=='undefined') && (PdeadKey !== null)) { - kbdInterface.deadkeyOutput(0, outputTarget, PdeadKey); + kbdInterface.deadkeyOutput(0, textStore, PdeadKey); } - outputTarget.invalidateSelection(); + textStore.invalidateSelection(); return true; } @@ -135,7 +135,7 @@ export abstract class ContextManagerBase * attached elements within the app/browser target. For `app/webview`, this should * always return a consistent value - likely, `null`. */ - protected abstract currentKeyboardSrcTarget(): OutputTargetBase; + protected abstract currentKeyboardSrcTarget(): TextStore; /** * Ensures that newly activated keyboards are set correctly within managed context, possibly @@ -143,16 +143,16 @@ export abstract class ContextManagerBase * @param kbd * @param target */ - protected abstract activateKeyboardForTarget(kbd: { keyboard: Keyboard, metadata: KeyboardStub }, target: OutputTargetBase): void; + protected abstract activateKeyboardForTarget(kbd: { keyboard: Keyboard, metadata: KeyboardStub }, target: TextStore): void; /** * Checks the pending keyboard-activation array for an entry corresponding to the specified - * OutputTarget. If found, also removes the entry for bookkeeping purposes. - * @param target The specific OutputTarget affected by the pending Keyboard activation. + * TextStore. If found, also removes the entry for bookkeeping purposes. + * @param target The specific TextStore affected by the pending Keyboard activation. * May be `null`, which corresponds to the global default Keyboard. * @returns `true` if pending activation is still valid, `false` otherwise. */ - private findAndPopActivation(target: OutputTargetBase): PendingActivation { + private findAndPopActivation(target: TextStore): PendingActivation { // Array.findIndex requires Chrome 45+. :( let activationIndex; for(activationIndex = 0; activationIndex < this.pendingActivations.length; activationIndex++) { @@ -180,7 +180,7 @@ export abstract class ContextManagerBase protected async deferredKeyboardActivation( kbdPromise: Promise, metadata: KeyboardStub, - target: OutputTargetBase + target: TextStore ): Promise { const activation: PendingActivation = { target: target, diff --git a/web/src/engine/main/src/engineConfiguration.ts b/web/src/engine/main/src/engineConfiguration.ts index 4c172aa00a..9ade2643a1 100644 --- a/web/src/engine/main/src/engineConfiguration.ts +++ b/web/src/engine/main/src/engineConfiguration.ts @@ -1,7 +1,7 @@ import { EventEmitter } from "eventemitter3"; import { - DeviceSpec, KeyboardProperties, ManagedPromise, OutputTargetBase, + DeviceSpec, KeyboardProperties, ManagedPromise, TextStore, ProcessorAction, physicalKeyDeviceAlias, SpacebarText } from "keyman/engine/keyboard"; import { PathConfiguration, PathOptionDefaults, PathOptionSpec } from "keyman/engine/interfaces"; @@ -110,9 +110,9 @@ export class EngineConfiguration extends EventEmitter { * after postKeystroke takes effect. Any behaviors defined here should be considered 'readonly' in * terms of context and should instead facilitate integration with the engine's host platform. * @param ruleBehavior The full effects of keystroke + postkeystroke rules from a processed keystroke. - * @param outputTarget The engine's current source for context + * @param textStore The engine's current source for context */ - onRuleFinalization(ruleBehavior: ProcessorAction, outputTarget: OutputTargetBase) {}; + onRuleFinalization(ruleBehavior: ProcessorAction, textStore: TextStore) {}; } export interface InitOptionSpec extends PathOptionSpec { diff --git a/web/src/engine/main/src/headless/inputProcessor.ts b/web/src/engine/main/src/headless/inputProcessor.ts index 76c90f919e..0518d989aa 100644 --- a/web/src/engine/main/src/headless/inputProcessor.ts +++ b/web/src/engine/main/src/headless/inputProcessor.ts @@ -12,7 +12,7 @@ import { JSKeyboard, KeyboardMinimalInterface, Mock, - OutputTargetBase, + TextStore, ProcessorAction, SystemStoreIDs, type Alternate, @@ -97,11 +97,11 @@ export class InputProcessor { * Handles default output and keyboard processing for both OSK and physical keystrokes. * * @param {Object} keyEvent The abstracted KeyEvent to use for keystroke processing - * @param {Object} outputTarget The OutputTarget receiving the KeyEvent + * @param {Object} textStore The TextStore receiving the KeyEvent * @returns {Object} A ProcessorAction object describing the cumulative effects of * all matched keyboard rules. */ - processKeyEvent(keyEvent: KeyEvent, outputTarget: OutputTargetBase): ProcessorAction { + processKeyEvent(keyEvent: KeyEvent, textStore: TextStore): ProcessorAction { const kbdMismatch = keyEvent.srcKeyboard && this.activeKeyboard != keyEvent.srcKeyboard; const trueActiveKeyboard = this.activeKeyboard; @@ -120,10 +120,10 @@ export class InputProcessor { // to revert it. If not, we assume it's a layer-change multitap, in which case // no such reset is needed. // TODO-web-core - if(!isEmptyTransform(transcription.transform) || !(transcription.preInput as Mock).isEqual(Mock.from(outputTarget))) { + if(!isEmptyTransform(transcription.transform) || !(transcription.preInput as Mock).isEqual(Mock.from(textStore))) { // Restores full context, including deadkeys in their exact pre-keystroke state. // TODO-web-core - (outputTarget as OutputTargetBase).restoreTo(transcription.preInput as Mock); + (textStore as TextStore).restoreTo(transcription.preInput as Mock); } /* else: @@ -139,7 +139,7 @@ export class InputProcessor { } } - return this._processKeyEvent(keyEvent, outputTarget); + return this._processKeyEvent(keyEvent, textStore); } finally { if(kbdMismatch) { // Restore our "current" activeKeyboard to its setting before the mismatching KeyEvent. @@ -152,10 +152,10 @@ export class InputProcessor { * Acts as the core of `processKeyEvent` once we're comfortable asserting that the incoming * keystroke matches the current `activeKeyboard`. * @param keyEvent - * @param outputTarget + * @param textStore * @returns */ - private _processKeyEvent(keyEvent: KeyEvent, outputTarget: OutputTargetBase): ProcessorAction { + private _processKeyEvent(keyEvent: KeyEvent, textStore: TextStore): ProcessorAction { const formFactor = keyEvent.device.formFactor; const fromOSK = keyEvent.isSynthetic; @@ -172,7 +172,7 @@ export class InputProcessor { // Will handle keystroke-based non-layer change modifier & state keys, mapping them through the physical keyboard's version // of state management. `doModifierPress` must always run. // TODO-web-core - if (this.keyboardProcessor.doModifierPress(keyEvent, outputTarget as OutputTargetBase, !fromOSK)) { + if (this.keyboardProcessor.doModifierPress(keyEvent, textStore as TextStore, !fromOSK)) { // If run on a desktop platform, we know that modifier & state key presses may not // produce output, so we may make an immediate return safely. if(!fromOSK) { @@ -197,17 +197,17 @@ export class InputProcessor { // // ...end I3363 (Build 301) - // Create a "mock" backup of the current outputTarget in its pre-input state. + // Create a "mock" backup of the current textStore in its pre-input state. // Current, long-existing assumption - it's DOM-backed. // TODO-web-core - const preInputMock = Mock.from(outputTarget as OutputTargetBase, true); + const preInputMock = Mock.from(textStore as TextStore, true); const startingLayerId = this.keyboardProcessor.layerId; // We presently need the true keystroke to run on the FULL context. That index is still // needed for some indexing operations when comparing two different output targets. // TODO-web-core - let ruleBehavior = this.keyboardProcessor.processKeystroke(keyEvent, outputTarget as OutputTargetBase); + let ruleBehavior = this.keyboardProcessor.processKeystroke(keyEvent, textStore as TextStore); // Swap layer as appropriate. if(keyEvent.kNextLayer) { @@ -243,7 +243,7 @@ export class InputProcessor { // Now that we've done all the keystroke processing needed, ensure any extra effects triggered // by the actual keystroke occur. - this.keyboardProcessor.finalizeProcessorAction(ruleBehavior, outputTarget); + this.keyboardProcessor.finalizeProcessorAction(ruleBehavior, textStore); // -- All keystroke (and 'alternate') processing is now complete. Time to finalize everything! -- @@ -255,7 +255,7 @@ export class InputProcessor { // We need a dummy ProcessorAction for keys which have no output (e.g. Shift) ruleBehavior = new ProcessorAction(); // TODO-web-core - ruleBehavior.transcription = (outputTarget as OutputTargetBase).buildTranscriptionFrom(outputTarget as OutputTargetBase, null, false); + ruleBehavior.transcription = (textStore as TextStore).buildTranscriptionFrom(textStore as TextStore, null, false); ruleBehavior.triggersDefaultCommand = true; } @@ -275,9 +275,9 @@ export class InputProcessor { this.keyboardProcessor.oldLayerStore.set(hasLayerChanged ? startingLayerId : ''); // TODO-web-core - const postRuleBehavior = this.keyboardProcessor.processPostKeystroke(this.contextDevice, outputTarget as OutputTargetBase); + const postRuleBehavior = this.keyboardProcessor.processPostKeystroke(this.contextDevice, textStore as TextStore); if (postRuleBehavior) { - this.keyboardProcessor.finalizeProcessorAction(postRuleBehavior, outputTarget); + this.keyboardProcessor.finalizeProcessorAction(postRuleBehavior, textStore); } // Yes, even for ruleBehavior.triggersDefaultCommand. Those tend to change the context. @@ -286,7 +286,7 @@ export class InputProcessor { // Text did not change (thus, no text "input") if we tabbed or merely moved the caret. if(!ruleBehavior.triggersDefaultCommand) { // For DOM-aware targets, this will trigger a DOM event page designers may listen for. - outputTarget.doInputEvent(); + textStore.doInputEvent(); } return keepRuleBehavior ? ruleBehavior : null; @@ -398,11 +398,11 @@ export class InputProcessor { return alternates; } - public resetContext(outputTarget?: OutputTargetBase) { + public resetContext(textStore?: TextStore) { // Also handles new-context events, which may modify the layer // TODO-web-core - this.keyboardProcessor.resetContext(outputTarget as OutputTargetBase); + this.keyboardProcessor.resetContext(textStore as TextStore); // With the layer now set, we trigger new predictions. - this.languageProcessor.invalidateContext(outputTarget, this.keyboardProcessor.layerId); + this.languageProcessor.invalidateContext(textStore, this.keyboardProcessor.layerId); } } \ No newline at end of file diff --git a/web/src/engine/main/src/headless/languageProcessor.ts b/web/src/engine/main/src/headless/languageProcessor.ts index 913562ef72..b3a749ce93 100644 --- a/web/src/engine/main/src/headless/languageProcessor.ts +++ b/web/src/engine/main/src/headless/languageProcessor.ts @@ -1,6 +1,6 @@ import { EventEmitter } from "eventemitter3"; import { LMLayer, WorkerFactory } from "@keymanapp/lexical-model-layer/web"; -import { Transcription, OutputTargetBase, Mock } from 'keyman/engine/keyboard'; +import { Transcription, TextStore, Mock } from 'keyman/engine/keyboard'; import { LanguageProcessorEventMap, ModelSpec, StateChangeEnum, ReadySuggestions } from 'keyman/engine/interfaces'; import ContextWindow from "./contextWindow.js"; import { TranscriptionCache } from "./transcriptionCache.js"; @@ -125,7 +125,7 @@ export class LanguageProcessor extends EventEmitter { }); } - public invalidateContext(outputTarget: OutputTargetBase, layerId: string): Promise { + public invalidateContext(textStore: TextStore, layerId: string): Promise { // If there's no active model, there can be no predictions. // We'll also be missing important data needed to even properly REQUEST the predictions. if(!this.currentModel || !this.configuration) { @@ -141,9 +141,9 @@ export class LanguageProcessor extends EventEmitter { // Signal to any predictive text UI that the context has changed, invalidating recent predictions. this.emit('invalidatesuggestions', 'context'); - if(outputTarget) { + if(textStore) { // TODO-web-core - const transcription = (outputTarget as OutputTargetBase).buildTranscriptionFrom((outputTarget as OutputTargetBase), null, false); + const transcription = (textStore as TextStore).buildTranscriptionFrom((textStore as TextStore), null, false); return this.predict_internal(transcription, true, layerId); } else { // if there's no active context source, there's nothing to @@ -154,13 +154,13 @@ export class LanguageProcessor extends EventEmitter { } } - public wordbreak(target: OutputTargetBase, layerId: string): Promise { + public wordbreak(target: TextStore, layerId: string): Promise { if(!this.isActive) { return null; } // TODO-web-core - const context = new ContextWindow(Mock.from((target as OutputTargetBase), false), this.configuration, layerId); + const context = new ContextWindow(Mock.from((target as TextStore), false), this.configuration, layerId); return this.lmEngine.wordbreak(context); } @@ -185,14 +185,14 @@ export class LanguageProcessor extends EventEmitter { /** * * @param suggestion - * @param outputTarget + * @param textStore * @param getLayerId a function that returns the current layerId, * required because layerid can be changed by PostKeystroke * @returns */ - public applySuggestion(suggestion: Suggestion, outputTarget: OutputTargetBase, getLayerId: ()=>string): Promise { - if(!outputTarget) { - throw "Accepting suggestions requires a destination OutputTargetBase instance." + public applySuggestion(suggestion: Suggestion, textStore: TextStore, getLayerId: ()=>string): Promise { + if(!textStore) { + throw "Accepting suggestions requires a destination TextStore instance." } if(!this.isActive) { @@ -223,13 +223,13 @@ export class LanguageProcessor extends EventEmitter { // In embedded mode, both Android and iOS are best served by calculating this transform and applying its // values as needed for use with their IME interfaces. // TODO-web-core - const transform = final.buildTransformFrom((outputTarget as OutputTargetBase)); + const transform = final.buildTransformFrom((textStore as TextStore)); // TODO-web-core - (outputTarget as OutputTargetBase).apply(transform); + (textStore as TextStore).apply(transform); // Tell the banner that a suggestion was applied, so it can call the // keyboard's PostKeystroke entry point as needed - this.emit('suggestionapplied', outputTarget); + this.emit('suggestionapplied', textStore); // Build a 'reversion' Transcription that can be used to undo this apply() if needed, // replacing the suggestion transform with the original input text. @@ -259,7 +259,7 @@ export class LanguageProcessor extends EventEmitter { // // If using the version from lm-layer: // let mappedReversion = reversion; // mappedReversion.transformId = reversionTranscription.token; - this.predictFromTarget(outputTarget, getLayerId()); + this.predictFromTarget(textStore, getLayerId()); return mappedReversion; }); @@ -267,9 +267,9 @@ export class LanguageProcessor extends EventEmitter { } } - public applyReversion(reversion: Reversion, outputTarget: OutputTargetBase) { - if(!outputTarget) { - throw "Accepting suggestions requires a destination OutputTargetBase instance." + public applyReversion(reversion: Reversion, textStore: TextStore) { + if(!textStore) { + throw "Accepting suggestions requires a destination TextStore instance." } if(!this.isActive) { @@ -298,9 +298,9 @@ export class LanguageProcessor extends EventEmitter { // In embedded mode, both Android and iOS are best served by calculating this transform and applying its // values as needed for use with their IME interfaces. // TODO-web-core - const transform = final.buildTransformFrom(outputTarget as OutputTargetBase); + const transform = final.buildTransformFrom(textStore as TextStore); // TODO-web-core - (outputTarget as OutputTargetBase).apply(transform); + (textStore as TextStore).apply(transform); // The reason we need to preserve the additive-inverse 'transformId' property on Reversions. const promise = this.currentPromise = this.lmEngine.revertSuggestion(reversion, new ContextWindow(original.preInput as Mock, this.configuration, null)) @@ -311,13 +311,13 @@ export class LanguageProcessor extends EventEmitter { return promise; } - public predictFromTarget(outputTarget: OutputTargetBase, layerId: string): Promise { - if(!this.isActive || !outputTarget) { + public predictFromTarget(textStore: TextStore, layerId: string): Promise { + if(!this.isActive || !textStore) { return null; } // TODO-web-core - const transcription = (outputTarget as OutputTargetBase).buildTranscriptionFrom(outputTarget as OutputTargetBase, null, false); + const transcription = (textStore as TextStore).buildTranscriptionFrom(textStore as TextStore, null, false); return this.predict(transcription, layerId); } diff --git a/web/src/engine/main/src/keyboardInterfaceBase.ts b/web/src/engine/main/src/keyboardInterfaceBase.ts index 746a16fa83..4f83056103 100644 --- a/web/src/engine/main/src/keyboardInterfaceBase.ts +++ b/web/src/engine/main/src/keyboardInterfaceBase.ts @@ -112,7 +112,7 @@ export class KeyboardInterfaceBase { this.resetContextCache(); - // As this function isn't provided a handle to an active outputTarget, we rely on + // As this function isn't provided a handle to an active textStore, we rely on // the context manager to resolve said issue. this.engine.contextManager.insertText(this, Ptext, PdeadKey); } diff --git a/web/src/engine/main/src/keymanEngineBase.ts b/web/src/engine/main/src/keymanEngineBase.ts index 25fcca5845..f110266cbb 100644 --- a/web/src/engine/main/src/keymanEngineBase.ts +++ b/web/src/engine/main/src/keymanEngineBase.ts @@ -1,4 +1,4 @@ -import { type KeyEvent, JSKeyboard, Keyboard, KeyboardProperties, KeyboardKeymanGlobal, ProcessorAction, OutputTargetBase } from "keyman/engine/keyboard"; +import { type KeyEvent, JSKeyboard, Keyboard, KeyboardProperties, KeyboardKeymanGlobal, ProcessorAction, TextStore } from "keyman/engine/keyboard"; import { ProcessorInitOptions } from 'keyman/engine/js-processor'; import { DOMKeyboardLoader as KeyboardLoader } from "keyman/engine/keyboard/dom-keyboard-loader"; import { WorkerFactory } from "@keymanapp/lexical-model-layer/web" @@ -54,9 +54,9 @@ export class KeymanEngineBase< protected keyEventRefocus?: () => void; private keyEventListener: KeyEventFullHandler = (event, callback) => { - const outputTarget = this.contextManager.activeTarget; + const textStore = this.contextManager.activeTarget; - if(!this.contextManager.activeKeyboard || !outputTarget) { + if(!this.contextManager.activeKeyboard || !textStore) { if(callback) { callback(null, null); } @@ -68,18 +68,18 @@ export class KeymanEngineBase< } if(this.keyEventRefocus) { - // Do anything needed to guarantee that the outputTarget stays active (`app/browser`: maintains focus). + // Do anything needed to guarantee that the textStore stays active (`app/browser`: maintains focus). // (Interaction with the OSK may have de-focused the element providing active context; // we want to restore it in case the user swaps back to the hardware keyboard afterward.) this.keyEventRefocus(); } // Clear any cached codepoint data; we can rebuild it if it's unchanged. - outputTarget.invalidateSelection(); + textStore.invalidateSelection(); // Deadkey matching continues to be troublesome. // Deleting matched deadkeys here seems to correct some of the issues. (JD 6/6/14) // TODO-web-core - (outputTarget as OutputTargetBase).deadkeys().deleteMatched(); // Delete any matched deadkeys before continuing + (textStore as TextStore).deadkeys().deleteMatched(); // Delete any matched deadkeys before continuing if(event.isSynthetic) { const oskLayer = this.osk.vkbd.layerId; @@ -89,7 +89,7 @@ export class KeymanEngineBase< this.core.keyboardProcessor.layerId = oskLayer; } } - const result = this.core.processKeyEvent(event, outputTarget); + const result = this.core.processKeyEvent(event, textStore); if(result && result.transcription?.transform) { this.config.onRuleFinalization(result, this.contextManager.activeTarget); @@ -278,7 +278,7 @@ export class KeymanEngineBase< keyboardProcessor.oldLayerStore.set(''); // Call the keyboard's entry point. // TODO-web-core - const data = keyboardProcessor.processPostKeystroke(keyboardProcessor.contextDevice, predictionContext.currentTarget as OutputTargetBase) + const data = keyboardProcessor.processPostKeystroke(keyboardProcessor.contextDevice, predictionContext.currentTarget as TextStore) // If we have a ProcessorAction as a result, run it on the target. This should // only change system store and variable store values. if (data) { diff --git a/web/src/test/auto/dom/cases/attachment/outputTargetForElement.def.ts b/web/src/test/auto/dom/cases/attachment/outputTargetForElement.def.ts index e90e0021b2..abdb27aa0b 100644 --- a/web/src/test/auto/dom/cases/attachment/outputTargetForElement.def.ts +++ b/web/src/test/auto/dom/cases/attachment/outputTargetForElement.def.ts @@ -61,7 +61,7 @@ describe('outputTargetForElement()', function () { this.attacher = null; }); - describe('standard `OutputTarget` roots', () => { + describe('standard `TextStore` roots', () => { // So, for these unit tests, attachment has already been established. We just need to // ensure it meets our expectations. diff --git a/web/src/test/auto/dom/cases/browser/contextManager.tests.ts b/web/src/test/auto/dom/cases/browser/contextManager.tests.ts index eabf6eefdd..57ecb803ac 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.tests.ts +++ b/web/src/test/auto/dom/cases/browser/contextManager.tests.ts @@ -239,8 +239,8 @@ describe('app/browser: ContextManager', function () { // Check our expectations re: the `targetchange` event. assert.isTrue(targetchange.calledOnce, 'targetchange event not raised'); - const outputTarget = targetchange.firstCall.args[0]; // Should be an `Input` instance. - assert.equal(outputTarget.getElement(), input, '.activeTarget does not match the newly-focused element'); + const textStore = targetchange.firstCall.args[0]; // Should be an `Input` instance. + assert.equal(textStore.getElement(), input, '.activeTarget does not match the newly-focused element'); }); it('change: null -> textarea', () => { @@ -254,8 +254,8 @@ describe('app/browser: ContextManager', function () { // Check our expectations re: the `targetchange` event. assert.isTrue(targetchange.calledOnce, 'targetchange event not raised'); - const outputTarget = targetchange.firstCall.args[0]; // Should be an `Input` instance. - assert.equal(outputTarget.getElement(), textarea, '.activeTarget does not match the newly-focused element'); + const textStore = targetchange.firstCall.args[0]; // Should be an `Input` instance. + assert.equal(textStore.getElement(), textarea, '.activeTarget does not match the newly-focused element'); }); it('change: null -> designIframe', () => { @@ -270,7 +270,7 @@ describe('app/browser: ContextManager', function () { // Either way, note that focus is handled specially for design-iframes, thus // we need slightly different focus-dispatch here. // - // Possible future improvement: OutputTarget.focusElement (property)? + // Possible future improvement: TextStore.focusElement (property)? // Though that may be affected by the Chrome vs Firefox bit noted above. dispatchFocus('focus', iframe.contentDocument.body); @@ -278,8 +278,8 @@ describe('app/browser: ContextManager', function () { // Check our expectations re: the `targetchange` event. assert.isTrue(targetchange.calledOnce, 'targetchange event not raised'); - const outputTarget = targetchange.firstCall.args[0]; // Should be an `Input` instance. - assert.equal(outputTarget.getElement(), iframe, '.activeTarget does not match the newly-focused element'); + const textStore = targetchange.firstCall.args[0]; // Should be an `Input` instance. + assert.equal(textStore.getElement(), iframe, '.activeTarget does not match the newly-focused element'); }); it('change: null -> contentEditable', () => { @@ -293,8 +293,8 @@ describe('app/browser: ContextManager', function () { // Check our expectations re: the `targetchange` event. assert.isTrue(targetchange.calledOnce, 'targetchange event not raised'); - const outputTarget = targetchange.firstCall.args[0]; // Should be an `Input` instance. - assert.equal(outputTarget.getElement(), editable, '.activeTarget does not match the newly-focused element'); + const textStore = targetchange.firstCall.args[0]; // Should be an `Input` instance. + assert.equal(textStore.getElement(), editable, '.activeTarget does not match the newly-focused element'); }); it('change: input -> null', () => { @@ -312,8 +312,8 @@ describe('app/browser: ContextManager', function () { // Check our expectations re: the `targetchange` event. assert.isTrue(targetchange.calledTwice, 'targetchange event not raised'); - const outputTarget = targetchange.secondCall.args[0]; // Should be null, since we lost focus. - assert.equal(outputTarget, null, 'targetchange event did not indicate clearing of .activeTarget'); + const textStore = targetchange.secondCall.args[0]; // Should be null, since we lost focus. + assert.equal(textStore, null, 'targetchange event did not indicate clearing of .activeTarget'); }); it('change: input disabled, -> null', async () => { @@ -335,8 +335,8 @@ describe('app/browser: ContextManager', function () { // Check our expectations re: the `targetchange` event. assert.isTrue(targetchange.calledTwice, 'targetchange event not raised'); - const outputTarget = targetchange.secondCall.args[0]; // Should be null, since we lost focus. - assert.equal(outputTarget, null, 'targetchange event did not indicate clearing of .activeTarget'); + const textStore = targetchange.secondCall.args[0]; // Should be null, since we lost focus. + assert.equal(textStore, null, 'targetchange event did not indicate clearing of .activeTarget'); }); it('change: input -> textarea', () => { @@ -358,8 +358,8 @@ describe('app/browser: ContextManager', function () { // Check our expectations re: the `targetchange` event. assert.isTrue(targetchange.calledThrice, 'targetchange event not raised'); - const outputTarget = targetchange.thirdCall.args[0]; // Should be an `Input` instance. - assert.equal(outputTarget.getElement(), textarea, '.activeTarget does not match the newly-focused element'); + const textStore = targetchange.thirdCall.args[0]; // Should be an `Input` instance. + assert.equal(textStore.getElement(), textarea, '.activeTarget does not match the newly-focused element'); }); it('restoration: input (no flags set)', () => { @@ -1145,7 +1145,7 @@ describe('app/browser: ContextManager', function () { // BUT the async load component should be resolved. await assertPromiseResolved(keyboardasyncload.firstCall.args[1], 0); - // Aspect 4: swap BACK to the async-loading keyboard's OutputTarget, which should + // Aspect 4: swap BACK to the async-loading keyboard's TextStore, which should // now be fully set to the keyboard that had been requested for activation upon it. dispatchFocus('blur', input); dispatchFocus('focus', textarea); diff --git a/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts b/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts index 5dc46da422..a6666898ca 100644 --- a/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts +++ b/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts @@ -10,7 +10,7 @@ document.body.appendChild(host); const u = (code: number) => String.fromCodePoint(code); -// Define common interface testing functions that can be run upon the OutputTarget interface. +// Define common interface testing functions that can be run upon the TextStore interface. class MockTests { public static Apple = { normal: 'apple', @@ -62,7 +62,7 @@ class MockTests { //#endregion } -describe('OutputTarget Mocking', function() { +describe('TextStore Mocking', function() { this.timeout(DEFAULT_BROWSER_TIMEOUT); before(function() { @@ -87,7 +87,7 @@ describe('OutputTarget Mocking', function() { assert.equal(mock.getDeadkeyCaret(), 5); }); - it('copies an existing OutputTarget without a text selection', function() { + it('copies an existing TextStore without a text selection', function() { const base = MockTests.setupBase(4); const mock = Mock.from(base); @@ -95,7 +95,7 @@ describe('OutputTarget Mocking', function() { assert.deepEqual(mock.deadkeys(), base.deadkeys()); }); - it('copies an existing OutputTarget with a text selection', function() { + it('copies an existing TextStore with a text selection', function() { const base = MockTests.setupBase(4, 5); const mock = Mock.from(base); @@ -124,7 +124,7 @@ describe('OutputTarget Mocking', function() { base.deadkeys().deleteMatched(); base.deleteCharsBeforeCaret(2); - assert.notDeepEqual(base.deadkeys(), baseInitDks, 'OutputTarget deadkey return is not a proper deep-copy'); + assert.notDeepEqual(base.deadkeys(), baseInitDks, 'TextStore deadkey return is not a proper deep-copy'); assert.equal(mock.getText(), MockTests.Apple.mixed); assert.deepEqual(mock.deadkeys(), baseInitDks); diff --git a/web/src/test/manual/web/osk/scratchspace/index.html b/web/src/test/manual/web/osk/scratchspace/index.html index 70b3f8fa79..47f9cea2b1 100644 --- a/web/src/test/manual/web/osk/scratchspace/index.html +++ b/web/src/test/manual/web/osk/scratchspace/index.html @@ -78,7 +78,7 @@
- +

Active Element

@@ -102,7 +102,7 @@
- +

Active Element

diff --git a/web/src/tools/testing/recorder-core/src/index.ts b/web/src/tools/testing/recorder-core/src/index.ts index 07e94d33b6..b1073d09cc 100644 --- a/web/src/tools/testing/recorder-core/src/index.ts +++ b/web/src/tools/testing/recorder-core/src/index.ts @@ -1,4 +1,4 @@ -import { KeyDistribution, KeyEvent, type OutputTargetBase, Mock } from "keyman/engine/keyboard"; +import { KeyDistribution, KeyEvent, type TextStore, Mock } from "keyman/engine/keyboard"; import Proctor from "./proctor.js"; @@ -216,8 +216,8 @@ export abstract class TestSequence { - // Start with an empty OutputTarget and a fresh KeyboardProcessor. + async test(proctor: Proctor, target?: TextStore): Promise<{success: boolean, result: string}> { + // Start with an empty TextStore and a fresh KeyboardProcessor. if(!target) { target = new Mock(); } diff --git a/web/src/tools/testing/recorder-core/src/nodeProctor.ts b/web/src/tools/testing/recorder-core/src/nodeProctor.ts index f70828ea8f..2bf5cc427a 100644 --- a/web/src/tools/testing/recorder-core/src/nodeProctor.ts +++ b/web/src/tools/testing/recorder-core/src/nodeProctor.ts @@ -8,7 +8,7 @@ import { RecordedSyntheticKeystroke } from "./index.js"; -import { KeyEvent, KeyEventSpec, KeyboardHarness, Mock, OutputTargetBase } from "keyman/engine/keyboard"; +import { KeyEvent, KeyEventSpec, KeyboardHarness, Mock, TextStore } from "keyman/engine/keyboard"; import { DeviceSpec } from "@keymanapp/web-utils"; import { JSKeyboardInterface, JSKeyboardProcessor } from 'keyman/engine/js-processor'; @@ -48,8 +48,8 @@ export default class NodeProctor extends Proctor { return true; } - async simulateSequence(sequence: TestSequence, target?: OutputTargetBase): Promise { - // Start with an empty OutputTarget and a fresh KeyboardProcessor. + async simulateSequence(sequence: TestSequence, target?: TextStore): Promise { + // Start with an empty TextStore and a fresh KeyboardProcessor. if(!target) { target = new Mock(); } @@ -88,7 +88,7 @@ export default class NodeProctor extends Proctor { // ... we _could_ if we wanted to, though. The framework is mostly in place; // it's a matter of actually adding the feature. // TODO-web-core - const ruleBehavior = processor.processKeystroke(new KeyEvent(keyEvent), (target as OutputTargetBase)); + const ruleBehavior = processor.processKeystroke(new KeyEvent(keyEvent), (target as TextStore)); if (this.debugMode) { console.log("Processing %d:", keyEvent.Lcode); diff --git a/web/src/tools/testing/recorder-core/src/proctor.ts b/web/src/tools/testing/recorder-core/src/proctor.ts index db1f36110e..1bf4a32071 100644 --- a/web/src/tools/testing/recorder-core/src/proctor.ts +++ b/web/src/tools/testing/recorder-core/src/proctor.ts @@ -1,5 +1,5 @@ import { type DeviceSpec } from "@keymanapp/web-utils"; -import { type OutputTargetBase } from "keyman/engine/keyboard"; +import { type TextStore } from "keyman/engine/keyboard"; import type { KeyboardTest, TestSet, TestSequence } from "./index.js"; @@ -49,5 +49,5 @@ export default abstract class Proctor { * Simulates the specified test sequence for use in testing. * @param sequence The recorded sequence, generally provided by a test set. */ - abstract simulateSequence(sequence: TestSequence, target?: OutputTargetBase): Promise; + abstract simulateSequence(sequence: TestSequence, target?: TextStore): Promise; } \ No newline at end of file diff --git a/web/src/tools/testing/recorder/browserProctor.ts b/web/src/tools/testing/recorder/browserProctor.ts index 822f802459..ea135c7a0d 100644 --- a/web/src/tools/testing/recorder/browserProctor.ts +++ b/web/src/tools/testing/recorder/browserProctor.ts @@ -4,7 +4,7 @@ import { type DeviceSpec } from "@keymanapp/web-utils"; -import { type OutputTargetBase } from "keyman/engine/keyboard"; +import { type TextStore } from "keyman/engine/keyboard"; import { type KeymanEngine } from 'keyman/app/browser'; @@ -81,7 +81,7 @@ export class BrowserProctor extends Proctor { // Execution of a test sequence depends on the testing environment; this handles // the browser-specific aspects. - async simulateSequence(sequence: TestSequence, outputTarget?: OutputTargetBase): Promise { + async simulateSequence(sequence: TestSequence, textStore?: TextStore): Promise { const driver = new BrowserDriver(this.target); // For the version 10.0 spec diff --git a/web/src/tools/testing/recorder/scribe.ts b/web/src/tools/testing/recorder/scribe.ts index 83179e76de..b31657961f 100644 --- a/web/src/tools/testing/recorder/scribe.ts +++ b/web/src/tools/testing/recorder/scribe.ts @@ -28,7 +28,7 @@ declare let keyman: KeymanEngine; // export namespace dom { // export declare var DOMEventHandlers: any; // export declare class Utils { -// static getOutputTarget(elem: HTMLElement): any; // text.OutputTarget; +// static getOutputTarget(elem: HTMLElement): any; // text.TextStore; // } // } From 0fc7416b682c408d14bbbb9499c59daa59eb615d Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 4 Nov 2025 14:30:46 +0100 Subject: [PATCH 05/17] =?UTF-8?q?refactor(web):=20rename=20`Mock`=20?= =?UTF-8?q?=E2=86=92=20`SyntheticTextStore`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/docs/internal/context-state-management.md | 16 ++-- web/docs/internal/keystroke-lifecycle.md | 2 +- web/docs/internal/keystroke-processing.md | 6 +- web/src/app/webview/src/contextManager.ts | 22 +++--- .../js-processor/src/jsKeyboardInterface.ts | 4 +- .../js-processor/src/jsKeyboardProcessor.ts | 6 +- web/src/engine/keyboard/src/defaultRules.ts | 2 +- web/src/engine/keyboard/src/index.ts | 2 +- .../src/{mock.ts => syntheticTextStore.ts} | 26 +++---- web/src/engine/keyboard/src/textStore.ts | 10 +-- .../engine/main/src/headless/contextWindow.ts | 8 +- .../main/src/headless/inputProcessor.ts | 12 +-- .../main/src/headless/languageProcessor.ts | 16 ++-- .../element_interfaces.tests.ts | 22 +++--- .../element-wrappers/target_mocks.tests.ts | 14 ++-- .../cases/keyboard/domKeyboardLoader.tests.ts | 4 +- .../prediction/predictionContext.tests.js | 28 +++---- .../js-processor/bundled-module.tests.js | 6 +- .../js-processor/engine/context.tests.js | 6 +- .../engine/notany_context.tests.js | 4 +- .../engine/js-processor/kbdInterface.tests.ts | 4 +- .../non-positional-rules.tests.js | 16 ++-- .../specialized-backspace.tests.js | 16 ++-- .../js-processor/transcriptions.tests.js | 74 +++++++++---------- .../engine/keyboard/keyboard-loading.tests.js | 4 +- .../keyboard/keyboardLoaderBase.tests.ts | 4 +- .../headless/engine/keyboard/mocks.tests.js | 22 +++--- .../main/headless/inputProcessor.tests.js | 12 +-- .../main/headless/languageProcessor.tests.js | 18 ++--- .../tools/testing/recorder-core/src/index.ts | 4 +- .../testing/recorder-core/src/nodeProctor.ts | 4 +- 31 files changed, 198 insertions(+), 196 deletions(-) rename web/src/engine/keyboard/src/{mock.ts => syntheticTextStore.ts} (82%) diff --git a/web/docs/internal/context-state-management.md b/web/docs/internal/context-state-management.md index 4d776defbf..9c25186f7b 100644 --- a/web/docs/internal/context-state-management.md +++ b/web/docs/internal/context-state-management.md @@ -71,29 +71,31 @@ It is possible to determine the `Transform` needed to transition from one `TextStore` to another using `TextStore.buildTransformFrom` (defined on `TextStore`). -### The `Mock` - representing context-state +### The `SyntheticTextStore` - representing context-state The comparison and contrast methods mentioned above for `TextStore` are of particular use for predictive text, which usually operates with a -headless implementation of the type, termed a `Mock`. This class may be +headless implementation of the type, termed a `SyntheticTextStore`. This class may be found in -[web/src/engine/js-processor/src/mock.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/js-processor/src/mock.ts). +[web/src/engine/keyboard/src/syntheticTextStore.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/keyboard/src/syntheticTextStore.ts). It is a fully-featured implementation of the `TextStore` interface. -It is possible to make a `Mock`-based clone of any +It is possible to make a `SyntheticTextStore`-based clone of any `TextStore`-derived type - a feature leveraged significantly within the inner workings of Keyman Engine for Web. As JS keyboards can have side effects beyond text-manipulation, predictive text generally -operates by first _cloning_ the "true" context source. `Mock`s are +operates by first _cloning_ the "true" context source. `SyntheticTextStore`s are also used when saving context states within the engine for later reference and/or reuse - a feature also utilized significantly for multitap support. -`Mock`s can also easily be constructed from scratch for a simple string. +`SyntheticTextStore`s can also easily be constructed from scratch for a simple string. Optionally, caret position or selection data may be specified at construction time as well. `epic/web-core`: in theory, this should make them easy to utilize for integration with Keyman Core. +- note: previously called `Mock` + ### The `Transcription` - representing context-state transitions The `Transcription` class (defined in @@ -144,7 +146,7 @@ multitap-generated keystrokes. ### JS-keyboard keystroke processing -For JS-keyboard keystroke processing, a `Mock` clone of the context is +For JS-keyboard keystroke processing, a `SyntheticTextStore` clone of the context is generated before any actual keyboard rule checks are applied. This provides a clear "before" state (eventually saved at `Transcription.preInput`) useful for determining the scope of the diff --git a/web/docs/internal/keystroke-lifecycle.md b/web/docs/internal/keystroke-lifecycle.md index 9940ac6d8e..68dfc14d02 100644 --- a/web/docs/internal/keystroke-lifecycle.md +++ b/web/docs/internal/keystroke-lifecycle.md @@ -126,7 +126,7 @@ The bulk of DOM key events trigger keystroke processing on key-down, though modi The `app/webview` version of the engine, which is designed to be used while embedded in a platform-specific host app, does not have direct access to standard hardware keystroke events, as those are handled by the host app's OS and by native code handlers run outside the host app's WebView containing the app/webview Web engine. It is the responsibility of the host app to handle hardware keystroke events and preprocess them on behalf of the Web engine, then forward them to the Web engine via JS call into the WebView. The `PassthroughKeyboard` class within `app/webview` space provides the method `raiseKeyEvent` as an internal API for this purpose, which converts the mobile-app format for hardware keystroke into the internal `KeyEvent` format. `raiseKeyEvent` also handles mnemonic keystroke processing and remapping. -Also note that this variant does not model the user's text context with Web elements - it is entirely managed through the `Mock` type. +Also note that this variant does not model the user's text context with Web elements - it is entirely managed through the `SyntheticTextStore` type. ### On-screen keyboard diff --git a/web/docs/internal/keystroke-processing.md b/web/docs/internal/keystroke-processing.md index 0945615c09..60c2c44f7e 100644 --- a/web/docs/internal/keystroke-processing.md +++ b/web/docs/internal/keystroke-processing.md @@ -6,7 +6,7 @@ In addition to handling keystroke events produced from hardware keyboards, Keyma Defined at [web/src/engine/keyboard/src/keyEvent.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/keyboard/src/keyEvent.ts), the `KeyEvent` is used to represent incoming _and_ potential keystrokes. This type is what the JS-keyboard processor references when evaluating keyboard rules during its keystroke processing. For versions of the engine that support AltGr aliasing, such aliasing will be applied during generation of `KeyEvent` objects. -Note that for predictive-text's fat-finger correction functionality, the engine will also generate versions of this type for nearby but _unpressed_ keys into the keystroke processing engine as well in order to facilitate predictions that follow context manipulations that could have resulted from specialized rules or reorders on neighboring keys. `Mock`-cloned copies of the active context-state will be leveraged to prevent unwanted manipulation of the true context source. +Note that for predictive-text's fat-finger correction functionality, the engine will also generate versions of this type for nearby but _unpressed_ keys into the keystroke processing engine as well in order to facilitate predictions that follow context manipulations that could have resulted from specialized rules or reorders on neighboring keys. `SyntheticTextStore`-cloned copies of the active context-state will be leveraged to prevent unwanted manipulation of the true context source. ### `isSynthetic` `isSynthetic` should be set to `true` if generated through interaction with an on-screen-keyboard or for fat-finger simulation. It should only be set to `false` if sending the basic key-event data through to the destination, without rule processing, leads to default handling picking up the slack. Browsers provide default handling of keystrokes not directly defined within keyboards, but this is not available for keystrokes against the engine's OSK without internal support. @@ -31,7 +31,7 @@ See also: [context-state-management.md](context-state-management.md#js-keyboard The "first stop" for incoming keystrokes is the `InputProcessor`, found at [web/src/engine/main/src/headless/inputProcessor.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/main/src/headless/.inputProcessor.ts), through its `processKeyEvent` method. This class manages higher-level functionality triggered by keystroke events while deferring actual interpretation of the incoming keystroke further down the line. Of particular note is that it also handles control-flows that require restoration of previously-occuring contexts. -This class is the connection point for generating prediction requests and receiving corresponding suggestions. In order to facilitate higher-quality predictive-text when enabled, the `InputProcessor` will _also_ generate and trigger processing for nearby keys. This process allows transforms, reorders, and KMN keyboard rules to take effect and be used as alternative context roots for predictions. These are generally run against `Mock`-based clones of the true context source and are additionally prevented from triggering long-term side-effects, such as changes to KMN-keyboard variable stores, by only calling `KeyboardProcessor.finalizeProcessorAction` for the true input keystroke's result object. +This class is the connection point for generating prediction requests and receiving corresponding suggestions. In order to facilitate higher-quality predictive-text when enabled, the `InputProcessor` will _also_ generate and trigger processing for nearby keys. This process allows transforms, reorders, and KMN keyboard rules to take effect and be used as alternative context roots for predictions. These are generally run against `SyntheticTextStore`-based clones of the true context source and are additionally prevented from triggering long-term side-effects, such as changes to KMN-keyboard variable stores, by only calling `KeyboardProcessor.finalizeProcessorAction` for the true input keystroke's result object. Keys generated by OSK multitap need special handling here as well; they should always be applied to the context state as it existed at the time of the initial tap. To facilitate this, the `InputProcessor` will directly rewind the active context-source to match the corresponding context state before requesting that the `KeyEvent` be processed. @@ -64,7 +64,7 @@ The method linked above is the primary entrypoint for rule processing of individ #### JS-keyboard interfacing -Certain Keyman language features can make permanent side-effect changes to state. In order to prevent these from taking place for every keystroke, the method that interfaces with JS keyboards - `JSKeyboardInterface.process` - saves the context state (as a `Mock`) and current variable store values, then prepares a fresh `ProcessorAction` instance, before passing control off to the keyboard's backing script. (Note that `JSKeyboardInterface` itself primarily consists of keyboard-script API called by JS-keyboard script.) +Certain Keyman language features can make permanent side-effect changes to state. In order to prevent these from taking place for every keystroke, the method that interfaces with JS keyboards - `JSKeyboardInterface.process` - saves the context state (as a `SyntheticTextStore`) and current variable store values, then prepares a fresh `ProcessorAction` instance, before passing control off to the keyboard's backing script. (Note that `JSKeyboardInterface` itself primarily consists of keyboard-script API called by JS-keyboard script.) A few of the keyboard-script API methods will mark `ProcessorAction` properties directly when called, but the bulk of its data will be set once the keyboard-script returns control to Keyman Engine for Web. At this time, variable store values will also be reverted to prevent possible cross-contamination effects when predictive text is active - they're reapplied later if `KeyboardProcessor.finalizeProcessorAction` is leveraged on the resulting instance. Components documented in [context-state-management.md](./context-state-management.md) are then leveraged to determine the total change to context caused by the keystroke. diff --git a/web/src/app/webview/src/contextManager.ts b/web/src/app/webview/src/contextManager.ts index 63d28cf850..68111bff6f 100644 --- a/web/src/app/webview/src/contextManager.ts +++ b/web/src/app/webview/src/contextManager.ts @@ -1,4 +1,4 @@ -import { JSKeyboard, Keyboard, TextStore, Transcription, TextTransform, Mock, findCommonSubstringEndIndex } from 'keyman/engine/keyboard'; +import { JSKeyboard, Keyboard, TextStore, Transcription, TextTransform, SyntheticTextStore, findCommonSubstringEndIndex } from 'keyman/engine/keyboard'; import { KeyboardStub } from 'keyman/engine/keyboard-storage'; import { ContextManagerBase } from 'keyman/engine/main'; import { WebviewConfiguration } from './configuration.js'; @@ -7,9 +7,9 @@ import { KMWString, isEmptyTransform } from '@keymanapp/web-utils'; export type OnInsertTextFunc = (deleteLeft: number, text: string, deleteRight: number) => void; -export class ContextHost extends Mock { +export class ContextHost extends SyntheticTextStore { readonly oninserttext?: OnInsertTextFunc; - private savedState: Mock; + private savedState: SyntheticTextStore; constructor(oninserttext: OnInsertTextFunc) { super(); @@ -30,7 +30,7 @@ export class ContextHost extends Mock { if(transcription) { //TODO-web-core: shouldn't need cast in the future? - const preInput = transcription.preInput as Mock; + const preInput = transcription.preInput as SyntheticTextStore; // If our saved state matches the `preInput` from the incoming transcription, just reuse its transform. // Will generally not match during multitap operations, though. // @@ -55,18 +55,18 @@ export class ContextHost extends Mock { } saveState() { - this.savedState = Mock.from(this); + this.savedState = SyntheticTextStore.from(this); } restoreTo(original: TextStore): void { - this.savedState = Mock.from(this); + this.savedState = SyntheticTextStore.from(this); // TODO-web-core super.restoreTo(original as TextStore); } updateContext(text: string, selStart: number, selEnd: number): boolean { let shouldResetContext = false; - const tempMock = new Mock(text, selStart ?? KMWString.length(text), selEnd ?? KMWString.length(text)); + const tempMock = new SyntheticTextStore(text, selStart ?? KMWString.length(text), selEnd ?? KMWString.length(text)); const newLeft = tempMock.getTextBeforeCaret(); const oldLeft = this.getTextBeforeCaret(); @@ -108,12 +108,12 @@ export class ContextHost extends Mock { // and we want a consistent interface for context synchronization between // host app + app/webview KMW. this.setSelection(KMWString.length(this.text)); - this.savedState = Mock.from(this); + this.savedState = SyntheticTextStore.from(this); } } export default class ContextManager extends ContextManagerBase { - // Change of context? Just replace the Mock. Context will be ENTIRELY controlled + // Change of context? Just replace the SyntheticTextStore. Context will be ENTIRELY controlled // by whatever is hosting the WebView. (Some aspects of this context replacement have // yet to be modularized at this time, though.) private _rawContext: ContextHost; @@ -130,7 +130,7 @@ export default class ContextManager extends ContextManagerBase { * @return {string} */ private defaultRuleBehavior(Lkc: KeyEvent, textStore: TextStore, readonly: boolean): ProcessorAction { - const preInput = Mock.from(textStore, readonly); + const preInput = SyntheticTextStore.from(textStore, readonly); const ruleBehavior = new ProcessorAction(); let matched = false; @@ -672,7 +672,7 @@ export class JSKeyboardProcessor extends EventEmitter { first.triggersDefaultCommand = first.triggersDefaultCommand || other.triggersDefaultCommand; - const mergingMock = Mock.from(first.transcription.preInput, false); + const mergingMock = SyntheticTextStore.from(first.transcription.preInput, false); mergingMock.apply(first.transcription.transform); mergingMock.apply(other.transcription.transform); diff --git a/web/src/engine/keyboard/src/defaultRules.ts b/web/src/engine/keyboard/src/defaultRules.ts index d39cdbe34f..06c88507be 100644 --- a/web/src/engine/keyboard/src/defaultRules.ts +++ b/web/src/engine/keyboard/src/defaultRules.ts @@ -112,7 +112,7 @@ export default class DefaultRules { /** * Codes matched here generally have default implementations when in a browser but require emulation - * for 'synthetic' `TextStore`s like `Mock`s, which have no default text handling. + * for 'synthetic' `TextStore`s like `SyntheticTextStore`s, which have no default text handling. */ public forSpecialEmulation(Lkc: KeyEvent): EmulationKeystrokes { let code = this.codeForEvent(Lkc); diff --git a/web/src/engine/keyboard/src/index.ts b/web/src/engine/keyboard/src/index.ts index dad2d5f4c5..7bda82d4fd 100644 --- a/web/src/engine/keyboard/src/index.ts +++ b/web/src/engine/keyboard/src/index.ts @@ -32,7 +32,7 @@ export { default as KeyMapping } from "./keyMapping.js"; export { type SystemStoreMutationHandler, MutableSystemStore, SystemStore, SystemStoreIDs, type SystemStoreDictionary } from "./systemStore.js"; export { type VariableStore, VariableStoreSerializer, VariableStoreDictionary } from "./variableStore.js"; -export { Mock } from "./mock.js"; +export { SyntheticTextStore } from "./syntheticTextStore.js"; export { TextStore } from "./textStore.js"; export { findCommonSubstringEndIndex } from "./stringDivergence.js"; export { Deadkey } from "./deadkeys.js"; diff --git a/web/src/engine/keyboard/src/mock.ts b/web/src/engine/keyboard/src/syntheticTextStore.ts similarity index 82% rename from web/src/engine/keyboard/src/mock.ts rename to web/src/engine/keyboard/src/syntheticTextStore.ts index b426815c51..3fb9ac8cbc 100644 --- a/web/src/engine/keyboard/src/mock.ts +++ b/web/src/engine/keyboard/src/syntheticTextStore.ts @@ -1,7 +1,7 @@ import { TextStore } from './textStore.js'; import { KMWString } from '@keymanapp/web-utils'; -export class Mock extends TextStore { +export class SyntheticTextStore extends TextStore { text: string; selStart: number; @@ -31,17 +31,17 @@ export class Mock extends TextStore { } } - // Clones the state of an existing EditableElement, creating a Mock version of its state. - static from(textStore: TextStore, readonly?: boolean): Mock { - let clone: Mock; + // Clones the state of an existing EditableElement, creating a SyntheticTextStore version of its state. + static from(textStore: TextStore, readonly?: boolean): SyntheticTextStore { + let clone: SyntheticTextStore; this.assertIsOutputTargetBase(textStore); - if (textStore instanceof Mock) { + if (textStore instanceof SyntheticTextStore) { // Avoids the need to run expensive kmwstring.ts `length()` - // calculations when deep-copying Mock instances. - const priorMock = textStore as Mock; - clone = new Mock(priorMock.text, priorMock.selStart, priorMock.selEnd); + // calculations when deep-copying SyntheticTextStore instances. + const priorMock = textStore as SyntheticTextStore; + clone = new SyntheticTextStore(priorMock.text, priorMock.selStart, priorMock.selEnd); } else { const text = textStore.getText(); const textLen = KMWString.length(text); @@ -57,10 +57,10 @@ export class Mock extends TextStore { selectionEnd = textLen - KMWString.length(afterText); } - // readonly group or not, the returned Mock remains the same. + // readonly group or not, the returned SyntheticTextStore remains the same. // New-context events should act as if the caret were at the earlier-in-context // side of the selection, same as standard keyboard rules. - clone = new Mock(text, selectionStart, selectionEnd); + clone = new SyntheticTextStore(text, selectionStart, selectionEnd); } // Also duplicate deadkey state! (Needed for fat-finger ops.) @@ -147,11 +147,11 @@ export class Mock extends TextStore { } /** - * Indicates if this Mock represents an identical context to that of another Mock. + * Indicates if this SyntheticTextStore represents an identical context to that of another SyntheticTextStore. * @param other * @returns */ - isEqual(other: Mock) { + isEqual(other: SyntheticTextStore) { return this.text == other.text && this.selStart == other.selStart && this.selEnd == other.selEnd @@ -159,6 +159,6 @@ export class Mock extends TextStore { } doInputEvent() { - // Mock isn't backed by an element, so it won't have any event listeners. + // SyntheticTextStore isn't backed by an element, so it won't have any event listeners. } } diff --git a/web/src/engine/keyboard/src/textStore.ts b/web/src/engine/keyboard/src/textStore.ts index 1e7889f918..9b405deee3 100644 --- a/web/src/engine/keyboard/src/textStore.ts +++ b/web/src/engine/keyboard/src/textStore.ts @@ -2,7 +2,7 @@ import { KMWString } from "@keymanapp/web-utils"; import { Alternate, TextTransform } from "./keyboards/textTransform.js"; import { Transcription } from "./keyboards/transcription.js"; import { findCommonSubstringEndIndex } from "./stringDivergence.js"; -import { Mock } from "./mock.js"; +import { SyntheticTextStore } from "./syntheticTextStore.js"; // Defines deadkey management in a manner attachable to each element interface. import { type KeyEvent } from 'keyman/engine/keyboard'; @@ -52,7 +52,7 @@ export abstract class TextStore { } /** - * Needed to properly clone deadkeys for use with Mock element interfaces toward predictive text purposes. + * Needed to properly clone deadkeys for use with SyntheticTextStore element interfaces toward predictive text purposes. * @param {object} dks An existing set of deadkeys to deep-copy for use by this element interface. */ protected setDeadkeys(dks: DeadkeyTracker) { @@ -65,7 +65,7 @@ export abstract class TextStore { * * This is designed for use as a "before and after" comparison to determine the effect of a single keyboard rule at a time. * As such, it assumes that the caret is immediately after any inserted text. - * @param from An output target (preferably a Mock) representing the prior state of the input/output system. + * @param from An output target (preferably a SyntheticTextStore) representing the prior state of the input/output system. */ buildTransformFrom(original: TextStore): TextTransform { const toLeft = this.getTextBeforeCaret(); @@ -93,12 +93,12 @@ export abstract class TextStore { // If we ever decide to re-add deadkey tracking, this is the place for it. - return new Transcription(keyEvent, transform, Mock.from(original, readonly), alternates); + return new Transcription(keyEvent, transform, SyntheticTextStore.from(original, readonly), alternates); } /** * Restores the `TextStore` to the indicated state. Designed for use with `Transcription.preInput`. - * @param original An `TextStore` (usually a `Mock`). + * @param original An `TextStore` (usually a `SyntheticTextStore`). */ restoreTo(original: TextStore) { this.clearSelection(); diff --git a/web/src/engine/main/src/headless/contextWindow.ts b/web/src/engine/main/src/headless/contextWindow.ts index 964fea5e20..07051d2332 100644 --- a/web/src/engine/main/src/headless/contextWindow.ts +++ b/web/src/engine/main/src/headless/contextWindow.ts @@ -1,5 +1,5 @@ import { LexicalModelTypes } from '@keymanapp/common-types'; -import { Mock } from "keyman/engine/keyboard"; +import { SyntheticTextStore } from "keyman/engine/keyboard"; import { KMWString } from '@keymanapp/web-utils'; export default class ContextWindow implements LexicalModelTypes.Context { @@ -18,7 +18,7 @@ export default class ContextWindow implements LexicalModelTypes.Context { casingForm?: LexicalModelTypes.CasingForm; - constructor(mock: Mock, config: LexicalModelTypes.Configuration, layerId: string) { + constructor(mock: SyntheticTextStore, config: LexicalModelTypes.Configuration, layerId: string) { this.left = mock.getTextBeforeCaret(); this.startOfBuffer = KMWString.length(this.left) <= config.leftContextCodePoints; if(!this.startOfBuffer) { @@ -38,9 +38,9 @@ export default class ContextWindow implements LexicalModelTypes.Context { null; } - public toMock(): Mock { + public toMock(): SyntheticTextStore { const caretPos = KMWString.length(this.left); - return new Mock(this.left + (this.right || ""), caretPos); + return new SyntheticTextStore(this.left + (this.right || ""), caretPos); } } \ No newline at end of file diff --git a/web/src/engine/main/src/headless/inputProcessor.ts b/web/src/engine/main/src/headless/inputProcessor.ts index 0518d989aa..aee818c42a 100644 --- a/web/src/engine/main/src/headless/inputProcessor.ts +++ b/web/src/engine/main/src/headless/inputProcessor.ts @@ -11,7 +11,7 @@ import { Codes, JSKeyboard, KeyboardMinimalInterface, - Mock, + SyntheticTextStore, TextStore, ProcessorAction, SystemStoreIDs, @@ -120,10 +120,10 @@ export class InputProcessor { // to revert it. If not, we assume it's a layer-change multitap, in which case // no such reset is needed. // TODO-web-core - if(!isEmptyTransform(transcription.transform) || !(transcription.preInput as Mock).isEqual(Mock.from(textStore))) { + if(!isEmptyTransform(transcription.transform) || !(transcription.preInput as SyntheticTextStore).isEqual(SyntheticTextStore.from(textStore))) { // Restores full context, including deadkeys in their exact pre-keystroke state. // TODO-web-core - (textStore as TextStore).restoreTo(transcription.preInput as Mock); + (textStore as TextStore).restoreTo(transcription.preInput as SyntheticTextStore); } /* else: @@ -200,7 +200,7 @@ export class InputProcessor { // Create a "mock" backup of the current textStore in its pre-input state. // Current, long-existing assumption - it's DOM-backed. // TODO-web-core - const preInputMock = Mock.from(textStore as TextStore, true); + const preInputMock = SyntheticTextStore.from(textStore as TextStore, true); const startingLayerId = this.keyboardProcessor.layerId; @@ -292,7 +292,7 @@ export class InputProcessor { return keepRuleBehavior ? ruleBehavior : null; } - private buildAlternates(ruleBehavior: ProcessorAction, keyEvent: KeyEvent, preInputMock: Mock): Alternate[] { + private buildAlternates(ruleBehavior: ProcessorAction, keyEvent: KeyEvent, preInputMock: SyntheticTextStore): Alternate[] { let alternates: Alternate[]; // If we're performing a 'default command', it's not a standard 'typing' event - don't do fat-finger stuff. @@ -360,7 +360,7 @@ export class InputProcessor { break; } - const mock = Mock.from(windowedMock, false); + const mock = SyntheticTextStore.from(windowedMock, false); const altKey = pair.keySpec; if(!altKey) { diff --git a/web/src/engine/main/src/headless/languageProcessor.ts b/web/src/engine/main/src/headless/languageProcessor.ts index b3a749ce93..aa76d7c22b 100644 --- a/web/src/engine/main/src/headless/languageProcessor.ts +++ b/web/src/engine/main/src/headless/languageProcessor.ts @@ -1,6 +1,6 @@ import { EventEmitter } from "eventemitter3"; import { LMLayer, WorkerFactory } from "@keymanapp/lexical-model-layer/web"; -import { Transcription, TextStore, Mock } from 'keyman/engine/keyboard'; +import { Transcription, TextStore, SyntheticTextStore } from 'keyman/engine/keyboard'; import { LanguageProcessorEventMap, ModelSpec, StateChangeEnum, ReadySuggestions } from 'keyman/engine/interfaces'; import ContextWindow from "./contextWindow.js"; import { TranscriptionCache } from "./transcriptionCache.js"; @@ -160,7 +160,7 @@ export class LanguageProcessor extends EventEmitter { } // TODO-web-core - const context = new ContextWindow(Mock.from((target as TextStore), false), this.configuration, layerId); + const context = new ContextWindow(SyntheticTextStore.from((target as TextStore), false), this.configuration, layerId); return this.lmEngine.wordbreak(context); } @@ -216,7 +216,7 @@ export class LanguageProcessor extends EventEmitter { // Apply the Suggestion! // Step 1: determine the final output text - const final = Mock.from(original.preInput, false); + const final = SyntheticTextStore.from(original.preInput, false); final.apply(suggestion.transform); // Step 2: build a final, master Transform that will produce the desired results from the CURRENT state. @@ -233,12 +233,12 @@ export class LanguageProcessor extends EventEmitter { // Build a 'reversion' Transcription that can be used to undo this apply() if needed, // replacing the suggestion transform with the original input text. - const preApply = Mock.from(original.preInput, false); + const preApply = SyntheticTextStore.from(original.preInput, false); preApply.apply(original.transform); // Builds the reversion option according to the loaded lexical model's known // syntactic properties. - const suggestionContext = new ContextWindow(original.preInput as Mock, this.configuration, getLayerId()); + const suggestionContext = new ContextWindow(original.preInput as SyntheticTextStore, this.configuration, getLayerId()); // We must accept the Suggestion from its original context, which was before // `original.transform` was applied. @@ -291,7 +291,7 @@ export class LanguageProcessor extends EventEmitter { // Apply the Reversion! // Step 1: determine the final output text - const final = Mock.from(original.preInput, false); + const final = SyntheticTextStore.from(original.preInput, false); final.apply(reversion.transform); // Should match original.transform, actually. (See applySuggestion) // Step 2: build a final, master Transform that will produce the desired results from the CURRENT state. @@ -303,7 +303,7 @@ export class LanguageProcessor extends EventEmitter { (textStore as TextStore).apply(transform); // The reason we need to preserve the additive-inverse 'transformId' property on Reversions. - const promise = this.currentPromise = this.lmEngine.revertSuggestion(reversion, new ContextWindow(original.preInput as Mock, this.configuration, null)) + const promise = this.currentPromise = this.lmEngine.revertSuggestion(reversion, new ContextWindow(original.preInput as SyntheticTextStore, this.configuration, null)) // If the "current Promise" is as set above, clear it. // If another one has been triggered since... don't. promise.then(() => this.currentPromise = (this.currentPromise == promise) ? null : this.currentPromise); @@ -331,7 +331,7 @@ export class LanguageProcessor extends EventEmitter { return null; } - const context = new ContextWindow(transcription.preInput as Mock, this.configuration, layerId); + const context = new ContextWindow(transcription.preInput as SyntheticTextStore, this.configuration, layerId); this.recordTranscription(transcription); if(resetContext) { diff --git a/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.tests.ts b/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.tests.ts index 99997f14d4..aeaf007833 100644 --- a/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.tests.ts +++ b/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.tests.ts @@ -1,6 +1,6 @@ import { assert } from 'chai'; -import { KMWString, Mock } from 'keyman/engine/keyboard'; +import { KMWString, SyntheticTextStore } from 'keyman/engine/keyboard'; import * as wrappers from 'keyman/engine/element-wrappers'; import { DynamicElements } from '../../test_utils.js'; @@ -320,10 +320,10 @@ class DesignIFrameTestHelper implements TestHelper { } //#endregion -//#region Defines helpers related to Mock test setup. +//#region Defines helpers related to SyntheticTextStore test setup. class MockTestHelper implements TestHelper { setupElement(): ElementPair { - return { elem: null, wrapper: new Mock() }; + return { elem: null, wrapper: new SyntheticTextStore() }; } resetWithText(pair: ElementPair, string: string) { @@ -371,7 +371,7 @@ class InterfaceTests { public static DesignIFrame = new DesignIFrameTestHelper(); - public static Mock = new MockTestHelper(); + public static SyntheticTextStore = new MockTestHelper(); //#region Defines common test patterns across element tests public static Tests = class { @@ -1379,8 +1379,8 @@ describe('Element Input/Output Interfacing', function () { }); }); - describe('The "Mock" output target', function () { - // Unique to the Mock type - element interface cloning tests. Is element state properly copied? + describe('The "SyntheticTextStore" output target', function () { + // Unique to the SyntheticTextStore type - element interface cloning tests. Is element state properly copied? // As those require a very different setup, they're in the target_mocks.js test case file instead. // Basic text-retrieval unit tests are now done headlessly in keyman/engine/keyboard. @@ -1388,26 +1388,26 @@ describe('Element Input/Output Interfacing', function () { describe('Text Mutation', function () { describe('deleteCharsBeforeCaret', function () { it("correctly deletes characters from 'context' (no active selection)", function () { - InterfaceTests.Tests.deleteCharsBeforeCaretNoSelection(InterfaceTests.Mock); + InterfaceTests.Tests.deleteCharsBeforeCaretNoSelection(InterfaceTests.SyntheticTextStore); }); it("correctly deletes characters from 'context' (with active selection)", function () { - InterfaceTests.Tests.deleteCharsBeforeCaretWithSelection(InterfaceTests.Mock); + InterfaceTests.Tests.deleteCharsBeforeCaretWithSelection(InterfaceTests.SyntheticTextStore); }); }); describe('insertTextBeforeCaret', function () { it("correctly replaces the element's 'context' (no active selection)", function () { - InterfaceTests.Tests.insertTextBeforeCaretNoSelection(InterfaceTests.Mock); + InterfaceTests.Tests.insertTextBeforeCaretNoSelection(InterfaceTests.SyntheticTextStore); }); it("correctly replaces the element's 'context' (with active selection)", function () { - InterfaceTests.Tests.insertTextBeforeCaretWithSelection(InterfaceTests.Mock); + InterfaceTests.Tests.insertTextBeforeCaretWithSelection(InterfaceTests.SyntheticTextStore); }); }); it('correctly maintains deadkeys', function () { - InterfaceTests.Tests.deadkeyMaintenance(InterfaceTests.Mock); + InterfaceTests.Tests.deadkeyMaintenance(InterfaceTests.SyntheticTextStore); }); }); }); diff --git a/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts b/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts index a6666898ca..ea4ac07707 100644 --- a/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts +++ b/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts @@ -1,6 +1,6 @@ import { assert } from 'chai'; -import { KMWString, Mock } from 'keyman/engine/keyboard'; +import { KMWString, SyntheticTextStore } from 'keyman/engine/keyboard'; import { Input } from 'keyman/engine/element-wrappers'; import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs'; @@ -78,10 +78,10 @@ describe('TextStore Mocking', function() { KMWString.enableSupplementaryPlane(false); }) - describe('The "Mock" output target', function() { + describe('The "SyntheticTextStore" output target', function() { describe('Initialization', function() { it('properly initializes from a raw string', function() { - const mock = new Mock(MockTests.Apple.mixed); + const mock = new SyntheticTextStore(MockTests.Apple.mixed); assert.equal(mock.getText(), MockTests.Apple.mixed); assert.equal(mock.getDeadkeyCaret(), 5); @@ -90,7 +90,7 @@ describe('TextStore Mocking', function() { it('copies an existing TextStore without a text selection', function() { const base = MockTests.setupBase(4); - const mock = Mock.from(base); + const mock = SyntheticTextStore.from(base); assert.equal(mock.getText(), MockTests.Apple.mixed); assert.deepEqual(mock.deadkeys(), base.deadkeys()); }); @@ -98,7 +98,7 @@ describe('TextStore Mocking', function() { it('copies an existing TextStore with a text selection', function() { const base = MockTests.setupBase(4, 5); - const mock = Mock.from(base); + const mock = SyntheticTextStore.from(base); // The selection should appear to be automatically deleted, as any text mutation // by KMW would automatically erase the text anyway. assert.equal(mock.getTextBeforeCaret(), MockTests.Apple.mixed.substr(0, 5)); @@ -114,7 +114,7 @@ describe('TextStore Mocking', function() { it('is not affected by mutation of the source element', function() { // Already-verified code const base = MockTests.setupBase(4); - const mock = Mock.from(base); + const mock = SyntheticTextStore.from(base); const baseInitDks = base.deadkeys().clone(); // Now for the actual test. @@ -133,7 +133,7 @@ describe('TextStore Mocking', function() { it('does not affect the source element when mutated', function() { // Already-verified code const base = MockTests.setupBase(4); - const mock = Mock.from(base); + const mock = SyntheticTextStore.from(base); const baseInitDks = base.deadkeys().clone(); // Now for the actual test. diff --git a/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.tests.ts b/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.tests.ts index 8cc5493d4e..326cb5b9aa 100644 --- a/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.tests.ts +++ b/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.tests.ts @@ -1,7 +1,7 @@ import { assert } from 'chai'; import { DOMKeyboardLoader } from 'keyman/engine/keyboard/dom-keyboard-loader'; -import { KeyboardHarness, JSKeyboard, MinimalKeymanGlobal, DeviceSpec, KeyboardKeymanGlobal, KeyboardDownloadError, KeyboardScriptError, Keyboard, Mock } from 'keyman/engine/keyboard'; +import { KeyboardHarness, JSKeyboard, MinimalKeymanGlobal, DeviceSpec, KeyboardKeymanGlobal, KeyboardDownloadError, KeyboardScriptError, Keyboard, SyntheticTextStore } from 'keyman/engine/keyboard'; import { JSKeyboardInterface } from 'keyman/engine/js-processor'; import { assertThrowsAsync } from 'keyman/tools/testing/test-utils'; @@ -82,7 +82,7 @@ describe('Keyboard loading in DOM', function() { // TODO: verify actual rule processing. const nullKeyEvent = jsKeyboard.constructNullKeyEvent(device); - const mock = new Mock(); + const mock = new SyntheticTextStore(); const result = jsHarness.processKeystroke(mock, nullKeyEvent); assert.isOk(result); diff --git a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.tests.js b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.tests.js index 96b8d98a26..dd2d75257f 100644 --- a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.tests.js +++ b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.tests.js @@ -4,7 +4,7 @@ import sinon from 'sinon'; import { LanguageProcessor, TranscriptionCache } from 'keyman/engine/main'; import { PredictionContext } from 'keyman/engine/interfaces'; import { Worker as LMWorker } from "@keymanapp/lexical-model-layer/node"; -import { DeviceSpec, Mock } from 'keyman/engine/keyboard'; +import { DeviceSpec, SyntheticTextStore } from 'keyman/engine/keyboard'; function compileDummyModel(suggestionSets) { return ` @@ -81,8 +81,8 @@ describe("PredictionContext", () => { let updateFake = sinon.fake(); predictiveContext.on('update', updateFake); - let mock = new Mock("appl", 4); // "appl|", with '|' as the caret position. - const initialMock = Mock.from(mock); + let mock = new SyntheticTextStore("appl", 4); // "appl|", with '|' as the caret position. + const initialMock = SyntheticTextStore.from(mock); const promise = predictiveContext.setCurrentTarget(mock); // Initial predictive state: no suggestions. context.initializeState() has not yet been called. @@ -120,8 +120,8 @@ describe("PredictionContext", () => { let updateFake = sinon.fake(); predictiveContext.on('update', updateFake); - let mock = new Mock("appl", 4); // "appl|", with '|' as the caret position. - const initialMock = Mock.from(mock); + let mock = new SyntheticTextStore("appl", 4); // "appl|", with '|' as the caret position. + const initialMock = SyntheticTextStore.from(mock); const promise = predictiveContext.setCurrentTarget(mock); // Initial predictive state: no suggestions. context.initializeState() has not yet been called. @@ -180,7 +180,7 @@ describe("PredictionContext", () => { const predictiveContext = new PredictionContext(langProcessor, dummiedGetLayer); - let mock = new Mock("appl", 4); // "appl|", with '|' as the caret position. + let mock = new SyntheticTextStore("appl", 4); // "appl|", with '|' as the caret position. const initialSuggestions = await predictiveContext.setCurrentTarget(mock); let updateFake = sinon.fake(); @@ -204,7 +204,7 @@ describe("PredictionContext", () => { const predictiveContext = new PredictionContext(langProcessor, dummiedGetLayer); - let textState = new Mock("appl", 4); // "appl|", with '|' as the caret position. + let textState = new SyntheticTextStore("appl", 4); // "appl|", with '|' as the caret position. await predictiveContext.setCurrentTarget(textState); @@ -213,7 +213,7 @@ describe("PredictionContext", () => { let suggestions; - let previousTextState = Mock.from(textState); + let previousTextState = SyntheticTextStore.from(textState); textState.insertTextBeforeCaret('e'); // appl| + e = apple let transcription = textState.buildTranscriptionFrom(previousTextState, null, true); await langProcessor.predict(transcription, dummiedGetLayer()); @@ -226,7 +226,7 @@ describe("PredictionContext", () => { assert.equal(suggestions.find((obj) => obj.transform.deleteLeft != 0).displayAs, 'apps'); // Now for the real test. - previousTextState = Mock.from(textState); // snapshot it! + previousTextState = SyntheticTextStore.from(textState); // snapshot it! const suggestionApply = suggestions.find((obj) => obj.displayAs == 'apply'); assert.isOk(suggestionApply); @@ -270,14 +270,14 @@ describe("PredictionContext", () => { const predictiveContext = new PredictionContext(langProcessor, dummiedGetLayer); - let textState = new Mock("appl", 4); // "appl|", with '|' as the caret position. + let textState = new SyntheticTextStore("appl", 4); // "appl|", with '|' as the caret position. // Test setup - return to the state at the end of the prior-defined unit test ('suggestion application...') await predictiveContext.setCurrentTarget(textState); // This is the point in time that a reversion operation will rewind the context to. - const revertBaseTextState = Mock.from(textState); + const revertBaseTextState = SyntheticTextStore.from(textState); textState.insertTextBeforeCaret('e'); // appl| + e = apple let transcription = textState.buildTranscriptionFrom(revertBaseTextState, null, true); @@ -291,7 +291,7 @@ describe("PredictionContext", () => { const suggestionApply = originalSuggestionSet.find((obj) => obj.displayAs == 'apply'); assert.isOk(suggestionApply); - let previousTextState = Mock.from(textState); + let previousTextState = SyntheticTextStore.from(textState); // For awaiting the suggestions generated upon applying our desired suggestion. // We aren't given a direct Promise for that, but we can construct one this way. @@ -320,7 +320,7 @@ describe("PredictionContext", () => { assert.equal(reversion.displayAs.length, previousTextState.getText().length + 2); // +2: opening + closing quotes. // Fire away! Time to apply the reversion. - previousTextState = Mock.from(textState); + previousTextState = SyntheticTextStore.from(textState); // Since the test uses a separate thread via Worker, make sure to set up any important event handlers // before we request the reversion. @@ -340,7 +340,7 @@ describe("PredictionContext", () => { assert.isNull(returnValue); // as per the method's spec. // Verify that the rewind + application of reversion worked! - let rewoundTextStateWithInput = Mock.from(revertBaseTextState); // appl + let rewoundTextStateWithInput = SyntheticTextStore.from(revertBaseTextState); // appl rewoundTextStateWithInput.apply(reversion.transform); // + e assert.equal(rewoundTextStateWithInput.getText(), 'apple'); // For visual clarity. diff --git a/web/src/test/auto/headless/engine/js-processor/bundled-module.tests.js b/web/src/test/auto/headless/engine/js-processor/bundled-module.tests.js index 0eceada34e..8601a06f45 100644 --- a/web/src/test/auto/headless/engine/js-processor/bundled-module.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/bundled-module.tests.js @@ -39,9 +39,9 @@ describe('Bundled ES Module for keyboard', function () { }); }); - describe('Mock', () => { + describe('SyntheticTextStore', () => { it('basic functionality test', () => { - let target = new KeyboardPackage.Mock("aple", 2); // ap | le + let target = new KeyboardPackage.SyntheticTextStore("aple", 2); // ap | le target.insertTextBeforeCaret('p'); assert.equal(target.getText(), "apple"); }); @@ -49,7 +49,7 @@ describe('Bundled ES Module for keyboard', function () { it('smp test', () => { KMWString.enableSupplementaryPlane(true); // Declared & defined in web-utils. try { - let target = new KeyboardPackage.Mock(u(0x1d5ba) + u(0x1d5c9) + u(0x1d5c5) + u(0x1d5be), 2); // ap | le + let target = new KeyboardPackage.SyntheticTextStore(u(0x1d5ba) + u(0x1d5c9) + u(0x1d5c5) + u(0x1d5be), 2); // ap | le target.insertTextBeforeCaret(u(0x1d5c9)); assert.equal(target.getText(), u(0x1d5ba) + u(0x1d5c9) + u(0x1d5c9) + u(0x1d5c5) + u(0x1d5be)); } finally { diff --git a/web/src/test/auto/headless/engine/js-processor/engine/context.tests.js b/web/src/test/auto/headless/engine/js-processor/engine/context.tests.js index 813f699b1e..54b70f84d6 100644 --- a/web/src/test/auto/headless/engine/js-processor/engine/context.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/engine/context.tests.js @@ -3,7 +3,7 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { MinimalKeymanGlobal, SyntheticTextStore } from 'keyman/engine/keyboard'; import { JSKeyboardInterface, JSKeyboardProcessor } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; @@ -61,7 +61,7 @@ function runEngineRuleSet(ruleSet, defaultNoun) { let proctor = new NodeProctor(keyboardWithHarness, device, assert.equal); // We want to specify the OutputTarget for this test; our actual concern is the resulting context. - var target = new Mock(); + var target = new SyntheticTextStore(); ruleSeq.test(proctor, target); // Now for the real test! @@ -1118,7 +1118,7 @@ describe('Engine - Context Matching', function() { let proctor = new NodeProctor(keyboardWithHarness, device, assert.equal); // We want to specify the OutputTarget for this test; our actual concern is the resulting context. - var target = new Mock(); + var target = new SyntheticTextStore(); ruleSeq.test(proctor, target); // Now for the real test! diff --git a/web/src/test/auto/headless/engine/js-processor/engine/notany_context.tests.js b/web/src/test/auto/headless/engine/js-processor/engine/notany_context.tests.js index 840de72cb0..2237d88f4e 100644 --- a/web/src/test/auto/headless/engine/js-processor/engine/notany_context.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/engine/notany_context.tests.js @@ -3,7 +3,7 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { MinimalKeymanGlobal, SyntheticTextStore } from 'keyman/engine/keyboard'; import { JSKeyboardInterface } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { NodeProctor, RecordedKeystrokeSequence } from '@keymanapp/recorder-core'; @@ -23,7 +23,7 @@ function runEngineRuleSet(ruleSet) { // Prepare the context! const ruleSeq = new RecordedKeystrokeSequence(ruleDef); const proctor = new NodeProctor(keyboardWithHarness, device, assert.equal); - const target = new Mock(); + const target = new SyntheticTextStore(); ruleSeq.test(proctor, target); } } diff --git a/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.ts b/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.ts index d3f75964c5..41485ebf59 100644 --- a/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.ts +++ b/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.ts @@ -3,7 +3,7 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { DeviceSpec, JSKeyboard, MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { DeviceSpec, JSKeyboard, MinimalKeymanGlobal, SyntheticTextStore } from 'keyman/engine/keyboard'; import { JSKeyboardInterface } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; @@ -43,7 +43,7 @@ describe('Headless keyboard loading', function () { // -- END: Standard Recorder-based unit test loading boilerplate -- // Runs a blank KeyEvent through the keyboard's rule processing. - harness.processKeystroke(new Mock(), (keyboard as JSKeyboard).constructNullKeyEvent(device)); + harness.processKeystroke(new SyntheticTextStore(), (keyboard as JSKeyboard).constructNullKeyEvent(device)); }); it('does not change the active kehboard', async function () { diff --git a/web/src/test/auto/headless/engine/js-processor/non-positional-rules.tests.js b/web/src/test/auto/headless/engine/js-processor/non-positional-rules.tests.js index a041880bb0..f3b425472e 100644 --- a/web/src/test/auto/headless/engine/js-processor/non-positional-rules.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/non-positional-rules.tests.js @@ -4,7 +4,7 @@ import { ModifierKeyConstants } from '@keymanapp/common-types'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { Codes, KeyEvent, MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { Codes, KeyEvent, MinimalKeymanGlobal, SyntheticTextStore } from 'keyman/engine/keyboard'; import { JSKeyboardInterface } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; @@ -41,7 +41,7 @@ describe('Engine - rule processing', function() { it('matches rules with mnemonic-specced KeyEvents', () => { // Note: plain 'n' is produced from default key outputs for sil_ipa, not a keyboard rule. - let mockMnemonic = new Mock('n'); + let mockMnemonic = new SyntheticTextStore('n'); let mnemonicEvent = new KeyEvent({ // sil_ipa is a mnenomic keyboard: it expects codes based on the key's standard character output. Lcode: '>'.charCodeAt(0), // 62 @@ -62,7 +62,7 @@ describe('Engine - rule processing', function() { it('requires correct modifiers', () => { // Note: plain 'n' is produced from default key outputs for sil_ipa, not a keyboard rule. - let mockMnemonic = new Mock('n'); + let mockMnemonic = new SyntheticTextStore('n'); let mnemonicEvent = new KeyEvent({ // sil_ipa is a mnenomic keyboard: it expects codes based on the key's standard character output. Lcode: '>'.charCodeAt(0), // 62 @@ -81,7 +81,7 @@ describe('Engine - rule processing', function() { }); it('does not match rules with positional-specced KeyEvents', () => { - let mockPositional = new Mock('n'); + let mockPositional = new SyntheticTextStore('n'); let positionalEvent = new KeyEvent({ // If it were positional, we'd use this instead: Lcode: Codes.keyCodes.K_COMMA, // 188 @@ -119,7 +119,7 @@ describe('Engine - rule processing', function() { }); it('matches rules with legacy-specced KeyEvents', () => { - let mockLegacy = new Mock(''); + let mockLegacy = new SyntheticTextStore(''); let legacyEvent = new KeyEvent({ // armenian is a KMW 1.0 keyboard: it expects codes based on the key's standard character output. Lcode: 'a'.charCodeAt(0), @@ -139,7 +139,7 @@ describe('Engine - rule processing', function() { }); it('ignores current modifiers and states', () => { - let mockLegacy = new Mock(''); + let mockLegacy = new SyntheticTextStore(''); let legacyEvent = new KeyEvent({ // armenian is a KMW 1.0 keyboard: it expects codes based on the key's standard character output. Lcode: 'a'.charCodeAt(0), @@ -159,7 +159,7 @@ describe('Engine - rule processing', function() { }); it('does not match rules with mnemonic-specced KeyEvents', () => { - let mockMnemonic = new Mock(''); + let mockMnemonic = new SyntheticTextStore(''); let mnemonicEvent = new KeyEvent({ // armenian is a KMW 1.0 keyboard: it expects codes based on the key's standard character output. Lcode: 'a'.charCodeAt(0), @@ -178,7 +178,7 @@ describe('Engine - rule processing', function() { }); it('does not match rules with positional-specced KeyEvents', () => { - let mockPositional = new Mock(''); + let mockPositional = new SyntheticTextStore(''); let positionalEvent = new KeyEvent({ // If it were positional, we'd use this instead: Lcode: Codes.keyCodes.K_A, diff --git a/web/src/test/auto/headless/engine/js-processor/specialized-backspace.tests.js b/web/src/test/auto/headless/engine/js-processor/specialized-backspace.tests.js index a1a2d1b229..a9a229bd75 100644 --- a/web/src/test/auto/headless/engine/js-processor/specialized-backspace.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/specialized-backspace.tests.js @@ -5,7 +5,7 @@ import { createRequire } from 'module'; const require = createRequire(import.meta.url); import { KMWString } from '@keymanapp/web-utils'; -import { Codes, KeyEvent, MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { Codes, KeyEvent, MinimalKeymanGlobal, SyntheticTextStore } from 'keyman/engine/keyboard'; import { JSKeyboardInterface, JSKeyboardProcessor } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { ModifierKeyConstants } from '@keymanapp/common-types'; @@ -128,7 +128,7 @@ describe('Engine - specialized backspace handling', function() { }); it('empty context, positional keyboard', () => { - let contextSource = new Mock(''); + let contextSource = new SyntheticTextStore(''); let event = new KeyEvent({ Lcode: Codes.keyCodes.K_BKSP, Lmodifiers: 0, @@ -156,7 +156,7 @@ describe('Engine - specialized backspace handling', function() { }); it("empty context, positional keyboard, outputless-key that's not BKSP", () => { - let contextSource = new Mock(''); + let contextSource = new SyntheticTextStore(''); let event = new KeyEvent({ Lcode: Codes.keyCodes.K_A, Lmodifiers: 0, @@ -189,7 +189,7 @@ describe('Engine - specialized backspace handling', function() { }); it('empty context, positional keyboard, but text is selected', () => { - let contextSource = new Mock('selected text', 0); + let contextSource = new SyntheticTextStore('selected text', 0); contextSource.setSelection(0, KMWString.length(contextSource.getText())); let event = new KeyEvent({ @@ -226,7 +226,7 @@ describe('Engine - specialized backspace handling', function() { }); it('empty left-context, positional keyboard', () => { - let contextSource = new Mock('post-caret text', 0); + let contextSource = new SyntheticTextStore('post-caret text', 0); let event = new KeyEvent({ Lcode: Codes.keyCodes.K_BKSP, Lmodifiers: 0, @@ -254,7 +254,7 @@ describe('Engine - specialized backspace handling', function() { }); it('empty context, mnemonic keyboard', () => { - let contextSource = new Mock(''); + let contextSource = new SyntheticTextStore(''); let event = new KeyEvent({ Lcode: Codes.keyCodes.K_BKSP, Lmodifiers: 0, @@ -282,7 +282,7 @@ describe('Engine - specialized backspace handling', function() { }); it('final empty context, positional keyboard, rule-handled BKSP', () => { - let contextSource = new Mock('abc', 2); + let contextSource = new SyntheticTextStore('abc', 2); let event = new KeyEvent({ Lcode: Codes.keyCodes.K_BKSP, Lmodifiers: 0, @@ -317,7 +317,7 @@ describe('Engine - specialized backspace handling', function() { // Special case: BKSP rule-matches with empty left-context. it("empty context, positional keyboard, outputless BKSP rule", () => { - let contextSource = new Mock(''); + let contextSource = new SyntheticTextStore(''); let event = new KeyEvent({ Lcode: Codes.keyCodes.K_BKSP, Lmodifiers: 0, diff --git a/web/src/test/auto/headless/engine/js-processor/transcriptions.tests.js b/web/src/test/auto/headless/engine/js-processor/transcriptions.tests.js index 8f81f0d362..2632431060 100644 --- a/web/src/test/auto/headless/engine/js-processor/transcriptions.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/transcriptions.tests.js @@ -1,6 +1,6 @@ import { assert } from 'chai'; -import { Mock, findCommonSubstringEndIndex } from 'keyman/engine/keyboard'; +import { SyntheticTextStore, findCommonSubstringEndIndex } from 'keyman/engine/keyboard'; import { KMWString } from '@keymanapp/web-utils'; // A unicode-coding like alias for use in constructing non-BMP strings. @@ -157,8 +157,8 @@ describe("Transcriptions and Transforms", function() { it("does not store an alias for related OutputTargets", function() { // We have other texts validating Mocks; by using them as our base 'element', this unit test file // could eventually run in 'headless' mode. - var target = new Mock("apple"); - var original = Mock.from(target); + var target = new SyntheticTextStore("apple"); + var original = SyntheticTextStore.from(target); target.insertTextBeforeCaret("s"); /* It's not exactly black box, but presently we don't NEED the keyEvent object for the method to work. @@ -174,8 +174,8 @@ describe("Transcriptions and Transforms", function() { it("handles context-free single-char output rules", function() { // We have other texts validating Mocks; by using them as our base 'element', this unit test file // could eventually run in 'headless' mode. - var target = new Mock("apple"); - var original = Mock.from(target); + var target = new SyntheticTextStore("apple"); + var original = SyntheticTextStore.from(target); target.insertTextBeforeCaret("s"); /* It's not exactly black box, but presently we don't NEED the keyEvent object for the method to work. @@ -187,8 +187,8 @@ describe("Transcriptions and Transforms", function() { assert.equal(transcription.transform.deleteLeft, 0, "Incorrectly detected left-of-caret deletions"); assert.equal(transcription.transform.deleteRight, 0, "Incorrectly detected right-of-caret deletions"); - target = new Mock("apple", 3); - original = Mock.from(target); + target = new SyntheticTextStore("apple", 3); + original = SyntheticTextStore.from(target); target.insertTextBeforeCaret("s"); // "appsle" var transcription = target.buildTranscriptionFrom(original, null); @@ -199,8 +199,8 @@ describe("Transcriptions and Transforms", function() { }); it("handles operations with moderately long text", function() { - var target = new Mock("The quick brown cat jumped onto the lazy dog.", 19); - var original = Mock.from(target); + var target = new SyntheticTextStore("The quick brown cat jumped onto the lazy dog.", 19); + var original = SyntheticTextStore.from(target); target.setSelection(30); // 19 + 11: moves it to after "onto". target.deleteCharsBeforeCaret(14); // delete: "cat jumped onto" target.insertTextBeforeCaret("fox jumped over"); @@ -228,8 +228,8 @@ he did. Unfortunately, he taught his apprentice everything he knew, then his apprentice killed him in his sleep. It's ironic he could save others from death, but not himself.`; // Sheev Palpatine, in the Star Wars prequels. - var target = new Mock(text, text.length); - var original = Mock.from(target); + var target = new SyntheticTextStore(text, text.length); + var original = SyntheticTextStore.from(target); target.deleteCharsBeforeCaret(1); target.insertTextBeforeCaret("!"); @@ -244,8 +244,8 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. }); it("handles deletions around the caret without text insertion", function() { - var target = new Mock("apple", 2); - var original = Mock.from(target); + var target = new SyntheticTextStore("apple", 2); + var original = SyntheticTextStore.from(target); target.setSelection(3); target.deleteCharsBeforeCaret(2); // "ale" @@ -262,8 +262,8 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. it("handles deletions around the caret without text insertion (non-BMP text)", function() { try { KMWString.enableSupplementaryPlane(true); - var target = new Mock(smpApple, 2); - var original = Mock.from(target); + var target = new SyntheticTextStore(smpApple, 2); + var original = SyntheticTextStore.from(target); target.setSelection(3); target.deleteCharsBeforeCaret(2); // "ale" @@ -283,8 +283,8 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. it("handles deletions around the caret with text insertion", function() { // We have other texts validating Mocks; by using them as our base 'element', this unit test file // could eventually run in 'headless' mode. - var target = new Mock("apple", 2); - var original = Mock.from(target); + var target = new SyntheticTextStore("apple", 2); + var original = SyntheticTextStore.from(target); target.setSelection(3); target.deleteCharsBeforeCaret(2); target.insertTextBeforeCaret("PP"); // "aPPle" @@ -300,8 +300,8 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. // CASE 2 - var target = new Mock("apple", 2); - var original = Mock.from(target); + var target = new SyntheticTextStore("apple", 2); + var original = SyntheticTextStore.from(target); target.setSelection(4); target.deleteCharsBeforeCaret(3); target.insertTextBeforeCaret("P"); // "aPe" @@ -317,8 +317,8 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. // CASE 3 - var target = new Mock("apple", 2); - var original = Mock.from(target); + var target = new SyntheticTextStore("apple", 2); + var original = SyntheticTextStore.from(target); target.setSelection(4); target.deleteCharsBeforeCaret(3); target.insertTextBeforeCaret("aaaaaaaaaaaaaa"); // "aaaaaaaaaaaaaaae" @@ -334,8 +334,8 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. // CASE 4 - var target = new Mock("apple", 2); - var original = Mock.from(target); + var target = new SyntheticTextStore("apple", 2); + var original = SyntheticTextStore.from(target); target.setSelection(5); target.deleteCharsBeforeCaret(4); target.insertTextBeforeCaret("les"); // "ales" - since we've appended a letter at the very end, the whole right-hand is indeed an insertion. @@ -357,9 +357,9 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. // We have other texts validating Mocks; by using them as our base 'element', this unit test file // could eventually run in 'headless' mode. - var target = new Mock(smpApple, 2); + var target = new SyntheticTextStore(smpApple, 2); let smpLE = u(0x1d5c5)+u(0x1d5be); - var original = Mock.from(target); + var original = SyntheticTextStore.from(target); target.setSelection(3); target.deleteCharsBeforeCaret(2); target.insertTextBeforeCaret(smpLE); // "alele" @@ -377,9 +377,9 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. // CASE 2 - var target = new Mock(smpApple, 2); + var target = new SyntheticTextStore(smpApple, 2); let smpB = u(0x1d5bb); - var original = Mock.from(target); + var original = SyntheticTextStore.from(target); target.setSelection(4); target.deleteCharsBeforeCaret(3); target.insertTextBeforeCaret(smpB); // "aPe" @@ -395,8 +395,8 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. // CASE 3 - var target = new Mock(smpApple, 2); - var original = Mock.from(target); + var target = new SyntheticTextStore(smpApple, 2); + var original = SyntheticTextStore.from(target); target.setSelection(4); target.deleteCharsBeforeCaret(3); target.insertTextBeforeCaret("aaaaaaaaaaaaaa"); // "aaaaaaaaaaaaaaae" @@ -412,9 +412,9 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. // CASE 4 - var target = new Mock(smpApple, 2); + var target = new SyntheticTextStore(smpApple, 2); let smpLES = u(0x1d5c5)+u(0x1d5be)+u(0x1d5cb); - var original = Mock.from(target); + var original = SyntheticTextStore.from(target); target.setSelection(5); target.deleteCharsBeforeCaret(4); target.insertTextBeforeCaret(smpLES); // "ales" - since we've appended a letter at the very end, the whole right-hand is indeed an insertion. @@ -435,9 +435,9 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. it('from targets with existing selection', () => { // | | - const target = new Mock("testing testing one two three"); + const target = new SyntheticTextStore("testing testing one two three"); target.setSelection(8, 20) - const original = Mock.from(target); + const original = SyntheticTextStore.from(target); target.clearSelection(); const transform = target.buildTransformFrom(original); @@ -451,7 +451,7 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. it('to targets with existing selection', () => { // | | - const target = new Mock("testing testing one two three"); + const target = new SyntheticTextStore("testing testing one two three"); target.setSelection(8, 20) const transform = { insert: '', @@ -471,8 +471,8 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. // We have other texts validating Mocks; by using them as our base 'element', this unit test file // could eventually run in 'headless' mode. - var target = new Mock("apple"); - var original = Mock.from(target); + var target = new SyntheticTextStore("apple"); + var original = SyntheticTextStore.from(target); target.setSelection(4); target.insertDeadkeyBeforeCaret(0); @@ -481,7 +481,7 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. target.setSelection(2); target.insertDeadkeyBeforeCaret(2); // 'a' dk(1) 'p' dk(2) | 'p' 'l' dk(0) 'e' - var original = Mock.from(target); + var original = SyntheticTextStore.from(target); target.hasDeadkeyMatch(0, 2); target.deadkeys().deleteMatched(); diff --git a/web/src/test/auto/headless/engine/keyboard/keyboard-loading.tests.js b/web/src/test/auto/headless/engine/keyboard/keyboard-loading.tests.js index e789f6bf51..d9dd5e7414 100644 --- a/web/src/test/auto/headless/engine/keyboard/keyboard-loading.tests.js +++ b/web/src/test/auto/headless/engine/keyboard/keyboard-loading.tests.js @@ -3,7 +3,7 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardHarness, MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { KeyboardHarness, MinimalKeymanGlobal, SyntheticTextStore } from 'keyman/engine/keyboard'; import { JSKeyboardInterface } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; @@ -61,7 +61,7 @@ describe('Headless keyboard loading', function() { let ruleHarness = new JSKeyboardInterface({}, MinimalKeymanGlobal); ruleHarness.activeKeyboard = keyboard; try { - ruleHarness.processKeystroke(new Mock(), keyboard.constructNullKeyEvent(device)); + ruleHarness.processKeystroke(new SyntheticTextStore(), keyboard.constructNullKeyEvent(device)); assert.fail(); } catch (err) { // Drives home an important detail: the 'global' object is effectively diff --git a/web/src/test/auto/headless/engine/keyboard/keyboardLoaderBase.tests.ts b/web/src/test/auto/headless/engine/keyboard/keyboardLoaderBase.tests.ts index 232293fbc2..e7f7ecd6d3 100644 --- a/web/src/test/auto/headless/engine/keyboard/keyboardLoaderBase.tests.ts +++ b/web/src/test/auto/headless/engine/keyboard/keyboardLoaderBase.tests.ts @@ -3,7 +3,7 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardHarness, MinimalKeymanGlobal, KeyboardDownloadError, DeviceSpec, InvalidKeyboardError, JSKeyboard, Mock } from 'keyman/engine/keyboard'; +import { KeyboardHarness, MinimalKeymanGlobal, KeyboardDownloadError, DeviceSpec, InvalidKeyboardError, JSKeyboard, SyntheticTextStore } from 'keyman/engine/keyboard'; import { JSKeyboardInterface } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { assertThrowsAsync, assertThrows } from 'keyman/tools/testing/test-utils'; @@ -84,7 +84,7 @@ describe('Headless keyboard loading', function() { // 'sandboxed' keyboard loading in the DOM!) const ruleHarness = new JSKeyboardInterface({}, MinimalKeymanGlobal); ruleHarness.activeKeyboard = keyboard as JSKeyboard; - assertThrows(() => ruleHarness.processKeystroke(new Mock(), (keyboard as JSKeyboard).constructNullKeyEvent(device)), 'k.KKM is not a function'); + assertThrows(() => ruleHarness.processKeystroke(new SyntheticTextStore(), (keyboard as JSKeyboard).constructNullKeyEvent(device)), 'k.KKM is not a function'); }); }); }); diff --git a/web/src/test/auto/headless/engine/keyboard/mocks.tests.js b/web/src/test/auto/headless/engine/keyboard/mocks.tests.js index b02f259886..eaf0f37a3b 100644 --- a/web/src/test/auto/headless/engine/keyboard/mocks.tests.js +++ b/web/src/test/auto/headless/engine/keyboard/mocks.tests.js @@ -1,13 +1,13 @@ import { assert } from 'chai'; -import { Mock } from 'keyman/engine/keyboard'; +import { SyntheticTextStore } from 'keyman/engine/keyboard'; describe('Mocks', function() { describe('app|les', () => { - const testMock = new Mock('apples', 3); + const testMock = new SyntheticTextStore('apples', 3); it('Cloning with .from()', () => { - assert.deepEqual(Mock.from(testMock), testMock); - assert.notStrictEqual(Mock.from(testMock), testMock); + assert.deepEqual(SyntheticTextStore.from(testMock), testMock); + assert.notStrictEqual(SyntheticTextStore.from(testMock), testMock); }); it('getText', () => { @@ -31,7 +31,7 @@ describe('Mocks', function() { }); it('clearSelection', () => { - let editMock = Mock.from(testMock); + let editMock = SyntheticTextStore.from(testMock); editMock.clearSelection(); assert.equal(editMock.getText(), testMock.getTextBeforeCaret() + testMock.getTextAfterCaret()); @@ -39,7 +39,7 @@ describe('Mocks', function() { assert.equal(editMock.getTextAfterCaret(), testMock.getTextAfterCaret()); assert.isTrue(editMock.isSelectionEmpty()); - let postClear = Mock.from(editMock); + let postClear = SyntheticTextStore.from(editMock); editMock.clearSelection(); // on same object; make sure its internal selection stuff updates correctly! assert.notStrictEqual(postClear, editMock); assert.deepEqual(postClear, editMock); @@ -47,11 +47,11 @@ describe('Mocks', function() { }); describe('app|les and ba|nanas', () => { // selection = 'les and ba' - const testMock = new Mock('apples and bananas', 3, 13); + const testMock = new SyntheticTextStore('apples and bananas', 3, 13); it('Cloning with from()', () => { - assert.deepEqual(Mock.from(testMock), testMock); - assert.notStrictEqual(Mock.from(testMock), testMock); + assert.deepEqual(SyntheticTextStore.from(testMock), testMock); + assert.notStrictEqual(SyntheticTextStore.from(testMock), testMock); }); it('getText', () => { @@ -75,7 +75,7 @@ describe('Mocks', function() { }); it('clearSelection', () => { - let editMock = Mock.from(testMock); + let editMock = SyntheticTextStore.from(testMock); editMock.clearSelection(); assert.equal(editMock.getText(), testMock.getTextBeforeCaret() + testMock.getTextAfterCaret()); @@ -83,7 +83,7 @@ describe('Mocks', function() { assert.equal(editMock.getTextAfterCaret(), testMock.getTextAfterCaret()); assert.isTrue(editMock.isSelectionEmpty()); - let postClear = Mock.from(editMock); + let postClear = SyntheticTextStore.from(editMock); editMock.clearSelection(); // on same object; make sure its internal selection stuff updates correctly! assert.notStrictEqual(postClear, editMock); assert.deepEqual(postClear, editMock); diff --git a/web/src/test/auto/headless/engine/main/headless/inputProcessor.tests.js b/web/src/test/auto/headless/engine/main/headless/inputProcessor.tests.js index d5b8eb1d86..01cfce2780 100644 --- a/web/src/test/auto/headless/engine/main/headless/inputProcessor.tests.js +++ b/web/src/test/auto/headless/engine/main/headless/inputProcessor.tests.js @@ -6,7 +6,7 @@ const require = createRequire(import.meta.url); import { InputProcessor } from 'keyman/engine/main'; import { JSKeyboardInterface } from 'keyman/engine/js-processor'; -import { MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { MinimalKeymanGlobal, SyntheticTextStore } from 'keyman/engine/keyboard'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { KeyboardTest } from '@keymanapp/recorder-core'; @@ -104,7 +104,7 @@ describe('InputProcessor', function() { it('with minimal context (no fat-fingers)', function() { this.timeout(32); // ms let core = new InputProcessor(device); - let context = new Mock("", 0); + let context = new SyntheticTextStore("", 0); core.keyboardProcessor.keyboardInterface = keyboardWithHarness; let keyboard = keyboardWithHarness.activeKeyboard; @@ -118,7 +118,7 @@ describe('InputProcessor', function() { it('with extremely long context (' + KMWString.length(coreSourceCode) + ' chars, no fat-fingers)', function() { // Assumes no SMP chars in the source, which is fine. - let context = new Mock(coreSourceCode, KMWString.length(coreSourceCode)); + let context = new SyntheticTextStore(coreSourceCode, KMWString.length(coreSourceCode)); this.timeout(500); // 500 ms, excluding text import. // These often run on VMs, so we'll be a bit generous. @@ -141,7 +141,7 @@ describe('InputProcessor', function() { it('with minimal context (with fat-fingers)', function() { this.timeout(32); // ms let core = new InputProcessor(device); - let context = new Mock("", 0); + let context = new SyntheticTextStore("", 0); core.keyboardProcessor.keyboardInterface = keyboardWithHarness; let keyboard = keyboardWithHarness.activeKeyboard; @@ -156,7 +156,7 @@ describe('InputProcessor', function() { it('with extremely long context (' + KMWString.length(coreSourceCode) + ' chars, with fat-fingers)', function() { // Assumes no SMP chars in the source, which is fine. - let context = new Mock(coreSourceCode, KMWString.length(coreSourceCode)); + let context = new SyntheticTextStore(coreSourceCode, KMWString.length(coreSourceCode)); this.timeout(500); // 500 ms, excluding text import. // These often run on VMs, so we'll be a bit generous. @@ -202,7 +202,7 @@ describe('InputProcessor', function() { it(testSet.msg ?? 'test', function() { this.timeout(32); // ms let core = new InputProcessor(device); - let context = new Mock("", 0); + let context = new SyntheticTextStore("", 0); core.keyboardProcessor.keyboardInterface = keyboardWithHarness; let keyboard = keyboardWithHarness.activeKeyboard; diff --git a/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js b/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js index 4fc2b0d9bd..a8b524f6b2 100644 --- a/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js +++ b/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js @@ -2,7 +2,7 @@ import { assert } from 'chai'; import { LanguageProcessor, TranscriptionCache } from 'keyman/engine/main'; import { SourcemappedWorker as LMWorker } from "@keymanapp/lexical-model-layer/node"; -import { Mock } from 'keyman/engine/keyboard'; +import { SyntheticTextStore } from 'keyman/engine/keyboard'; /* * Unit tests for the Dummy prediction model. @@ -110,7 +110,7 @@ describe('LanguageProcessor', function() { }); it("generates the expected prediction set", function(done) { - let contextSource = new Mock("li", 2); + let contextSource = new SyntheticTextStore("li", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); languageProcessor.loadModel(modelSpec).then(function() { @@ -147,7 +147,7 @@ describe('LanguageProcessor', function() { describe("does not alter casing when input is lowercased", function() { it("when input is fully lowercased", function(done) { - let contextSource = new Mock("li", 2); + let contextSource = new SyntheticTextStore("li", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); languageProcessor.loadModel(modelSpec).then(function() { @@ -164,7 +164,7 @@ describe('LanguageProcessor', function() { }); it("when input has non-initial uppercased letters", function(done) { - let contextSource = new Mock("lI", 2); + let contextSource = new SyntheticTextStore("lI", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); languageProcessor.loadModel(modelSpec).then(function() { @@ -182,7 +182,7 @@ describe('LanguageProcessor', function() { }); it("unless the suggestion has uppercased letters", function(done) { - let contextSource = new Mock("i", 1); + let contextSource = new SyntheticTextStore("i", 1); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); languageProcessor.loadModel(modelSpec).then(function() { @@ -201,7 +201,7 @@ describe('LanguageProcessor', function() { describe("uppercases suggestions when input is fully capitalized ", function() { it("for suggestions with default casing (== 'lower')", function(done) { - let contextSource = new Mock("LI", 2); + let contextSource = new SyntheticTextStore("LI", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); languageProcessor.loadModel(modelSpec).then(function() { @@ -219,7 +219,7 @@ describe('LanguageProcessor', function() { }); it("for precapitalized suggestions", function(done) { - let contextSource = new Mock("I", 1); + let contextSource = new SyntheticTextStore("I", 1); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); languageProcessor.loadModel(modelSpec).then(function() { @@ -239,7 +239,7 @@ describe('LanguageProcessor', function() { describe("initial-cases suggestions when input uses initial casing ", function() { describe("when input is a single capitalized letter", function() { it("for suggestions with default casing (== 'lower')", function(done) { - let contextSource = new Mock("L", 1); + let contextSource = new SyntheticTextStore("L", 1); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); languageProcessor.loadModel(modelSpec).then(function() { @@ -259,7 +259,7 @@ describe('LanguageProcessor', function() { describe("input length > 1", function() { it("for suggestions with default casing (== 'lower')", function(done) { - let contextSource = new Mock("Li", 2); + let contextSource = new SyntheticTextStore("Li", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); languageProcessor.loadModel(modelSpec).then(function() { diff --git a/web/src/tools/testing/recorder-core/src/index.ts b/web/src/tools/testing/recorder-core/src/index.ts index b1073d09cc..f74639f207 100644 --- a/web/src/tools/testing/recorder-core/src/index.ts +++ b/web/src/tools/testing/recorder-core/src/index.ts @@ -1,4 +1,4 @@ -import { KeyDistribution, KeyEvent, type TextStore, Mock } from "keyman/engine/keyboard"; +import { KeyDistribution, KeyEvent, type TextStore, SyntheticTextStore } from "keyman/engine/keyboard"; import Proctor from "./proctor.js"; @@ -219,7 +219,7 @@ export abstract class TestSequence { // Start with an empty TextStore and a fresh KeyboardProcessor. if(!target) { - target = new Mock(); + target = new SyntheticTextStore(); } proctor.before(); diff --git a/web/src/tools/testing/recorder-core/src/nodeProctor.ts b/web/src/tools/testing/recorder-core/src/nodeProctor.ts index 2bf5cc427a..9297e45e84 100644 --- a/web/src/tools/testing/recorder-core/src/nodeProctor.ts +++ b/web/src/tools/testing/recorder-core/src/nodeProctor.ts @@ -8,7 +8,7 @@ import { RecordedSyntheticKeystroke } from "./index.js"; -import { KeyEvent, KeyEventSpec, KeyboardHarness, Mock, TextStore } from "keyman/engine/keyboard"; +import { KeyEvent, KeyEventSpec, KeyboardHarness, SyntheticTextStore, TextStore } from "keyman/engine/keyboard"; import { DeviceSpec } from "@keymanapp/web-utils"; import { JSKeyboardInterface, JSKeyboardProcessor } from 'keyman/engine/js-processor'; @@ -51,7 +51,7 @@ export default class NodeProctor extends Proctor { async simulateSequence(sequence: TestSequence, target?: TextStore): Promise { // Start with an empty TextStore and a fresh KeyboardProcessor. if(!target) { - target = new Mock(); + target = new SyntheticTextStore(); } // Establish a fresh processor, setting its keyboard appropriately for the test. From 22a93cd3f6ea94b411e71453ae294c70e4f5e3fa Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 4 Nov 2025 14:39:40 +0100 Subject: [PATCH 06/17] =?UTF-8?q?refactor(web):=20rename=20directory=20`el?= =?UTF-8?q?ement-wrappers`=20=E2=86=92=20`element-text-stores`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also rename module from element-wrappers → element-text-stores. --- web/README.md | 2 +- web/build.sh | 92 +++++++++---------- web/package.json | 8 +- web/src/app/browser/src/beepHandler.ts | 2 +- web/src/app/browser/src/configuration.ts | 2 +- web/src/app/browser/src/contextManager.ts | 2 +- .../app/browser/src/hardwareEventKeyboard.ts | 2 +- web/src/app/browser/src/keyboardInterface.ts | 2 +- web/src/app/browser/src/keymanEngine.ts | 2 +- web/src/engine/attachment/build.sh | 2 +- .../engine/attachment/src/attachmentInfo.ts | 2 +- web/src/engine/attachment/src/index.ts | 4 +- .../attachment/src/outputTargetForElement.ts | 2 +- .../attachment/src/pageContextAttachment.ts | 2 +- web/src/engine/attachment/tsconfig.json | 2 +- .../build.sh | 2 +- .../readme.md | 2 +- .../src/contentEditable.ts | 0 .../src/designIFrame.ts | 0 .../src/index.ts | 0 .../src/input.ts | 0 .../src/outputTargetElementWrapper.ts | 0 .../src/readme.md | 0 .../src/textarea.ts | 0 .../src/utils.ts | 0 .../src/wrapElement.ts | 0 .../tsconfig.json | 4 +- .../element_interfaces.tests.ts | 2 +- .../target_mocks.tests.ts | 2 +- .../test/auto/dom/web-test-runner.config.mjs | 4 +- 30 files changed, 72 insertions(+), 72 deletions(-) rename web/src/engine/{element-wrappers => element-text-stores}/build.sh (97%) rename web/src/engine/{element-wrappers => element-text-stores}/readme.md (86%) rename web/src/engine/{element-wrappers => element-text-stores}/src/contentEditable.ts (100%) rename web/src/engine/{element-wrappers => element-text-stores}/src/designIFrame.ts (100%) rename web/src/engine/{element-wrappers => element-text-stores}/src/index.ts (100%) rename web/src/engine/{element-wrappers => element-text-stores}/src/input.ts (100%) rename web/src/engine/{element-wrappers => element-text-stores}/src/outputTargetElementWrapper.ts (100%) rename web/src/engine/{element-wrappers => element-text-stores}/src/readme.md (100%) rename web/src/engine/{element-wrappers => element-text-stores}/src/textarea.ts (100%) rename web/src/engine/{element-wrappers => element-text-stores}/src/utils.ts (100%) rename web/src/engine/{element-wrappers => element-text-stores}/src/wrapElement.ts (100%) rename web/src/engine/{element-wrappers => element-text-stores}/tsconfig.json (56%) rename web/src/test/auto/dom/cases/{element-wrappers => element-text-stores}/element_interfaces.tests.ts (99%) rename web/src/test/auto/dom/cases/{element-wrappers => element-text-stores}/target_mocks.tests.ts (98%) diff --git a/web/README.md b/web/README.md index 5417309067..1a8129a7b8 100644 --- a/web/README.md +++ b/web/README.md @@ -117,7 +117,7 @@ graph TD; subgraph ClassicWeb["`**ClassicWeb** Intermediate-level engine modules`"] - Elements["/web/src/engine/element-wrappers"]; + Elements["/web/src/engine/element-text-stores"]; Elements-->JSProc; KeyboardStorage["/web/src/engine/keyboard-storage"]; KeyboardStorage-->Interfaces; diff --git a/web/build.sh b/web/build.sh index 5c9b54647d..8f2c7984ed 100755 --- a/web/build.sh +++ b/web/build.sh @@ -22,28 +22,28 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \ "build" \ "start Starts the test server" \ "test" \ - "coverage Create an HTML page with code coverage" \ - ":app/browser The form of Keyman Engine for Web for use on websites" \ - ":app/webview A puppetable version of KMW designed for use in a host app's WebView" \ - ":app/ui Builds KMW's desktop form-factor keyboard-selection UI modules" \ - ":engine/attachment Subset used for detecting valid page contexts for use in text editing " \ - ":engine/core-processor Keyman Core WASM integration" \ - ":engine/common/web-utils Low-level, headless utility methods and classes used across multiple modules" \ - ":engine/dom-utils A common subset of function used for DOM calculations, layout, etc" \ - ":engine/events Specialized classes utilized to support KMW API events" \ - ":engine/element-wrappers Subset used to integrate with website elements" \ - ":engine/interfaces Subset used to configure KMW" \ - ":engine/js-processor Build JS processor for KMW" \ - ":engine/keyboard Builds KMW's keyboard-loading and caching code" \ - ":engine/keyboard-storage Subset used to collate keyboards and request them from the cloud" \ - ":engine/main Builds all common code used by KMW's app/-level targets" \ - ":engine/osk Builds the Web OSK module" \ - ":engine/predictive-text Builds KMW's predictive text module" \ - ":help Online documentation" \ - ":samples Builds all needed resources for the KMW sample-page set" \ - ":tools Builds engine-related development resources" \ - ":test-pages=src/test/manual Builds resources needed for the KMW manual testing pages" \ - ":_all (Meta build target used when targets are not specified)" + "coverage Create an HTML page with code coverage" \ + ":app/browser The form of Keyman Engine for Web for use on websites" \ + ":app/webview A puppetable version of KMW designed for use in a host app's WebView" \ + ":app/ui Builds KMW's desktop form-factor keyboard-selection UI modules" \ + ":engine/attachment Subset used for detecting valid page contexts for use in text editing " \ + ":engine/core-processor Keyman Core WASM integration" \ + ":engine/common/web-utils Low-level, headless utility methods and classes used across multiple modules" \ + ":engine/dom-utils A common subset of function used for DOM calculations, layout, etc" \ + ":engine/events Specialized classes utilized to support KMW API events" \ + ":engine/element-text-stores Subset used to integrate with website elements" \ + ":engine/interfaces Subset used to configure KMW" \ + ":engine/js-processor Build JS processor for KMW" \ + ":engine/keyboard Builds KMW's keyboard-loading and caching code" \ + ":engine/keyboard-storage Subset used to collate keyboards and request them from the cloud" \ + ":engine/main Builds all common code used by KMW's app/-level targets" \ + ":engine/osk Builds the Web OSK module" \ + ":engine/predictive-text Builds KMW's predictive text module" \ + ":help Online documentation" \ + ":samples Builds all needed resources for the KMW sample-page set" \ + ":tools Builds engine-related development resources" \ + ":test-pages=src/test/manual Builds resources needed for the KMW manual testing pages" \ + ":_all (Meta build target used when targets are not specified)" # Possible TODO? # "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \ @@ -56,27 +56,27 @@ if builder_is_debug_build; then fi builder_describe_outputs \ - configure "/node_modules" \ - build "/web/build/test/dom/cases/attachment/outputTargetForElement.tests.html" \ - build:app/browser "/web/build/app/browser/lib/index.mjs" \ - build:app/webview "/web/build/app/webview/${config}/keymanweb-webview.js" \ - build:app/ui "/web/build/app/ui/${config}/kmwuitoggle.js" \ - build:engine/attachment "/web/build/engine/attachment/lib/index.mjs" \ - build:engine/core-processor "/web/build/engine/core-processor/lib/index.mjs" \ - build:engine/dom-utils "/web/build/engine/dom-utils/obj/index.js" \ - build:engine/events "/web/build/engine/events/lib/index.mjs" \ - build:engine/element-wrappers "/web/build/engine/element-wrappers/lib/index.mjs" \ - build:engine/interfaces "/web/build/engine/interfaces/lib/index.mjs" \ - build:engine/js-processor "/web/build/engine/js-processor/lib/index.mjs" \ - build:engine/keyboard "/web/build/engine/keyboard/lib/index.mjs" \ - build:engine/keyboard-storage "/web/build/engine/keyboard-storage/lib/index.mjs" \ - build:engine/main "/web/build/engine/main/lib/index.mjs" \ - build:engine/osk "/web/build/engine/osk/lib/index.mjs" \ - build:engine/predictive-text "/web/src/engine/predictive-text/worker-main/build/lib/web/index.mjs" \ - build:engine/common/web-utils "/web/src/engine/common/web-utils/build/lib/index.mjs" \ - build:samples "/web/src/samples/simplest/keymanweb.js" \ - build:tools "/web/build/tools/building/sourcemap-root/index.js" \ - build:test-pages "/web/build/test-resources/sentry-manager.js" + configure "/node_modules" \ + build "/web/build/test/dom/cases/attachment/outputTargetForElement.tests.html" \ + build:app/browser "/web/build/app/browser/lib/index.mjs" \ + build:app/webview "/web/build/app/webview/${config}/keymanweb-webview.js" \ + build:app/ui "/web/build/app/ui/${config}/kmwuitoggle.js" \ + build:engine/attachment "/web/build/engine/attachment/lib/index.mjs" \ + build:engine/core-processor "/web/build/engine/core-processor/lib/index.mjs" \ + build:engine/dom-utils "/web/build/engine/dom-utils/obj/index.js" \ + build:engine/events "/web/build/engine/events/lib/index.mjs" \ + build:engine/element-text-stores "/web/build/engine/element-text-stores/lib/index.mjs" \ + build:engine/interfaces "/web/build/engine/interfaces/lib/index.mjs" \ + build:engine/js-processor "/web/build/engine/js-processor/lib/index.mjs" \ + build:engine/keyboard "/web/build/engine/keyboard/lib/index.mjs" \ + build:engine/keyboard-storage "/web/build/engine/keyboard-storage/lib/index.mjs" \ + build:engine/main "/web/build/engine/main/lib/index.mjs" \ + build:engine/osk "/web/build/engine/osk/lib/index.mjs" \ + build:engine/predictive-text "/web/src/engine/predictive-text/worker-main/build/lib/web/index.mjs" \ + build:engine/common/web-utils "/web/src/engine/common/web-utils/build/lib/index.mjs" \ + build:samples "/web/src/samples/simplest/keymanweb.js" \ + build:tools "/web/build/tools/building/sourcemap-root/index.js" \ + build:test-pages "/web/build/test-resources/sentry-manager.js" BUNDLE_CMD="node ${KEYMAN_ROOT}/web/src/tools/es-bundling/build/common-bundle.mjs" @@ -169,14 +169,14 @@ builder_run_child_actions build:engine/dom-utils builder_run_child_actions build:engine/keyboard builder_run_child_actions build:engine/js-processor -builder_run_child_actions build:engine/element-wrappers +builder_run_child_actions build:engine/element-text-stores builder_run_child_actions build:engine/events builder_run_child_actions build:engine/interfaces # Uses engine/dom-utils and engine/interfaces builder_run_child_actions build:engine/osk -# Uses engine/element-wrappers +# Uses engine/element-text-stores builder_run_child_actions build:engine/attachment # Uses engine/interfaces (due to resource-path config interface) @@ -191,7 +191,7 @@ builder_run_child_actions build:engine/core-processor # Uses engine/interfaces, engine/keyboard-storage, & engine/osk builder_run_child_actions build:engine/main -# Uses all but engine/element-wrappers and engine/attachment +# Uses all but engine/element-text-stores and engine/attachment builder_run_child_actions build:app/webview # Uses literally everything `engine/` above diff --git a/web/package.json b/web/package.json index 5d3f9be6f6..3506c5e9f9 100644 --- a/web/package.json +++ b/web/package.json @@ -22,10 +22,10 @@ "types": "./build/engine/dom-utils/obj/index.d.ts", "import": "./build/engine/dom-utils/obj/index.js" }, - "./engine/element-wrappers": { - "es6-bundling": "./src/engine/element-wrappers/src/index.ts", - "types": "./build/engine/element-wrappers/obj/index.d.ts", - "import": "./build/engine/element-wrappers/obj/index.js" + "./engine/element-text-stores": { + "es6-bundling": "./src/engine/element-text-stores/src/index.ts", + "types": "./build/engine/element-text-stores/obj/index.d.ts", + "import": "./build/engine/element-text-stores/obj/index.js" }, "./engine/events": { "es6-bundling": "./src/engine/events/src/index.ts", diff --git a/web/src/app/browser/src/beepHandler.ts b/web/src/app/browser/src/beepHandler.ts index 05fb6516bb..51d036eaf7 100644 --- a/web/src/app/browser/src/beepHandler.ts +++ b/web/src/app/browser/src/beepHandler.ts @@ -1,6 +1,6 @@ import { type JSKeyboardInterface } from 'keyman/engine/js-processor'; import { JSKeyboard, type KeyboardMinimalInterface } from 'keyman/engine/keyboard'; -import { DesignIFrame, OutputTargetElementWrapper } from 'keyman/engine/element-wrappers'; +import { DesignIFrame, OutputTargetElementWrapper } from 'keyman/engine/element-text-stores'; // Utility object used to handle beep (keyboard error response) operations. class BeepData { diff --git a/web/src/app/browser/src/configuration.ts b/web/src/app/browser/src/configuration.ts index 60f033a358..9910f358ec 100644 --- a/web/src/app/browser/src/configuration.ts +++ b/web/src/app/browser/src/configuration.ts @@ -1,6 +1,6 @@ import { EngineConfiguration, InitOptionSpec, InitOptionDefaults } from "keyman/engine/main"; -import { OutputTargetElementWrapper as DOMOutputTarget } from 'keyman/engine/element-wrappers'; +import { OutputTargetElementWrapper as DOMOutputTarget } from 'keyman/engine/element-text-stores'; import { TextStore, ProcessorAction } from 'keyman/engine/keyboard'; import { isEmptyTransform } from '@keymanapp/web-utils'; import { AlertHost } from "./utils/alertHost.js"; diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index fb7e5ef92c..f570769f9f 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -3,7 +3,7 @@ import { type KeyboardStub } from 'keyman/engine/keyboard-storage'; import { CookieSerializer } from 'keyman/engine/dom-utils'; import { eventOutputTarget, outputTargetForElement, PageContextAttachment } from 'keyman/engine/attachment'; import { DomEventTracker, LegacyEventEmitter } from 'keyman/engine/events'; -import { DesignIFrame, OutputTargetElementWrapper, nestedInstanceOf } from 'keyman/engine/element-wrappers'; +import { DesignIFrame, OutputTargetElementWrapper, nestedInstanceOf } from 'keyman/engine/element-text-stores'; import { ContextManagerBase, type KeyboardInterfaceBase, diff --git a/web/src/app/browser/src/hardwareEventKeyboard.ts b/web/src/app/browser/src/hardwareEventKeyboard.ts index c25497aae3..c3e63760fb 100644 --- a/web/src/app/browser/src/hardwareEventKeyboard.ts +++ b/web/src/app/browser/src/hardwareEventKeyboard.ts @@ -4,7 +4,7 @@ import { ModifierKeyConstants } from '@keymanapp/common-types'; import { HardKeyboardBase, processForMnemonicsAndLegacy } from 'keyman/engine/main'; import { DomEventTracker } from 'keyman/engine/events'; -import { DesignIFrame, nestedInstanceOf } from 'keyman/engine/element-wrappers'; +import { DesignIFrame, nestedInstanceOf } from 'keyman/engine/element-text-stores'; import { eventOutputTarget, outputTargetForElement } from 'keyman/engine/attachment'; import ContextManager from './contextManager.js'; diff --git a/web/src/app/browser/src/keyboardInterface.ts b/web/src/app/browser/src/keyboardInterface.ts index 69b1bc7447..7783929964 100644 --- a/web/src/app/browser/src/keyboardInterface.ts +++ b/web/src/app/browser/src/keyboardInterface.ts @@ -1,4 +1,4 @@ -import { type OutputTargetElementWrapper } from 'keyman/engine/element-wrappers'; +import { type OutputTargetElementWrapper } from 'keyman/engine/element-text-stores'; import { FloatingOSKView } from 'keyman/engine/osk'; import { KeyboardInterfaceBase } from 'keyman/engine/main'; diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 237eaf7b4e..8173aeeff6 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -1,7 +1,7 @@ import { KeymanWebKeyboard } from '@keymanapp/common-types'; import { KeymanEngineBase, DeviceDetector } from 'keyman/engine/main'; import { getAbsoluteY } from 'keyman/engine/dom-utils'; -import { OutputTargetElementWrapper } from 'keyman/engine/element-wrappers'; +import { OutputTargetElementWrapper } from 'keyman/engine/element-text-stores'; import { TwoStateActivator, VisualKeyboard diff --git a/web/src/engine/attachment/build.sh b/web/src/engine/attachment/build.sh index 042c5aa83e..6294fcb41a 100755 --- a/web/src/engine/attachment/build.sh +++ b/web/src/engine/attachment/build.sh @@ -16,7 +16,7 @@ SUBPROJECT_NAME=engine/attachment builder_describe "Builds the Keyman Engine for Web (KMW) attachment engine." \ "@/web/src/engine/dom-utils" \ - "@/web/src/engine/element-wrappers" \ + "@/web/src/engine/element-text-stores" \ "clean" \ "configure" \ "build" \ diff --git a/web/src/engine/attachment/src/attachmentInfo.ts b/web/src/engine/attachment/src/attachmentInfo.ts index 05811300cb..d3d447802d 100644 --- a/web/src/engine/attachment/src/attachmentInfo.ts +++ b/web/src/engine/attachment/src/attachmentInfo.ts @@ -1,4 +1,4 @@ -import { OutputTargetElementWrapper } from 'keyman/engine/element-wrappers'; +import { OutputTargetElementWrapper } from 'keyman/engine/element-text-stores'; export class AttachmentInfo { /** diff --git a/web/src/engine/attachment/src/index.ts b/web/src/engine/attachment/src/index.ts index 8e75e7645f..cb0fc51871 100644 --- a/web/src/engine/attachment/src/index.ts +++ b/web/src/engine/attachment/src/index.ts @@ -9,7 +9,7 @@ export { eventOutputTarget, outputTargetForElement } from './outputTargetForElem export { PageContextAttachment, PageAttachmentOptions } from './pageContextAttachment.js'; /* - * Following from the prior "Note:", we republish `engine/element-wrappers` here - + * Following from the prior "Note:", we republish `engine/element-text-stores` here - * this matters quite strongly for certain unit tests. */ -export * from 'keyman/engine/element-wrappers'; \ No newline at end of file +export * from 'keyman/engine/element-text-stores'; \ No newline at end of file diff --git a/web/src/engine/attachment/src/outputTargetForElement.ts b/web/src/engine/attachment/src/outputTargetForElement.ts index 2dee5327c9..10bb5ac5b0 100644 --- a/web/src/engine/attachment/src/outputTargetForElement.ts +++ b/web/src/engine/attachment/src/outputTargetForElement.ts @@ -1,4 +1,4 @@ -import { nestedInstanceOf } from "keyman/engine/element-wrappers"; +import { nestedInstanceOf } from "keyman/engine/element-text-stores"; /** * Given a DOM event related to an KMW-attached element, this function determines diff --git a/web/src/engine/attachment/src/pageContextAttachment.ts b/web/src/engine/attachment/src/pageContextAttachment.ts index 7923298578..c75e04b828 100644 --- a/web/src/engine/attachment/src/pageContextAttachment.ts +++ b/web/src/engine/attachment/src/pageContextAttachment.ts @@ -1,7 +1,7 @@ import { EventEmitter } from 'eventemitter3'; import { DeviceSpec, InternalKeyboardFont } from "keyman/engine/keyboard"; -import { Input, nestedInstanceOf, wrapElement } from "keyman/engine/element-wrappers"; +import { Input, nestedInstanceOf, wrapElement } from "keyman/engine/element-text-stores"; import { arrayFromNodeList, createStyleSheet, diff --git a/web/src/engine/attachment/tsconfig.json b/web/src/engine/attachment/tsconfig.json index 3ce6a07308..2c32c333c5 100644 --- a/web/src/engine/attachment/tsconfig.json +++ b/web/src/engine/attachment/tsconfig.json @@ -12,6 +12,6 @@ "references": [ { "path": "../dom-utils" }, - { "path": "../element-wrappers" } + { "path": "../element-text-stores" } ] } diff --git a/web/src/engine/element-wrappers/build.sh b/web/src/engine/element-text-stores/build.sh similarity index 97% rename from web/src/engine/element-wrappers/build.sh rename to web/src/engine/element-text-stores/build.sh index 3e653280dd..d2833fd78e 100755 --- a/web/src/engine/element-wrappers/build.sh +++ b/web/src/engine/element-text-stores/build.sh @@ -6,7 +6,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/builder-full.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE -SUBPROJECT_NAME=engine/element-wrappers +SUBPROJECT_NAME=engine/element-text-stores . "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" diff --git a/web/src/engine/element-wrappers/readme.md b/web/src/engine/element-text-stores/readme.md similarity index 86% rename from web/src/engine/element-wrappers/readme.md rename to web/src/engine/element-text-stores/readme.md index 2a7685edbc..f178b184be 100644 --- a/web/src/engine/element-wrappers/readme.md +++ b/web/src/engine/element-text-stores/readme.md @@ -1,4 +1,4 @@ -## engine/element-wrappers +## engine/element-text-stores This submodule provides a subset of the main engine's Web-oriented code that's used to 'wrap' webpage elements as part of KMW attachment and interface the element with the `keyboard` submodule. \ No newline at end of file diff --git a/web/src/engine/element-wrappers/src/contentEditable.ts b/web/src/engine/element-text-stores/src/contentEditable.ts similarity index 100% rename from web/src/engine/element-wrappers/src/contentEditable.ts rename to web/src/engine/element-text-stores/src/contentEditable.ts diff --git a/web/src/engine/element-wrappers/src/designIFrame.ts b/web/src/engine/element-text-stores/src/designIFrame.ts similarity index 100% rename from web/src/engine/element-wrappers/src/designIFrame.ts rename to web/src/engine/element-text-stores/src/designIFrame.ts diff --git a/web/src/engine/element-wrappers/src/index.ts b/web/src/engine/element-text-stores/src/index.ts similarity index 100% rename from web/src/engine/element-wrappers/src/index.ts rename to web/src/engine/element-text-stores/src/index.ts diff --git a/web/src/engine/element-wrappers/src/input.ts b/web/src/engine/element-text-stores/src/input.ts similarity index 100% rename from web/src/engine/element-wrappers/src/input.ts rename to web/src/engine/element-text-stores/src/input.ts diff --git a/web/src/engine/element-wrappers/src/outputTargetElementWrapper.ts b/web/src/engine/element-text-stores/src/outputTargetElementWrapper.ts similarity index 100% rename from web/src/engine/element-wrappers/src/outputTargetElementWrapper.ts rename to web/src/engine/element-text-stores/src/outputTargetElementWrapper.ts diff --git a/web/src/engine/element-wrappers/src/readme.md b/web/src/engine/element-text-stores/src/readme.md similarity index 100% rename from web/src/engine/element-wrappers/src/readme.md rename to web/src/engine/element-text-stores/src/readme.md diff --git a/web/src/engine/element-wrappers/src/textarea.ts b/web/src/engine/element-text-stores/src/textarea.ts similarity index 100% rename from web/src/engine/element-wrappers/src/textarea.ts rename to web/src/engine/element-text-stores/src/textarea.ts diff --git a/web/src/engine/element-wrappers/src/utils.ts b/web/src/engine/element-text-stores/src/utils.ts similarity index 100% rename from web/src/engine/element-wrappers/src/utils.ts rename to web/src/engine/element-text-stores/src/utils.ts diff --git a/web/src/engine/element-wrappers/src/wrapElement.ts b/web/src/engine/element-text-stores/src/wrapElement.ts similarity index 100% rename from web/src/engine/element-wrappers/src/wrapElement.ts rename to web/src/engine/element-text-stores/src/wrapElement.ts diff --git a/web/src/engine/element-wrappers/tsconfig.json b/web/src/engine/element-text-stores/tsconfig.json similarity index 56% rename from web/src/engine/element-wrappers/tsconfig.json rename to web/src/engine/element-text-stores/tsconfig.json index 15a58bbf2a..32b9ecd1e3 100644 --- a/web/src/engine/element-wrappers/tsconfig.json +++ b/web/src/engine/element-text-stores/tsconfig.json @@ -3,8 +3,8 @@ "compilerOptions": { "baseUrl": "./", - "outDir": "../../../build/engine/element-wrappers/obj/", - "tsBuildInfoFile": "../../../build/engine/element-wrappers/obj/tsconfig.tsbuildinfo", + "outDir": "../../../build/engine/element-text-stores/obj/", + "tsBuildInfoFile": "../../../build/engine/element-text-stores/obj/tsconfig.tsbuildinfo", "rootDir": "./src" }, diff --git a/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.tests.ts b/web/src/test/auto/dom/cases/element-text-stores/element_interfaces.tests.ts similarity index 99% rename from web/src/test/auto/dom/cases/element-wrappers/element_interfaces.tests.ts rename to web/src/test/auto/dom/cases/element-text-stores/element_interfaces.tests.ts index aeaf007833..43b6b03df4 100644 --- a/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.tests.ts +++ b/web/src/test/auto/dom/cases/element-text-stores/element_interfaces.tests.ts @@ -1,7 +1,7 @@ import { assert } from 'chai'; import { KMWString, SyntheticTextStore } from 'keyman/engine/keyboard'; -import * as wrappers from 'keyman/engine/element-wrappers'; +import * as wrappers from 'keyman/engine/element-text-stores'; import { DynamicElements } from '../../test_utils.js'; import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs'; diff --git a/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts b/web/src/test/auto/dom/cases/element-text-stores/target_mocks.tests.ts similarity index 98% rename from web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts rename to web/src/test/auto/dom/cases/element-text-stores/target_mocks.tests.ts index ea4ac07707..ea5cb7c731 100644 --- a/web/src/test/auto/dom/cases/element-wrappers/target_mocks.tests.ts +++ b/web/src/test/auto/dom/cases/element-text-stores/target_mocks.tests.ts @@ -1,7 +1,7 @@ import { assert } from 'chai'; import { KMWString, SyntheticTextStore } from 'keyman/engine/keyboard'; -import { Input } from 'keyman/engine/element-wrappers'; +import { Input } from 'keyman/engine/element-text-stores'; import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs'; diff --git a/web/src/test/auto/dom/web-test-runner.config.mjs b/web/src/test/auto/dom/web-test-runner.config.mjs index 6ac9d46c85..f1acf47d85 100644 --- a/web/src/test/auto/dom/web-test-runner.config.mjs +++ b/web/src/test/auto/dom/web-test-runner.config.mjs @@ -69,9 +69,9 @@ export default { files: ['web/build/test/dom/cases/dom-utils/**/*.tests.mjs'] }, { - name: 'engine/element-wrappers', + name: 'engine/element-text-stores', // Relative, from the containing package.json - files: ['web/build/test/dom/cases/element-wrappers/**/*.tests.mjs'] + files: ['web/build/test/dom/cases/element-text-stores/**/*.tests.mjs'] }, { name: 'engine/gesture-processor', From e4d6ae9123a0c0b788a94f2c922fdfc2c025f774 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 4 Nov 2025 15:01:18 +0100 Subject: [PATCH 07/17] =?UTF-8?q?refactor(web):=20rename=20`OutputTargetEl?= =?UTF-8?q?ementWrapper`=20=E2=86=92=20`AbstractElementTextStore`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/app/browser/src/beepHandler.ts | 6 ++--- web/src/app/browser/src/configuration.ts | 2 +- web/src/app/browser/src/contextManager.ts | 26 +++++++++---------- web/src/app/browser/src/keyboardInterface.ts | 4 +-- web/src/app/browser/src/keymanEngine.ts | 4 +-- .../engine/attachment/src/attachmentInfo.ts | 6 ++--- .../src/contentEditable.ts | 4 +-- .../element-text-stores/src/designIFrame.ts | 4 +-- .../engine/element-text-stores/src/index.ts | 2 +- .../engine/element-text-stores/src/input.ts | 4 +-- .../src/outputTargetElementWrapper.ts | 2 +- .../element-text-stores/src/textarea.ts | 4 +-- .../element-text-stores/src/wrapElement.ts | 4 +-- 13 files changed, 36 insertions(+), 36 deletions(-) diff --git a/web/src/app/browser/src/beepHandler.ts b/web/src/app/browser/src/beepHandler.ts index 51d036eaf7..8264bd7fe9 100644 --- a/web/src/app/browser/src/beepHandler.ts +++ b/web/src/app/browser/src/beepHandler.ts @@ -1,6 +1,6 @@ import { type JSKeyboardInterface } from 'keyman/engine/js-processor'; import { JSKeyboard, type KeyboardMinimalInterface } from 'keyman/engine/keyboard'; -import { DesignIFrame, OutputTargetElementWrapper } from 'keyman/engine/element-text-stores'; +import { DesignIFrame, AbstractElementTextStore } from 'keyman/engine/element-text-stores'; // Utility object used to handle beep (keyboard error response) operations. class BeepData { @@ -33,8 +33,8 @@ export class BeepHandler { * @param {Object} Pelem element to flash * Description Flash body as substitute for audible beep; notify embedded device to vibrate */ - beep(textStore: OutputTargetElementWrapper) { - if (!(textStore instanceof OutputTargetElementWrapper)) { + beep(textStore: AbstractElementTextStore) { + if (!(textStore instanceof AbstractElementTextStore)) { return; } diff --git a/web/src/app/browser/src/configuration.ts b/web/src/app/browser/src/configuration.ts index 9910f358ec..d6b73dd013 100644 --- a/web/src/app/browser/src/configuration.ts +++ b/web/src/app/browser/src/configuration.ts @@ -1,6 +1,6 @@ import { EngineConfiguration, InitOptionSpec, InitOptionDefaults } from "keyman/engine/main"; -import { OutputTargetElementWrapper as DOMOutputTarget } from 'keyman/engine/element-text-stores'; +import { AbstractElementTextStore as DOMOutputTarget } from 'keyman/engine/element-text-stores'; import { TextStore, ProcessorAction } from 'keyman/engine/keyboard'; import { isEmptyTransform } from '@keymanapp/web-utils'; import { AlertHost } from "./utils/alertHost.js"; diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index f570769f9f..58b2cef50e 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -3,7 +3,7 @@ import { type KeyboardStub } from 'keyman/engine/keyboard-storage'; import { CookieSerializer } from 'keyman/engine/dom-utils'; import { eventOutputTarget, outputTargetForElement, PageContextAttachment } from 'keyman/engine/attachment'; import { DomEventTracker, LegacyEventEmitter } from 'keyman/engine/events'; -import { DesignIFrame, OutputTargetElementWrapper, nestedInstanceOf } from 'keyman/engine/element-text-stores'; +import { DesignIFrame, AbstractElementTextStore, nestedInstanceOf } from 'keyman/engine/element-text-stores'; import { ContextManagerBase, type KeyboardInterfaceBase, @@ -47,8 +47,8 @@ export default class ContextManager extends ContextManagerBase('KeymanWeb_Keyboard'); readonly focusAssistant = new FocusAssistant(() => this.activeTarget?.isForcingScroll()); readonly page: PageContextAttachment; - private mostRecentTarget: OutputTargetElementWrapper; - private currentTarget: OutputTargetElementWrapper; + private mostRecentTarget: AbstractElementTextStore; + private currentTarget: AbstractElementTextStore; private globalKeyboard: {keyboard: JSKeyboard, metadata: KeyboardStub}; @@ -175,7 +175,7 @@ export default class ContextManager extends ContextManagerBase { + get activeTarget(): AbstractElementTextStore { /* * Assumption: the maintainingFocus flag may only be set when there is a current target. * This is not enforced proactively at present, but the assumption should hold. (2023-05-03) @@ -184,7 +184,7 @@ export default class ContextManager extends ContextManagerBase { + get lastActiveTarget(): AbstractElementTextStore { return this.mostRecentTarget; } @@ -229,7 +229,7 @@ export default class ContextManager extends ContextManagerBase, sendEvents?: boolean) { + public setActiveTarget(target: AbstractElementTextStore, sendEvents?: boolean) { const previousTarget = this.mostRecentTarget; const originalTarget = this.activeTarget; // may differ, depending on focus state. @@ -369,7 +369,7 @@ export default class ContextManager extends ContextManagerBase { + protected currentKeyboardSrcTarget(): AbstractElementTextStore { const target = this.currentTarget || this.mostRecentTarget; if(this.isTargetKeyboardIndependent(target)) { @@ -379,7 +379,7 @@ export default class ContextManager extends ContextManagerBase): boolean { + private isTargetKeyboardIndependent(target: AbstractElementTextStore): boolean { const attachmentInfo = target?.getElement()._kmwAttachment; // If null or undefined, we're in 'global' mode. @@ -387,7 +387,7 @@ export default class ContextManager extends ContextManagerBase) { + activateKeyboardForTarget(kbd: { keyboard: JSKeyboard, metadata: KeyboardStub }, target: AbstractElementTextStore) { const attachment = target?.getElement()._kmwAttachment; if(!attachment) { @@ -421,7 +421,7 @@ export default class ContextManager extends ContextManagerBase, kbdId: string, langId: string) { + public setKeyboardForTarget(target: AbstractElementTextStore, kbdId: string, langId: string) { if(target instanceof DesignIFrame) { console.warn("'keymanweb.setKeyboardForControl' cannot set keyboard on iframes."); return; @@ -456,7 +456,7 @@ export default class ContextManager extends ContextManagerBase) { + public getKeyboardStubForTarget(target: AbstractElementTextStore) { if(!this.isTargetKeyboardIndependent(target)) { return this.globalKeyboard.metadata; } else { @@ -614,7 +614,7 @@ export default class ContextManager extends ContextManagerBase): boolean { + _CommonFocusHelper(textStore: AbstractElementTextStore): boolean { const focusAssistant = this.focusAssistant; const activeKeyboard = this.activeKeyboard?.keyboard; @@ -738,7 +738,7 @@ export default class ContextManager extends ContextManagerBase) { + doChangeEvent(target: AbstractElementTextStore) { if(target.changed) { const event = new Event('change', {"bubbles": true, "cancelable": false}); target.getElement().dispatchEvent(event); diff --git a/web/src/app/browser/src/keyboardInterface.ts b/web/src/app/browser/src/keyboardInterface.ts index 7783929964..ded3a1d71c 100644 --- a/web/src/app/browser/src/keyboardInterface.ts +++ b/web/src/app/browser/src/keyboardInterface.ts @@ -1,4 +1,4 @@ -import { type OutputTargetElementWrapper } from 'keyman/engine/element-text-stores'; +import { type AbstractElementTextStore } from 'keyman/engine/element-text-stores'; import { FloatingOSKView } from 'keyman/engine/osk'; import { KeyboardInterfaceBase } from 'keyman/engine/main'; @@ -29,7 +29,7 @@ export class KeyboardInterface extends KeyboardInterfaceBase { /** * Legacy entry points (non-standard names)- included only to allow existing IME keyboards to continue to be used */ - getLastActiveElement(): OutputTargetElementWrapper { + getLastActiveElement(): AbstractElementTextStore { return this.engine.contextManager.lastActiveTarget; } diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 8173aeeff6..45166e563e 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -1,7 +1,7 @@ import { KeymanWebKeyboard } from '@keymanapp/common-types'; import { KeymanEngineBase, DeviceDetector } from 'keyman/engine/main'; import { getAbsoluteY } from 'keyman/engine/dom-utils'; -import { OutputTargetElementWrapper } from 'keyman/engine/element-text-stores'; +import { AbstractElementTextStore } from 'keyman/engine/element-text-stores'; import { TwoStateActivator, VisualKeyboard @@ -76,7 +76,7 @@ export class KeymanEngine extends KeymanEngineBase { - const e = (target as OutputTargetElementWrapper)?.getElement(); + const e = (target as AbstractElementTextStore)?.getElement(); if(this.osk) { (this.osk.activationModel as TwoStateActivator).activationTrigger = e; } diff --git a/web/src/engine/attachment/src/attachmentInfo.ts b/web/src/engine/attachment/src/attachmentInfo.ts index d3d447802d..4b3042ab2a 100644 --- a/web/src/engine/attachment/src/attachmentInfo.ts +++ b/web/src/engine/attachment/src/attachmentInfo.ts @@ -1,10 +1,10 @@ -import { OutputTargetElementWrapper } from 'keyman/engine/element-text-stores'; +import { AbstractElementTextStore } from 'keyman/engine/element-text-stores'; export class AttachmentInfo { /** * Provides the core interface between the DOM and the actual keyboard. */ - interface: OutputTargetElementWrapper; + interface: AbstractElementTextStore; /** * Tracks the control's independent keyboard selection, when applicable. @@ -21,7 +21,7 @@ export class AttachmentInfo { */ inputMode?: string; - constructor(eleInterface: OutputTargetElementWrapper, kbd: string, touch?: boolean) { + constructor(eleInterface: AbstractElementTextStore, kbd: string, touch?: boolean) { this.interface = eleInterface; this.keyboard = kbd; } diff --git a/web/src/engine/element-text-stores/src/contentEditable.ts b/web/src/engine/element-text-stores/src/contentEditable.ts index 5b3169bc1a..ae52eca86c 100644 --- a/web/src/engine/element-text-stores/src/contentEditable.ts +++ b/web/src/engine/element-text-stores/src/contentEditable.ts @@ -1,4 +1,4 @@ -import { OutputTargetElementWrapper } from './outputTargetElementWrapper.js'; +import { AbstractElementTextStore } from './outputTargetElementWrapper.js'; import { KMWString } from '@keymanapp/web-utils'; class SelectionCaret { @@ -21,7 +21,7 @@ class SelectionRange { } } -export class ContentEditable extends OutputTargetElementWrapper<{}> { +export class ContentEditable extends AbstractElementTextStore<{}> { root: HTMLElement; constructor(ele: HTMLElement) { diff --git a/web/src/engine/element-text-stores/src/designIFrame.ts b/web/src/engine/element-text-stores/src/designIFrame.ts index edf9b450cf..701a49fdf2 100644 --- a/web/src/engine/element-text-stores/src/designIFrame.ts +++ b/web/src/engine/element-text-stores/src/designIFrame.ts @@ -1,4 +1,4 @@ -import { OutputTargetElementWrapper } from './outputTargetElementWrapper.js'; +import { AbstractElementTextStore } from './outputTargetElementWrapper.js'; import { KMWString } from '@keymanapp/web-utils'; class SelectionCaret { @@ -32,7 +32,7 @@ class StyleCommand { } } -export class DesignIFrame extends OutputTargetElementWrapper<{}> { +export class DesignIFrame extends AbstractElementTextStore<{}> { root: HTMLIFrameElement; doc: Document; docRoot: HTMLElement; diff --git a/web/src/engine/element-text-stores/src/index.ts b/web/src/engine/element-text-stores/src/index.ts index 1cf3e36ecf..c5b5264314 100644 --- a/web/src/engine/element-text-stores/src/index.ts +++ b/web/src/engine/element-text-stores/src/index.ts @@ -2,6 +2,6 @@ export { wrapElement } from './wrapElement.js'; export { ContentEditable } from './contentEditable.js'; export { DesignIFrame } from './designIFrame.js'; export { Input } from './input.js'; -export { OutputTargetElementWrapper } from './outputTargetElementWrapper.js'; +export { AbstractElementTextStore } from './outputTargetElementWrapper.js'; export { TextArea } from './textarea.js'; export { nestedInstanceOf } from './utils.js'; \ No newline at end of file diff --git a/web/src/engine/element-text-stores/src/input.ts b/web/src/engine/element-text-stores/src/input.ts index cae3b43398..5666ad6f9a 100644 --- a/web/src/engine/element-text-stores/src/input.ts +++ b/web/src/engine/element-text-stores/src/input.ts @@ -1,4 +1,4 @@ -import { OutputTargetElementWrapper } from './outputTargetElementWrapper.js'; +import { AbstractElementTextStore } from './outputTargetElementWrapper.js'; import { KMWString } from '@keymanapp/web-utils'; interface EventMap { @@ -22,7 +22,7 @@ interface EventMap { 'unhandlednewline': (element: HTMLInputElement) => void } -export class Input extends OutputTargetElementWrapper { +export class Input extends AbstractElementTextStore { root: HTMLInputElement; /** diff --git a/web/src/engine/element-text-stores/src/outputTargetElementWrapper.ts b/web/src/engine/element-text-stores/src/outputTargetElementWrapper.ts index 316704d51d..e22d6588ae 100644 --- a/web/src/engine/element-text-stores/src/outputTargetElementWrapper.ts +++ b/web/src/engine/element-text-stores/src/outputTargetElementWrapper.ts @@ -1,7 +1,7 @@ import { TextStore } from "keyman/engine/keyboard"; import { EventEmitter } from 'eventemitter3'; -export abstract class OutputTargetElementWrapper extends TextStore { +export abstract class AbstractElementTextStore extends TextStore { // JS/TS can't do true multiple inheritance, so we maintain class events on a readonly field. public readonly events: EventEmitter = new EventEmitter(); diff --git a/web/src/engine/element-text-stores/src/textarea.ts b/web/src/engine/element-text-stores/src/textarea.ts index eeb86fbd82..e8dc6e9f52 100644 --- a/web/src/engine/element-text-stores/src/textarea.ts +++ b/web/src/engine/element-text-stores/src/textarea.ts @@ -1,7 +1,7 @@ -import { OutputTargetElementWrapper } from './outputTargetElementWrapper.js'; +import { AbstractElementTextStore } from './outputTargetElementWrapper.js'; import { KMWString } from '@keymanapp/web-utils'; -export class TextArea extends OutputTargetElementWrapper<{}> { +export class TextArea extends AbstractElementTextStore<{}> { root: HTMLTextAreaElement; /** diff --git a/web/src/engine/element-text-stores/src/wrapElement.ts b/web/src/engine/element-text-stores/src/wrapElement.ts index dac09b30b6..56eb006894 100644 --- a/web/src/engine/element-text-stores/src/wrapElement.ts +++ b/web/src/engine/element-text-stores/src/wrapElement.ts @@ -1,11 +1,11 @@ -import { type OutputTargetElementWrapper } from './outputTargetElementWrapper.js'; +import { type AbstractElementTextStore } from './outputTargetElementWrapper.js'; import { Input } from './input.js'; import { TextArea } from './textarea.js'; import { DesignIFrame } from './designIFrame.js'; import { ContentEditable } from './contentEditable.js'; import { nestedInstanceOf } from './utils.js'; -export function wrapElement(e: HTMLElement): OutputTargetElementWrapper { +export function wrapElement(e: HTMLElement): AbstractElementTextStore { // Complex type scoping is implemented here so that kmwutils.ts is not a dependency for test compilations. if(nestedInstanceOf(e, "HTMLInputElement")) { From f096be98480f4d386fd161e5c06d8f0aba238880 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 4 Nov 2025 15:28:45 +0100 Subject: [PATCH 08/17] =?UTF-8?q?refactor(web):=20rename=20element=20wrapp?= =?UTF-8?q?er=20classes=20=E2=86=92=20*ElementTextStore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/app/browser/src/beepHandler.ts | 4 +- web/src/app/browser/src/contextManager.ts | 14 +- .../app/browser/src/hardwareEventKeyboard.ts | 6 +- .../attachment/src/pageContextAttachment.ts | 6 +- ....ts => contentEditableElementTextStore.ts} | 2 +- ...ame.ts => designIFrameElementTextStore.ts} | 2 +- .../engine/element-text-stores/src/index.ts | 8 +- .../{input.ts => inputElementTextStore.ts} | 2 +- ...extarea.ts => textAreaElementTextStore.ts} | 2 +- .../element-text-stores/src/wrapElement.ts | 18 +- .../attachment/outputTargetForElement.def.ts | 56 +++---- .../dom/cases/browser/contextManager.tests.ts | 10 +- .../element_interfaces.tests.ts | 158 +++++++++--------- .../element-text-stores/target_mocks.tests.ts | 6 +- 14 files changed, 147 insertions(+), 147 deletions(-) rename web/src/engine/element-text-stores/src/{contentEditable.ts => contentEditableElementTextStore.ts} (98%) rename web/src/engine/element-text-stores/src/{designIFrame.ts => designIFrameElementTextStore.ts} (99%) rename web/src/engine/element-text-stores/src/{input.ts => inputElementTextStore.ts} (98%) rename web/src/engine/element-text-stores/src/{textarea.ts => textAreaElementTextStore.ts} (98%) diff --git a/web/src/app/browser/src/beepHandler.ts b/web/src/app/browser/src/beepHandler.ts index 8264bd7fe9..76936c220c 100644 --- a/web/src/app/browser/src/beepHandler.ts +++ b/web/src/app/browser/src/beepHandler.ts @@ -1,6 +1,6 @@ import { type JSKeyboardInterface } from 'keyman/engine/js-processor'; import { JSKeyboard, type KeyboardMinimalInterface } from 'keyman/engine/keyboard'; -import { DesignIFrame, AbstractElementTextStore } from 'keyman/engine/element-text-stores'; +import { DesignIFrameElementTextStore, AbstractElementTextStore } from 'keyman/engine/element-text-stores'; // Utility object used to handle beep (keyboard error response) operations. class BeepData { @@ -40,7 +40,7 @@ export class BeepHandler { // All code after this point is DOM-based, triggered by the beep. let Pelem: HTMLElement = textStore.getElement(); - if(textStore instanceof DesignIFrame) { + if(textStore instanceof DesignIFrameElementTextStore) { Pelem = textStore.docRoot; // I1446 - beep sometimes fails to flash when using OSK and rich control } diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 58b2cef50e..5cee5fc523 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -3,7 +3,7 @@ import { type KeyboardStub } from 'keyman/engine/keyboard-storage'; import { CookieSerializer } from 'keyman/engine/dom-utils'; import { eventOutputTarget, outputTargetForElement, PageContextAttachment } from 'keyman/engine/attachment'; import { DomEventTracker, LegacyEventEmitter } from 'keyman/engine/events'; -import { DesignIFrame, AbstractElementTextStore, nestedInstanceOf } from 'keyman/engine/element-text-stores'; +import { DesignIFrameElementTextStore, AbstractElementTextStore, nestedInstanceOf } from 'keyman/engine/element-text-stores'; import { ContextManagerBase, type KeyboardInterfaceBase, @@ -92,7 +92,7 @@ export default class ContextManager extends ContextManagerBase { - if(!(elem._kmwAttachment.interface instanceof DesignIFrame)) { + if(!(elem._kmwAttachment.interface instanceof DesignIFrameElementTextStore)) { // For anything attached but (design-mode) iframes... // This block: has to do with maintaining focus. @@ -261,7 +261,7 @@ export default class ContextManager extends ContextManagerBase, kbdId: string, langId: string) { - if(target instanceof DesignIFrame) { + if(target instanceof DesignIFrameElementTextStore) { console.warn("'keymanweb.setKeyboardForControl' cannot set keyboard on iframes."); return; } @@ -650,7 +650,7 @@ export default class ContextManager extends ContextManagerBase { const target = outputTargetForElement(Pelem); - if(!(target instanceof DesignIFrame)) { + if(!(target instanceof DesignIFrameElementTextStore)) { // These need to be on the actual input element, as otherwise the keyboard will disappear on touch. eventTracker.attachDOMEvent(Pelem, 'keypress', this._KeyPress); eventTracker.attachDOMEvent(Pelem, 'keydown', this._KeyDown); @@ -253,7 +253,7 @@ export default class HardwareEventKeyboard extends HardKeyboardBase { page.on('disabled', (Pelem) => { const target = outputTargetForElement(Pelem); - if(!(target instanceof DesignIFrame)) { + if(!(target instanceof DesignIFrameElementTextStore)) { eventTracker.detachDOMEvent(Pelem, 'keypress', this._KeyPress); eventTracker.detachDOMEvent(Pelem, 'keydown', this._KeyDown); eventTracker.detachDOMEvent(Pelem, 'keyup', this._KeyUp); diff --git a/web/src/engine/attachment/src/pageContextAttachment.ts b/web/src/engine/attachment/src/pageContextAttachment.ts index c75e04b828..cc67291189 100644 --- a/web/src/engine/attachment/src/pageContextAttachment.ts +++ b/web/src/engine/attachment/src/pageContextAttachment.ts @@ -1,7 +1,7 @@ import { EventEmitter } from 'eventemitter3'; import { DeviceSpec, InternalKeyboardFont } from "keyman/engine/keyboard"; -import { Input, nestedInstanceOf, wrapElement } from "keyman/engine/element-text-stores"; +import { InputElementTextStore, nestedInstanceOf, wrapElement } from "keyman/engine/element-text-stores"; import { arrayFromNodeList, createStyleSheet, @@ -236,7 +236,7 @@ export class PageContextAttachment extends EventEmitter { if(x instanceof x.ownerDocument.defaultView.HTMLTextAreaElement) { return true; } else if(x instanceof x.ownerDocument.defaultView.HTMLInputElement) { - if (Input.isSupportedType(x.type)) { + if (InputElementTextStore.isSupportedType(x.type)) { return true; } } else if(x instanceof x.ownerDocument.defaultView.HTMLIFrameElement) { @@ -734,7 +734,7 @@ export class PageContextAttachment extends EventEmitter { const t2=document.getElementsByTagName('textarea'); for(let i=0; i { +export class ContentEditableElementTextStore extends AbstractElementTextStore<{}> { root: HTMLElement; constructor(ele: HTMLElement) { diff --git a/web/src/engine/element-text-stores/src/designIFrame.ts b/web/src/engine/element-text-stores/src/designIFrameElementTextStore.ts similarity index 99% rename from web/src/engine/element-text-stores/src/designIFrame.ts rename to web/src/engine/element-text-stores/src/designIFrameElementTextStore.ts index 701a49fdf2..65ffa46835 100644 --- a/web/src/engine/element-text-stores/src/designIFrame.ts +++ b/web/src/engine/element-text-stores/src/designIFrameElementTextStore.ts @@ -32,7 +32,7 @@ class StyleCommand { } } -export class DesignIFrame extends AbstractElementTextStore<{}> { +export class DesignIFrameElementTextStore extends AbstractElementTextStore<{}> { root: HTMLIFrameElement; doc: Document; docRoot: HTMLElement; diff --git a/web/src/engine/element-text-stores/src/index.ts b/web/src/engine/element-text-stores/src/index.ts index c5b5264314..fab6323bce 100644 --- a/web/src/engine/element-text-stores/src/index.ts +++ b/web/src/engine/element-text-stores/src/index.ts @@ -1,7 +1,7 @@ export { wrapElement } from './wrapElement.js'; -export { ContentEditable } from './contentEditable.js'; -export { DesignIFrame } from './designIFrame.js'; -export { Input } from './input.js'; +export { ContentEditableElementTextStore } from './contentEditableElementTextStore.js'; +export { DesignIFrameElementTextStore } from './designIFrameElementTextStore.js'; +export { InputElementTextStore } from './inputElementTextStore.js'; export { AbstractElementTextStore } from './outputTargetElementWrapper.js'; -export { TextArea } from './textarea.js'; +export { TextAreaElementTextStore } from './textAreaElementTextStore.js'; export { nestedInstanceOf } from './utils.js'; \ No newline at end of file diff --git a/web/src/engine/element-text-stores/src/input.ts b/web/src/engine/element-text-stores/src/inputElementTextStore.ts similarity index 98% rename from web/src/engine/element-text-stores/src/input.ts rename to web/src/engine/element-text-stores/src/inputElementTextStore.ts index 5666ad6f9a..bd86e75e05 100644 --- a/web/src/engine/element-text-stores/src/input.ts +++ b/web/src/engine/element-text-stores/src/inputElementTextStore.ts @@ -22,7 +22,7 @@ interface EventMap { 'unhandlednewline': (element: HTMLInputElement) => void } -export class Input extends AbstractElementTextStore { +export class InputElementTextStore extends AbstractElementTextStore { root: HTMLInputElement; /** diff --git a/web/src/engine/element-text-stores/src/textarea.ts b/web/src/engine/element-text-stores/src/textAreaElementTextStore.ts similarity index 98% rename from web/src/engine/element-text-stores/src/textarea.ts rename to web/src/engine/element-text-stores/src/textAreaElementTextStore.ts index e8dc6e9f52..46b753b262 100644 --- a/web/src/engine/element-text-stores/src/textarea.ts +++ b/web/src/engine/element-text-stores/src/textAreaElementTextStore.ts @@ -1,7 +1,7 @@ import { AbstractElementTextStore } from './outputTargetElementWrapper.js'; import { KMWString } from '@keymanapp/web-utils'; -export class TextArea extends AbstractElementTextStore<{}> { +export class TextAreaElementTextStore extends AbstractElementTextStore<{}> { root: HTMLTextAreaElement; /** diff --git a/web/src/engine/element-text-stores/src/wrapElement.ts b/web/src/engine/element-text-stores/src/wrapElement.ts index 56eb006894..a4dbe01964 100644 --- a/web/src/engine/element-text-stores/src/wrapElement.ts +++ b/web/src/engine/element-text-stores/src/wrapElement.ts @@ -1,30 +1,30 @@ import { type AbstractElementTextStore } from './outputTargetElementWrapper.js'; -import { Input } from './input.js'; -import { TextArea } from './textarea.js'; -import { DesignIFrame } from './designIFrame.js'; -import { ContentEditable } from './contentEditable.js'; +import { InputElementTextStore } from './inputElementTextStore.js'; +import { TextAreaElementTextStore } from './textAreaElementTextStore.js'; +import { DesignIFrameElementTextStore } from './designIFrameElementTextStore.js'; +import { ContentEditableElementTextStore } from './contentEditableElementTextStore.js'; import { nestedInstanceOf } from './utils.js'; export function wrapElement(e: HTMLElement): AbstractElementTextStore { // Complex type scoping is implemented here so that kmwutils.ts is not a dependency for test compilations. if(nestedInstanceOf(e, "HTMLInputElement")) { - return new Input( e); + return new InputElementTextStore( e); } else if(nestedInstanceOf(e, "HTMLTextAreaElement")) { - return new TextArea( e); + return new TextAreaElementTextStore( e); } else if(nestedInstanceOf(e, "HTMLIFrameElement")) { const iframe = e; if(iframe.contentWindow && iframe.contentWindow.document && iframe.contentWindow.document.designMode == "on") { - return new DesignIFrame(iframe); + return new DesignIFrameElementTextStore(iframe); } else if (e.isContentEditable) { // Do content-editable