diff --git a/web/README.md b/web/README.md index 1a30362e9b..7cd9e02f00 100644 --- a/web/README.md +++ b/web/README.md @@ -119,7 +119,7 @@ graph TD; Device["/web/src/engine/device-detect"]; Device----->WebUtils; Elements["/web/src/engine/element-wrappers"]; - Elements-->KP; + Elements-->JSProc; KeyboardCache["/web/src/engine/package-cache"]; KeyboardCache-->Interfaces; DomUtils["/web/src/engine/dom-utils"]; diff --git a/web/src/app/browser/src/configuration.ts b/web/src/app/browser/src/configuration.ts index 35983ac30c..df2153df82 100644 --- a/web/src/app/browser/src/configuration.ts +++ b/web/src/app/browser/src/configuration.ts @@ -1,8 +1,7 @@ import { EngineConfiguration, InitOptionSpec, InitOptionDefaults } from "keyman/engine/main"; import { OutputTarget as DOMOutputTarget } from 'keyman/engine/element-wrappers'; -import { isEmptyTransform, OutputTarget } from 'keyman/engine/keyboard'; -import { RuleBehavior } from 'keyman/engine/js-processor'; +import { isEmptyTransform, OutputTarget, RuleBehavior } from 'keyman/engine/js-processor'; import { AlertHost } from "./utils/alertHost.js"; import { whenDocumentReady } from "./utils/documentReady.js"; diff --git a/web/src/app/browser/src/defaultBrowserRules.ts b/web/src/app/browser/src/defaultBrowserRules.ts index 46f80664f7..4f0ffbe012 100644 --- a/web/src/app/browser/src/defaultBrowserRules.ts +++ b/web/src/app/browser/src/defaultBrowserRules.ts @@ -2,9 +2,9 @@ import { ModifierKeyConstants } from '@keymanapp/common-types'; import { Codes, DefaultRules, - type KeyEvent, - type OutputTarget + type KeyEvent } from 'keyman/engine/keyboard'; +import { type OutputTarget } from 'keyman/engine/js-processor'; import ContextManager from './contextManager.js'; diff --git a/web/src/app/webview/src/contextManager.ts b/web/src/app/webview/src/contextManager.ts index de41348142..2b242de39c 100644 --- a/web/src/app/webview/src/contextManager.ts +++ b/web/src/app/webview/src/contextManager.ts @@ -1,4 +1,5 @@ -import { type Keyboard, Mock, OutputTarget, Transcription, findCommonSubstringEndIndex, isEmptyTransform, TextTransform } from 'keyman/engine/keyboard'; +import { type Keyboard } from 'keyman/engine/keyboard'; +import { Mock, OutputTarget, Transcription, findCommonSubstringEndIndex, isEmptyTransform, TextTransform } from 'keyman/engine/js-processor'; import { KeyboardStub } from 'keyman/engine/package-cache'; import { ContextManagerBase } from 'keyman/engine/main'; import { WebviewConfiguration } from './configuration.js'; diff --git a/web/src/engine/element-wrappers/build.sh b/web/src/engine/element-wrappers/build.sh index 1d0f580213..e188c7dd41 100755 --- a/web/src/engine/element-wrappers/build.sh +++ b/web/src/engine/element-wrappers/build.sh @@ -13,7 +13,7 @@ SUBPROJECT_NAME=engine/element-wrappers # ################################ Main script ################################ builder_describe "Builds DOM-based OutputTarget subclasses used by the Keyman Engine for Web (KMW)." \ - "@/web/src/engine/keyboard" \ + "@/web/src/engine/js-processor" \ "clean" \ "configure" \ "build" \ diff --git a/web/src/engine/element-wrappers/src/outputTarget.ts b/web/src/engine/element-wrappers/src/outputTarget.ts index 5d0f2e38ce..43623af781 100644 --- a/web/src/engine/element-wrappers/src/outputTarget.ts +++ b/web/src/engine/element-wrappers/src/outputTarget.ts @@ -1,4 +1,4 @@ -import { OutputTarget as OutputTargetBase } from "keyman/engine/keyboard"; +import { OutputTarget as OutputTargetBase } from "keyman/engine/js-processor"; import { EventEmitter } from 'eventemitter3'; export default abstract class OutputTarget extends OutputTargetBase { diff --git a/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts b/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts index 84e7851987..df5ebae896 100644 --- a/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts +++ b/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts @@ -1,7 +1,7 @@ /// import { EventEmitter } from "eventemitter3"; -import { OutputTarget } from "keyman/engine/keyboard"; +import { OutputTarget } from "keyman/engine/js-processor"; export class ReadySuggestions { suggestions: Suggestion[]; diff --git a/web/src/engine/interfaces/src/prediction/predictionContext.ts b/web/src/engine/interfaces/src/prediction/predictionContext.ts index dabeb366da..221875e62c 100644 --- a/web/src/engine/interfaces/src/prediction/predictionContext.ts +++ b/web/src/engine/interfaces/src/prediction/predictionContext.ts @@ -1,7 +1,6 @@ import { EventEmitter } from "eventemitter3"; import { type LanguageProcessorSpec , ReadySuggestions, type InvalidateSourceEnum, StateChangeHandler } from './languageProcessor.interface.js'; -import { type OutputTarget } from "keyman/engine/keyboard"; -import { type KeyboardProcessor } from 'keyman/engine/js-processor'; +import { type KeyboardProcessor, type OutputTarget } from 'keyman/engine/js-processor'; interface PredictionContextEventMap { update: (suggestions: Suggestion[]) => void; diff --git a/web/src/engine/keyboard/src/deadkeys.ts b/web/src/engine/js-processor/src/deadkeys.ts similarity index 100% rename from web/src/engine/keyboard/src/deadkeys.ts rename to web/src/engine/js-processor/src/deadkeys.ts diff --git a/web/src/engine/js-processor/src/index.ts b/web/src/engine/js-processor/src/index.ts index f87a044bd7..1755499119 100644 --- a/web/src/engine/js-processor/src/index.ts +++ b/web/src/engine/js-processor/src/index.ts @@ -4,3 +4,8 @@ export { default as RuleBehavior } from "./ruleBehavior.js"; export * from './kbdInterface.js'; export { default as KeyboardInterface } from "./kbdInterface.js"; export * from "./systemStores.js"; +export * from "./deadkeys.js"; +export { default as OutputTarget } from "./outputTarget.js"; +export * from "./outputTarget.js"; +export { Mock } from "./mock.js"; +export * from "./stringDivergence.js"; diff --git a/web/src/engine/js-processor/src/kbdInterface.ts b/web/src/engine/js-processor/src/kbdInterface.ts index 73332dc8c8..b39b2baea2 100644 --- a/web/src/engine/js-processor/src/kbdInterface.ts +++ b/web/src/engine/js-processor/src/kbdInterface.ts @@ -7,7 +7,10 @@ import { type DeviceSpec } from "@keymanapp/web-utils"; import { ModifierKeyConstants } from '@keymanapp/common-types'; -import { Codes, type KeyEvent, type Deadkey, KeyMapping, type OutputTarget, Mock, Keyboard, KeyboardHarness, KeyboardKeymanGlobal, VariableStoreDictionary } from "keyman/engine/keyboard"; +import { Codes, type KeyEvent, KeyMapping, Keyboard, KeyboardHarness, KeyboardKeymanGlobal, VariableStoreDictionary } from "keyman/engine/keyboard"; +import type OutputTarget from './outputTarget.js'; +import { type Deadkey } from './deadkeys.js'; +import { Mock } from "./mock.js"; import RuleBehavior from "./ruleBehavior.js"; import { ComplexKeyboardStore, type KeyboardStore, KeyboardStoreElement, SystemStoreIDs, SystemStore, MutableSystemStore, PlatformSystemStore, VariableStore, VariableStoreSerializer } from "./systemStores.js"; diff --git a/web/src/engine/js-processor/src/keyboardProcessor.ts b/web/src/engine/js-processor/src/keyboardProcessor.ts index 861ecadb36..dea537dfdf 100644 --- a/web/src/engine/js-processor/src/keyboardProcessor.ts +++ b/web/src/engine/js-processor/src/keyboardProcessor.ts @@ -10,8 +10,10 @@ import { EventEmitter } from 'eventemitter3'; import { ModifierKeyConstants } from '@keymanapp/common-types'; import { Codes, type Keyboard, MinimalKeymanGlobal, KeyEvent, Layouts, - type OutputTarget, Mock, DefaultRules, EmulationKeystrokes + DefaultRules, EmulationKeystrokes } from "keyman/engine/keyboard"; +import { Mock } from "./mock.js"; +import type OutputTarget from "./outputTarget.js"; import RuleBehavior from "./ruleBehavior.js"; import KeyboardInterface from './kbdInterface.js'; import { DeviceSpec, globalObject } from "@keymanapp/web-utils"; diff --git a/web/src/engine/js-processor/src/mock.ts b/web/src/engine/js-processor/src/mock.ts new file mode 100644 index 0000000000..151d8313ca --- /dev/null +++ b/web/src/engine/js-processor/src/mock.ts @@ -0,0 +1,157 @@ +import OutputTarget from './outputTarget.js'; + +// Due to some interesting requirements on compile ordering in TS, +// this needs to be in the same file as OutputTarget now. +export class Mock extends OutputTarget { + text: string; + + selStart: number; + selEnd: number; + selForward: boolean = true; + + constructor(text?: string, caretPos?: number); + constructor(text?: string, selStart?: number, selEnd?: number); + constructor(text?: string, selStart?: number, selEnd?: number) { + super(); + + this.text = text ? text : ""; + var defaultLength = this.text._kmwLength(); + + // Ensures that `caretPos == 0` is handled correctly. + this.selStart = typeof selStart == "number" ? selStart : defaultLength; + + // If no selection-end is set, selection length is implied to be 0. + this.selEnd = typeof selEnd == "number" ? selEnd : this.selStart; + + this.selForward = this.selEnd >= this.selStart; + } + + // Clones the state of an existing EditableElement, creating a Mock version of its state. + static from(outputTarget: OutputTarget, readonly?: boolean) { + let clone: Mock; + + if (outputTarget instanceof Mock) { + // Avoids the need to run expensive kmwstring.ts / `_kmwLength()` + // calculations when deep-copying Mock instances. + let priorMock = outputTarget as Mock; + clone = new Mock(priorMock.text, priorMock.selStart, priorMock.selEnd); + } else { + const text = outputTarget.getText(); + const textLen = text._kmwLength(); + + // If !hasSelection() + let selectionStart: number = textLen; + let selectionEnd: number = 0; + + if (outputTarget.hasSelection()) { + let beforeText = outputTarget.getTextBeforeCaret(); + let afterText = outputTarget.getTextAfterCaret(); + selectionStart = beforeText._kmwLength(); + selectionEnd = textLen - afterText._kmwLength(); + } + + // readonly group or not, the returned Mock 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); + } + + // Also duplicate deadkey state! (Needed for fat-finger ops.) + clone.setDeadkeys(outputTarget.deadkeys()); + + return clone; + } + + clearSelection(): void { + this.text = this.getTextBeforeCaret() + this.getTextAfterCaret(); + this.selEnd = this.selStart; + this.selForward = true; + } + + invalidateSelection(): void { + return; + } + + isSelectionEmpty(): boolean { + return this.selStart == this.selEnd; + } + + hasSelection(): boolean { + return true; + } + + getDeadkeyCaret(): number { + return this.selStart; + } + + setSelection(start: number, end?: number) { + this.selStart = start; + this.selEnd = typeof end == 'number' ? end : start; + + this.selForward = end >= start; + if (!this.selForward) { + let temp = this.selStart; + this.selStart = this.selEnd; + this.selEnd = temp; + } + } + + getTextBeforeCaret(): string { + return this.text.kmwSubstr(0, this.selStart); + } + + getSelectedText(): string { + return this.text.kmwSubstr(this.selStart, this.selEnd - this.selStart); + } + + getTextAfterCaret(): string { + return this.text.kmwSubstr(this.selEnd); + } + + getText(): string { + return this.text; + } + + deleteCharsBeforeCaret(dn: number): void { + if (dn >= 0) { + if (dn > this.selStart) { + dn = this.selStart; + } + this.adjustDeadkeys(-dn); + this.text = this.text.kmwSubstr(0, this.selStart - dn) + this.text.kmwSubstr(this.selStart); + this.selStart -= dn; + this.selEnd -= dn; + } + } + + insertTextBeforeCaret(s: string): void { + this.adjustDeadkeys(s._kmwLength()); + this.text = this.getTextBeforeCaret() + s + this.text.kmwSubstr(this.selStart); + this.selStart += s.kmwLength(); + this.selEnd += s.kmwLength(); + } + + handleNewlineAtCaret(): void { + this.insertTextBeforeCaret('\n'); + } + + protected setTextAfterCaret(s: string): void { + this.text = this.getTextBeforeCaret() + s; + } + + /** + * Indicates if this Mock represents an identical context to that of another Mock. + * @param other + * @returns + */ + isEqual(other: Mock) { + return this.text == other.text + && this.selStart == other.selStart + && this.selEnd == other.selEnd + && this.deadkeys().equal(other.deadkeys()); + } + + doInputEvent() { + // Mock isn't backed by an element, so it won't have any event listeners. + } +} diff --git a/web/src/engine/keyboard/src/outputTarget.ts b/web/src/engine/js-processor/src/outputTarget.ts similarity index 69% rename from web/src/engine/keyboard/src/outputTarget.ts rename to web/src/engine/js-processor/src/outputTarget.ts index 63c4c8a042..f48647092c 100644 --- a/web/src/engine/keyboard/src/outputTarget.ts +++ b/web/src/engine/js-processor/src/outputTarget.ts @@ -2,11 +2,12 @@ import { extendString } from "@keymanapp/web-utils"; import { findCommonSubstringEndIndex } from "./stringDivergence.js"; +import { Mock } from "./mock.js"; extendString(); // Defines deadkey management in a manner attachable to each element interface. -import type KeyEvent from "./keyEvent.js"; +import { type KeyEvent } from 'keyman/engine/keyboard'; import { Deadkey, DeadkeyTracker } from "./deadkeys.js"; // Also relies on string-extensions provided by the web-utils package. @@ -307,159 +308,3 @@ export default abstract class OutputTarget { */ abstract doInputEvent(): void; } - -// Due to some interesting requirements on compile ordering in TS, -// this needs to be in the same file as OutputTarget now. -export class Mock extends OutputTarget { - text: string; - - selStart: number; - selEnd: number; - selForward: boolean = true; - - constructor(text?: string, caretPos?: number); - constructor(text?: string, selStart?: number, selEnd?: number); - constructor(text?: string, selStart?: number, selEnd?: number) { - super(); - - this.text = text ? text : ""; - var defaultLength = this.text._kmwLength(); - - // Ensures that `caretPos == 0` is handled correctly. - this.selStart = typeof selStart == "number" ? selStart : defaultLength; - - // If no selection-end is set, selection length is implied to be 0. - this.selEnd = typeof selEnd == "number" ? selEnd : this.selStart; - - this.selForward = this.selEnd >= this.selStart; - } - - // Clones the state of an existing EditableElement, creating a Mock version of its state. - static from(outputTarget: OutputTarget, readonly?: boolean) { - let clone: Mock; - - if(outputTarget instanceof Mock) { - // Avoids the need to run expensive kmwstring.ts / `_kmwLength()` - // calculations when deep-copying Mock instances. - let priorMock = outputTarget as Mock; - clone = new Mock(priorMock.text, priorMock.selStart, priorMock.selEnd); - } else { - const text = outputTarget.getText(); - const textLen = text._kmwLength(); - - // If !hasSelection() - let selectionStart: number = textLen; - let selectionEnd: number = 0; - - if(outputTarget.hasSelection()) { - let beforeText = outputTarget.getTextBeforeCaret(); - let afterText = outputTarget.getTextAfterCaret(); - selectionStart = beforeText._kmwLength(); - selectionEnd = textLen - afterText._kmwLength(); - } - - // readonly group or not, the returned Mock 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); - } - - // Also duplicate deadkey state! (Needed for fat-finger ops.) - clone.setDeadkeys(outputTarget.deadkeys()); - - return clone; - } - - clearSelection(): void { - this.text = this.getTextBeforeCaret() + this.getTextAfterCaret(); - this.selEnd = this.selStart; - this.selForward = true; - } - - invalidateSelection(): void { - return; - } - - isSelectionEmpty(): boolean { - return this.selStart == this.selEnd; - } - - hasSelection(): boolean { - return true; - } - - getDeadkeyCaret(): number { - return this.selStart; - } - - setSelection(start: number, end?: number) { - this.selStart = start; - this.selEnd = typeof end == 'number' ? end : start; - - this.selForward = end >= start; - if(!this.selForward) { - let temp = this.selStart; - this.selStart = this.selEnd; - this.selEnd = temp; - } - } - - getTextBeforeCaret(): string { - return this.text.kmwSubstr(0, this.selStart); - } - - getSelectedText(): string { - return this.text.kmwSubstr(this.selStart, this.selEnd - this.selStart); - } - - getTextAfterCaret(): string { - return this.text.kmwSubstr(this.selEnd); - } - - getText(): string { - return this.text; - } - - deleteCharsBeforeCaret(dn: number): void { - if(dn >= 0) { - if(dn > this.selStart) { - dn = this.selStart; - } - this.adjustDeadkeys(-dn); - this.text = this.text.kmwSubstr(0, this.selStart - dn) + this.text.kmwSubstr(this.selStart); - this.selStart -= dn; - this.selEnd -= dn; - } - } - - insertTextBeforeCaret(s: string): void { - this.adjustDeadkeys(s._kmwLength()); - this.text = this.getTextBeforeCaret() + s + this.text.kmwSubstr(this.selStart); - this.selStart += s.kmwLength(); - this.selEnd += s.kmwLength(); - } - - handleNewlineAtCaret(): void { - this.insertTextBeforeCaret('\n'); - } - - protected setTextAfterCaret(s: string): void { - this.text = this.getTextBeforeCaret() + s; - } - - /** - * Indicates if this Mock represents an identical context to that of another Mock. - * @param other - * @returns - */ - isEqual(other: Mock) { - return this.text == other.text - && this.selStart == other.selStart - && this.selEnd == other.selEnd - && this.deadkeys().equal(other.deadkeys()); - } - - doInputEvent() { - // Mock isn't backed by an element, so it won't have any event listeners. - } -} \ No newline at end of file diff --git a/web/src/engine/js-processor/src/ruleBehavior.ts b/web/src/engine/js-processor/src/ruleBehavior.ts index 0c0f6c0db5..a6b2c9973a 100644 --- a/web/src/engine/js-processor/src/ruleBehavior.ts +++ b/web/src/engine/js-processor/src/ruleBehavior.ts @@ -1,7 +1,9 @@ /// import KeyboardProcessor from "./keyboardProcessor.js"; -import { OutputTarget, Mock, type Transcription, VariableStoreDictionary } from "keyman/engine/keyboard"; +import { VariableStoreDictionary } from "keyman/engine/keyboard"; +import OutputTarget, { type Transcription } from './outputTarget.js'; +import { Mock } from "./mock.js"; import { type VariableStore } from "./systemStores.js"; /** diff --git a/web/src/engine/keyboard/src/stringDivergence.ts b/web/src/engine/js-processor/src/stringDivergence.ts similarity index 100% rename from web/src/engine/keyboard/src/stringDivergence.ts rename to web/src/engine/js-processor/src/stringDivergence.ts diff --git a/web/src/engine/keyboard/src/defaultRules.ts b/web/src/engine/keyboard/src/defaultRules.ts index 94acb02688..5512114d93 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 OutputTarget from './outputTarget.js'; +import { type OutputTarget } from './outputTarget.interface.js'; export enum EmulationKeystrokes { Enter = '\n', diff --git a/web/src/engine/keyboard/src/index.ts b/web/src/engine/keyboard/src/index.ts index 1963ed01c6..f5a4edea71 100644 --- a/web/src/engine/keyboard/src/index.ts +++ b/web/src/engine/keyboard/src/index.ts @@ -26,15 +26,11 @@ export { default as StateKeyMap } from "./keyboards/stateKeyMap.js"; export { default as Codes } from "./codes.js"; export * from "./codes.js"; -export * from "./deadkeys.js"; export { default as DefaultRules } from "./defaultRules.js"; export * from "./defaultRules.js"; export { default as KeyEvent } from "./keyEvent.js"; export * from "./keyEvent.js"; export { default as KeyMapping } from "./keyMapping.js"; -export { default as OutputTarget } from "./outputTarget.js"; -export * from "./outputTarget.js"; -export * from "./stringDivergence.js"; export * from "@keymanapp/web-utils"; diff --git a/web/src/engine/keyboard/src/keyboards/keyboard.ts b/web/src/engine/keyboard/src/keyboards/keyboard.ts index 0c0ff75512..42463cd6e7 100644 --- a/web/src/engine/keyboard/src/keyboards/keyboard.ts +++ b/web/src/engine/keyboard/src/keyboards/keyboard.ts @@ -2,7 +2,7 @@ import Codes from "../codes.js"; import { EncodedVisualKeyboard, LayoutSpec, Layouts } from "./defaultLayouts.js"; import { ActiveKey, ActiveLayout, ActiveSubKey } from "./activeLayout.js"; import KeyEvent from "../keyEvent.js"; -import type OutputTarget from "../outputTarget.js"; +import { type OutputTarget } from "../outputTarget.interface.js"; import { ModifierKeyConstants, TouchLayout } from "@keymanapp/common-types"; type TouchLayoutSpec = TouchLayout.TouchLayoutPlatform & { isDefault?: boolean}; diff --git a/web/src/engine/keyboard/src/outputTarget.interface.ts b/web/src/engine/keyboard/src/outputTarget.interface.ts new file mode 100644 index 0000000000..410094c812 --- /dev/null +++ b/web/src/engine/keyboard/src/outputTarget.interface.ts @@ -0,0 +1,104 @@ +export interface OutputTarget { + /** + * Signifies that this OutputTarget 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 f20dd19d00..5c774aa72d 100644 --- a/web/src/engine/main/src/contextManagerBase.ts +++ b/web/src/engine/main/src/contextManagerBase.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'eventemitter3'; -import { ManagedPromise, type Keyboard, type OutputTarget } from 'keyman/engine/keyboard'; -import { type KeyboardInterface } from 'keyman/engine/js-processor'; +import { ManagedPromise, type Keyboard } from 'keyman/engine/keyboard'; +import { type KeyboardInterface, type OutputTarget } from 'keyman/engine/js-processor'; import { StubAndKeyboardCache, type KeyboardStub } from 'keyman/engine/package-cache'; import { PredictionContext } from 'keyman/engine/interfaces'; import { EngineConfiguration } from './engineConfiguration.js'; diff --git a/web/src/engine/main/src/engineConfiguration.ts b/web/src/engine/main/src/engineConfiguration.ts index d6f6f0d698..050a7be11e 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, OutputTarget, physicalKeyDeviceAlias, SpacebarText } from "keyman/engine/keyboard"; -import { RuleBehavior } from 'keyman/engine/js-processor'; +import { DeviceSpec, KeyboardProperties, ManagedPromise, physicalKeyDeviceAlias, SpacebarText } from "keyman/engine/keyboard"; +import { OutputTarget, RuleBehavior } from 'keyman/engine/js-processor'; import { PathConfiguration, PathOptionDefaults, PathOptionSpec } from "keyman/engine/interfaces"; import { Device } from "keyman/engine/device-detect"; import { KeyboardStub } from "keyman/engine/package-cache"; diff --git a/web/src/engine/main/src/headless/contextWindow.ts b/web/src/engine/main/src/headless/contextWindow.ts index b3337c29e5..9fcd1458b2 100644 --- a/web/src/engine/main/src/headless/contextWindow.ts +++ b/web/src/engine/main/src/headless/contextWindow.ts @@ -1,4 +1,4 @@ -import { Mock } from "keyman/engine/keyboard"; +import { Mock } from "keyman/engine/js-processor"; export default class ContextWindow implements Context { // Used to limit the range of context replicated for use of keyboard rules within diff --git a/web/src/engine/main/src/headless/inputProcessor.ts b/web/src/engine/main/src/headless/inputProcessor.ts index 9ff0c4b81c..6d8904c563 100644 --- a/web/src/engine/main/src/headless/inputProcessor.ts +++ b/web/src/engine/main/src/headless/inputProcessor.ts @@ -7,16 +7,18 @@ import { LanguageProcessor } from "./languageProcessor.js"; import type { ModelSpec } from "keyman/engine/interfaces"; import { globalObject, DeviceSpec } from "@keymanapp/web-utils"; +import { Codes, type Keyboard, type KeyEvent } from "keyman/engine/keyboard"; import { type Alternate, - Codes, isEmptyTransform, - type Keyboard, - type KeyEvent, + KeyboardInterface, + KeyboardProcessor, Mock, type OutputTarget, -} from "keyman/engine/keyboard"; -import { KeyboardInterface, KeyboardProcessor, RuleBehavior, type ProcessorInitOptions, SystemStoreIDs } from 'keyman/engine/js-processor'; + RuleBehavior, + type ProcessorInitOptions, + SystemStoreIDs +} 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 8f93d39d87..c1c1721484 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 } from "@keymanapp/lexical-model-layer/web"; -import { OutputTarget, Transcription, Mock } from "keyman/engine/keyboard"; +import { OutputTarget, Transcription, Mock } from "keyman/engine/js-processor"; 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/headless/transcriptionCache.ts b/web/src/engine/main/src/headless/transcriptionCache.ts index 9430bde5ee..af05853e17 100644 --- a/web/src/engine/main/src/headless/transcriptionCache.ts +++ b/web/src/engine/main/src/headless/transcriptionCache.ts @@ -1,4 +1,4 @@ -import { Transcription } from "keyman/engine/keyboard"; +import { Transcription } from "keyman/engine/js-processor"; const TRANSCRIPTION_BUFFER_SIZE = 10; diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index dd898e1ee9..e88cf2ac74 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -13,9 +13,9 @@ import { StateKeyMap, ActiveSubKey, timedPromise, - ActiveKeyBase, - isEmptyTransform + ActiveKeyBase } from 'keyman/engine/keyboard'; +import { isEmptyTransform } from 'keyman/engine/js-processor'; import { buildCorrectiveLayout } from './correctionLayout.js'; import { distributionFromDistanceMaps, keyTouchDistances } from './corrections.js'; diff --git a/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.spec.ts b/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.spec.ts index 813ce78911..cdfc8814af 100644 --- a/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.spec.ts +++ b/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.spec.ts @@ -1,6 +1,7 @@ import { assert } from 'chai'; -import { extendString, Mock } from 'keyman/engine/keyboard'; +import { extendString } from 'keyman/engine/keyboard'; +import { Mock } from 'keyman/engine/js-processor'; 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.spec.ts b/web/src/test/auto/dom/cases/element-wrappers/target_mocks.spec.ts index 85116eaa8a..707d863899 100644 --- a/web/src/test/auto/dom/cases/element-wrappers/target_mocks.spec.ts +++ b/web/src/test/auto/dom/cases/element-wrappers/target_mocks.spec.ts @@ -1,6 +1,7 @@ import { assert } from 'chai'; -import { extendString, Mock } from 'keyman/engine/keyboard'; +import { extendString } from 'keyman/engine/keyboard'; +import { Mock } from 'keyman/engine/js-processor'; 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.spec.ts b/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.spec.ts index bcf4afcf65..da610402eb 100644 --- a/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.spec.ts +++ b/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.spec.ts @@ -1,8 +1,8 @@ import { assert } from 'chai'; import { DOMKeyboardLoader } from 'keyman/engine/keyboard/dom-keyboard-loader'; -import { extendString, KeyboardHarness, Keyboard, MinimalKeymanGlobal, Mock, DeviceSpec, KeyboardKeymanGlobal } from 'keyman/engine/keyboard'; -import { KeyboardInterface } from 'keyman/engine/js-processor'; +import { extendString, KeyboardHarness, Keyboard, MinimalKeymanGlobal, DeviceSpec, KeyboardKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, Mock } from 'keyman/engine/js-processor'; declare let window: typeof globalThis; // KeymanEngine from the web/ folder... when available. diff --git a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js index 39ab4aea59..dbc24cad4f 100644 --- a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js +++ b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js @@ -4,8 +4,8 @@ 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 { KeyboardProcessor } from 'keyman/engine/js-processor'; +import { DeviceSpec } from 'keyman/engine/keyboard'; +import { KeyboardProcessor, Mock } from 'keyman/engine/js-processor'; function compileDummyModel(suggestionSets) { return ` diff --git a/web/src/test/auto/headless/engine/js-processor/engine/context.js b/web/src/test/auto/headless/engine/js-processor/engine/context.js index 3b318607a0..a355ead265 100644 --- a/web/src/test/auto/headless/engine/js-processor/engine/context.js +++ b/web/src/test/auto/headless/engine/js-processor/engine/context.js @@ -3,8 +3,8 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; -import { KeyboardInterface, KeyboardProcessor } from 'keyman/engine/js-processor'; +import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, KeyboardProcessor, Mock } 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/engine/notany_context.js b/web/src/test/auto/headless/engine/js-processor/engine/notany_context.js index 1fc2ec0129..ecc7dd92d0 100644 --- a/web/src/test/auto/headless/engine/js-processor/engine/notany_context.js +++ b/web/src/test/auto/headless/engine/js-processor/engine/notany_context.js @@ -3,8 +3,8 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; -import { KeyboardInterface } from 'keyman/engine/js-processor'; +import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, Mock } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { NodeProctor, RecordedKeystrokeSequence } from '@keymanapp/recorder-core'; import { extendString } from '@keymanapp/web-utils'; diff --git a/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.js b/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.js index 03203a840f..0d08496442 100644 --- a/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.js +++ b/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.js @@ -3,8 +3,8 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; -import { KeyboardInterface } from 'keyman/engine/js-processor'; +import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, Mock } 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/mocks.js b/web/src/test/auto/headless/engine/js-processor/mocks.js index b02f259886..bad81bfcca 100644 --- a/web/src/test/auto/headless/engine/js-processor/mocks.js +++ b/web/src/test/auto/headless/engine/js-processor/mocks.js @@ -1,5 +1,5 @@ import { assert } from 'chai'; -import { Mock } from 'keyman/engine/keyboard'; +import { Mock } from 'keyman/engine/js-processor'; describe('Mocks', function() { describe('app|les', () => { diff --git a/web/src/test/auto/headless/engine/js-processor/non-positional-rules.js b/web/src/test/auto/headless/engine/js-processor/non-positional-rules.js index 2ab62a4840..a65cec6708 100644 --- a/web/src/test/auto/headless/engine/js-processor/non-positional-rules.js +++ b/web/src/test/auto/headless/engine/js-processor/non-positional-rules.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, Mock } from 'keyman/engine/keyboard'; -import { KeyboardInterface } from 'keyman/engine/js-processor'; +import { Codes, KeyEvent, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, Mock } 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.js b/web/src/test/auto/headless/engine/js-processor/specialized-backspace.js index 967701c63a..7e35817f07 100644 --- a/web/src/test/auto/headless/engine/js-processor/specialized-backspace.js +++ b/web/src/test/auto/headless/engine/js-processor/specialized-backspace.js @@ -4,8 +4,8 @@ import fs from 'fs'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { Codes, KeyEvent, MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; -import { KeyboardInterface, KeyboardProcessor } from 'keyman/engine/js-processor'; +import { Codes, KeyEvent, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, KeyboardProcessor, Mock } 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.js b/web/src/test/auto/headless/engine/js-processor/transcriptions.js index 569b36d3cd..45c09b3b80 100644 --- a/web/src/test/auto/headless/engine/js-processor/transcriptions.js +++ b/web/src/test/auto/headless/engine/js-processor/transcriptions.js @@ -1,6 +1,6 @@ import { assert } from 'chai'; -import { Mock, findCommonSubstringEndIndex } from 'keyman/engine/keyboard'; +import { Mock, findCommonSubstringEndIndex } from 'keyman/engine/js-processor'; import { extendString } from '@keymanapp/web-utils'; extendString(); // Ensure KMW's string-extension functionality is available. diff --git a/web/src/test/auto/headless/engine/keyboard/keyboard-loading.js b/web/src/test/auto/headless/engine/keyboard/keyboard-loading.js index 1dc2baad9b..0dd89a3614 100644 --- a/web/src/test/auto/headless/engine/keyboard/keyboard-loading.js +++ b/web/src/test/auto/headless/engine/keyboard/keyboard-loading.js @@ -3,8 +3,8 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardHarness, MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; -import { KeyboardInterface } from 'keyman/engine/js-processor'; +import { KeyboardHarness, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, Mock } from 'keyman/engine/js-processor'; import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; describe('Headless keyboard loading', function() { diff --git a/web/src/tools/testing/recorder-core/src/index.ts b/web/src/tools/testing/recorder-core/src/index.ts index caa0262f6b..2a73971023 100644 --- a/web/src/tools/testing/recorder-core/src/index.ts +++ b/web/src/tools/testing/recorder-core/src/index.ts @@ -1,5 +1,5 @@ -import { type OutputTarget } from "keyman/engine/keyboard"; -import { KeyDistribution, KeyEvent, Mock } from "keyman/engine/keyboard"; +import { Mock, type OutputTarget } from "keyman/engine/js-processor"; +import { KeyDistribution, KeyEvent } 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 e9a08530c8..204ef21939 100644 --- a/web/src/tools/testing/recorder-core/src/nodeProctor.ts +++ b/web/src/tools/testing/recorder-core/src/nodeProctor.ts @@ -8,7 +8,8 @@ import { RecordedSyntheticKeystroke } from "./index.js"; -import { KeyEvent, KeyEventSpec, Mock, type OutputTarget, KeyboardHarness } from "keyman/engine/keyboard"; +import { KeyEvent, KeyEventSpec, KeyboardHarness } from "keyman/engine/keyboard"; +import { Mock, type OutputTarget } from "keyman/engine/js-processor"; import { DeviceSpec } from "@keymanapp/web-utils"; import { KeyboardInterface, KeyboardProcessor } from 'keyman/engine/js-processor'; diff --git a/web/src/tools/testing/recorder-core/src/proctor.ts b/web/src/tools/testing/recorder-core/src/proctor.ts index 0242b911a1..ec4f584b04 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 OutputTarget } from "keyman/engine/keyboard"; +import { type OutputTarget } from "keyman/engine/js-processor"; import type { KeyboardTest, TestSet, TestSequence } from "./index.js"; diff --git a/web/src/tools/testing/recorder/browserProctor.ts b/web/src/tools/testing/recorder/browserProctor.ts index 73e5a80ea7..a48e5572cb 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 OutputTarget } from "keyman/engine/keyboard"; +import { type OutputTarget } from "keyman/engine/js-processor"; import { type KeymanEngine } from 'keyman/app/browser';