From fe16d0ce45d2d2c2df287c394326d2ab2ddb4896 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 25 Apr 2023 14:24:27 +0700 Subject: [PATCH] chore(web): initial connection of attachment engine to ContextManager --- web/package.json | 8 +++ web/src/app/browser/src/contextManager.ts | 18 +++++++ .../app/browser/src/defaultBrowserRules.ts | 14 +++-- .../attachment/src/pageContextAttachment.ts | 54 ++++++++++++++++--- .../engine/namespaced-main/dom/domManager.ts | 37 ------------- 5 files changed, 82 insertions(+), 49 deletions(-) diff --git a/web/package.json b/web/package.json index 40f10622a5..2ffea125b5 100644 --- a/web/package.json +++ b/web/package.json @@ -2,6 +2,14 @@ "name": "keyman", "description": "Facilitates text input in any language.", "exports": { + "./engine/attachment": { + "types": "./build/engine/attachment/obj/index.d.ts", + "import": "./build/engine/attachment/obj/index.js" + }, + "./engine/attachment/lib": { + "types": "./build/engine/attachment/obj/index.d.ts", + "import": "./build/engine/attachment/lib/index.mjs" + }, "./engine/paths": { "types": "./build/engine/paths/obj/index.d.ts", "import": "./build/engine/paths/obj/index.js" diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 3ee3a744fb..b2641a88c9 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -2,6 +2,7 @@ import { type Keyboard, Mock } from '@keymanapp/keyboard-processor'; import { type KeyboardStub } from 'keyman/engine/package-cache'; import { CookieSerializer } from 'keyman/engine/dom-utils'; import { OutputTarget } from 'keyman/engine/element-wrappers'; +import { PageContextAttachment } from 'keyman/engine/attachment'; import { ContextManagerBase, type KeyboardInterface @@ -18,6 +19,23 @@ export default class ContextManager extends ContextManagerBase('KeymanWeb_Keyboard'); readonly focusAssistant = new FocusAssistant(); + readonly page: PageContextAttachment; + + constructor(engineConfig: BrowserConfiguration) { + super(engineConfig); + + this.page = new PageContextAttachment(window.document, { + hostDevice: this.config.hostDevice, + isTopLevel: true + }); + + this.engineConfig.deferForInitialization.then(() => { + // TODO: set up attachment-listeners here that can add necessary event-hooks + // for focus management here! + + this.page.install(this.engineConfig.attachType == 'manual'); + }); + } initialize(): void { this.on('keyboardasyncload', (stub, completion) => { diff --git a/web/src/app/browser/src/defaultBrowserRules.ts b/web/src/app/browser/src/defaultBrowserRules.ts index 135340b77b..c38fff84db 100644 --- a/web/src/app/browser/src/defaultBrowserRules.ts +++ b/web/src/app/browser/src/defaultBrowserRules.ts @@ -36,16 +36,22 @@ export default class DefaultBrowserRules extends DefaultRules { const contextManager = this.contextManager; + let elem: HTMLElement; switch(code) { - // This method will be transplanted to the specific `ContextManager` module stored above. + // This method will be handled between `ContextManager` and PageContextAttachment: + // pageContextAttachment.findNeighboringInput(contextManager.activeTarget.getElement(), ) case Codes.keyCodes['K_TAB']: - domManager.moveToNext((Lkc.Lmodifiers & Codes.modifierCodes['SHIFT']) != 0); + const bBack = (Lkc.Lmodifiers & Codes.modifierCodes['SHIFT']) != 0; + elem = contextManager.page.findNeighboringInput(contextManager.activeTarget.getElement(), bBack); + elem.focus(); break; case Codes.keyCodes['K_TABBACK']: - domManager.moveToNext(true); + elem = contextManager.page.findNeighboringInput(contextManager.activeTarget.getElement(), true); + elem.focus(); break; case Codes.keyCodes['K_TABFWD']: - domManager.moveToNext(false); + elem = contextManager.page.findNeighboringInput(contextManager.activeTarget.getElement(), false); + elem.focus(); break; } diff --git a/web/src/engine/attachment/src/pageContextAttachment.ts b/web/src/engine/attachment/src/pageContextAttachment.ts index 7abea79612..a949647caa 100644 --- a/web/src/engine/attachment/src/pageContextAttachment.ts +++ b/web/src/engine/attachment/src/pageContextAttachment.ts @@ -136,11 +136,6 @@ export interface PageAttachmentOptions { */ hostDevice: DeviceSpec; - /** - * The KMW init() option, as set for the page. - */ - attachType: 'manual' | 'auto'; - /** * Should only be set to `true` for the top-level page. Should be `false` for * any pages embedded in another page via iframe. @@ -199,6 +194,8 @@ export class PageContextAttachment extends EventEmitter { return this._sortedInputs; } + private manualAttach: boolean; + /** * Tracks the attachment MutationObserver. */ @@ -239,7 +236,10 @@ export class PageContextAttachment extends EventEmitter { * Call this method **once**, when the page is fully loaded, to attach to all page elements * eligible to serve as context for Keyman keyboard input. */ - install() { + install(manualAttach: boolean) { + // Do before _SetupDocument! + this.manualAttach = manualAttach; + this._SetupDocument(document.documentElement); // KMW 16.0 and before: these were only ever established for the top-level doc, and so for @@ -247,7 +247,7 @@ export class PageContextAttachment extends EventEmitter { // // That said, for future consideration: enable it within iframe-internal documents too. if(this.options.isTopLevel) { - this.initMutationObservers(this.document, this.options.attachType == 'manual'); + this.initMutationObservers(this.document, manualAttach); } } @@ -558,7 +558,7 @@ export class PageContextAttachment extends EventEmitter { embeddedPageAttachment.on('enabled', (elem) => this.emit('enabled', elem)); embeddedPageAttachment.on('disabled', (elem) => this.emit('disabled', elem)); - embeddedPageAttachment.install(); + embeddedPageAttachment.install(this.manualAttach); } } } @@ -737,6 +737,44 @@ export class PageContextAttachment extends EventEmitter { this._sortedInputs=tList; } + /** + * Move focus to next (or previous) input or text area element on TAB + * Uses list of actual input elements + * + * Note that activeElement() on touch devices returns the DIV that overlays + * the input element, not the element itself. + * + * @param {number|boolean} bBack Direction to move (0 or 1) + */ + findNeighboringInput(activeBase: HTMLElement, bBack: number|boolean) { + var i,t=this.sortedInputs; + + if(t.length == 0) { + return null; + } + + // Identify the active element in the list of inputs ordered by position + for(i=0; i= t.length ? i-t.length : i; + i = i < 0 ? i+t.length : i; + + return t[i]; + } + /** * Function _GetDocumentEditables diff --git a/web/src/engine/namespaced-main/dom/domManager.ts b/web/src/engine/namespaced-main/dom/domManager.ts index 514f828ca6..a9ce2803d0 100644 --- a/web/src/engine/namespaced-main/dom/domManager.ts +++ b/web/src/engine/namespaced-main/dom/domManager.ts @@ -453,43 +453,6 @@ namespace com.keyman.dom { } } - /** - * Move focus to next (or previous) input or text area element on TAB - * Uses list of actual input elements - * - * Note that activeElement() on touch devices returns the DIV that overlays - * the input element, not the element itself. - * - * @param {number|boolean} bBack Direction to move (0 or 1) - */ - moveToNext(bBack: number|boolean) { - var i,t=this.sortedInputs, activeBase = this.activeElement; - var touchable = this.keyman.util.device.touchable; - - if(t.length == 0) { - return; - } - - // For touchable devices, get the base element of the DIV - if(touchable) { - activeBase=activeBase.base; - } - - // Identify the active element in the list of inputs ordered by position - for(i=0; i= t.length ? i-t.length : i; - i = i < 0 ? i+t.length : i; - - // Move to the selected element - t[i].focus(); - } - /** * Move focus to user-specified element *