Skip to content

fix(web): allow to set default keyboard to 'off' - #16524

Merged
ermshiperete merged 5 commits into
masterfrom
fix/web/16080_kbdoff
Sep 17, 2026
Merged

ermshiperete merged 5 commits into
masterfrom
fix/web/16080_kbdoff

Conversation

@ermshiperete

@ermshiperete ermshiperete commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

The parameters for setKeyboardForControl basically have three different modes: - an id of a keyboard/language to set that keyboard, enabling independent keyboard mode - empty string to set the system keyboard, enabling independent keyboard mode - null to disable independent keyboard mode.

Our previous code didn't properly handle the last two modes. This change fixes the problems and also clarifies and updates the documentation.

Also included is an improvement to the guide-examples e2e tests that now wait until all keyboards are loaded (which may fix #16167).

Partially drafted by kilo.ai.

User tests will follow in an upcoming PR for #16522.

Fixes: #16080
Build-bot: skip release:web
Test-bot: skip

@github-project-automation github-project-automation Bot moved this to Todo in Keyman Sep 3, 2026
@keymanapp-test-bot keymanapp-test-bot Bot added the user-test-missing User tests have not yet been defined for the PR label Sep 3, 2026
@keymanapp-test-bot

keymanapp-test-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

User Test Results

Test specification and instructions

User tests are not required

Test Artifacts

@keymanapp-test-bot keymanapp-test-bot Bot added this to the B19S1 milestone Sep 3, 2026
@ermshiperete
ermshiperete changed the base branch from chore/web/typesInTest to refactor/web/funcscope September 4, 2026 20:32
@ermshiperete
ermshiperete force-pushed the fix/web/16080_kbdoff branch 2 times, most recently from 96c527c to 9de509a Compare September 7, 2026 17:19
}

test.describe.skip('First example from the guide', function () {
test.describe('First example from the guide', function () {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guide-examples e2e tests are re-enabled; if it turns out that they're still not stable then we can disable them again.


languageMenu.lgList.style.display='none'; //still allows blank menu momentarily on selection
languageMenu.keyman.contextManager.activateKeyboard(entry.kn, entry.kc,true);
languageMenu.keyman.contextManager.restoreLastActiveTextStore();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already done in activateKeyboard, so there's no need to do it twice (especially since activateKeyboard is async, so this call could possibly work with outdated data...)

@ermshiperete
ermshiperete marked this pull request as ready for review September 8, 2026 09:55
Base automatically changed from refactor/web/funcscope to master September 8, 2026 09:56
The parameters for `setKeyboardForControl` basically have three
different modes:
- an id of a keyboard/language to set that keyboard, enabling independent
  keyboard mode
- empty string to set the system keyboard, enabling independent keyboard
  mode
- `null` to disable independent keyboard mode.

Our previous code didn't properly handle the last two modes. This change
fixes the problems and also clarifies and updates the documentation.

Also included is an improvement to the guide-examples e2e tests that now
wait until all keyboards are loaded (which may fix #16167).

Partially drafted by kilo.ai.

Fixes: #16080
Build-bot: skip release:web
Comment thread web/src/app/browser/src/keymanEngine.ts Outdated
Comment on lines +296 to +298
if (!elem.ownerDocument.defaultView) {
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What role does this new conditional play?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to play safe. defaultView could theoretically be null.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems unlikely? But if we want to really play safe, we should:

Suggested change
if (!elem.ownerDocument.defaultView) {
return;
}
if (!elem?.ownerDocument?.defaultView) {
return;
}

That will cover cases where elem is not set.

In some ways, this may be misleading -- perhaps it would be better for an error to be raised, so the consumer knows that this failed. Either by returning false or by throw:

Suggested change
if (!elem.ownerDocument.defaultView) {
return;
}
if (!elem?.ownerDocument?.defaultView) {
return false;
}
Suggested change
if (!elem.ownerDocument.defaultView) {
return;
}
if (!elem?.ownerDocument?.defaultView) {
throw new Error('setKeyboardForControl: elem null or has no default View');
}

However, my primary thought here is that there are so many things we could consider for all the API endpoints -- this is a piecemeal validation step which will probably become inconsistent over time with other API endpoints, so really we should be thinking about parameter validation across the whole API -- types, object shape, return values / exceptions / console.warn / console.error, and implement that consistently. That would be a better outcome than micro patches. We see a similar issue with lines 299-302 below, where we have a console warning raised which is not really visible to the API consumer at runtime -- so becomes an invisible log message in 99% of cases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I'll undo this check.
Done.

* @param langId
*/
public setKeyboardForTextStore(textStore: AbstractElementTextStore<any>, kbdId: string, langId: string): void {
public setKeyboardForTextStore(textStore: AbstractElementTextStore<any>, kbdId: string | null, langId: string | null): void {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use kbdId?: string, langId?: string instead? That way, there's no need for the nullish fallbacksin line 317 of keymanEngine.ts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since null is one of the possible values I'd like to keep it in the type to be explicit. But I can make the arguments optional so that the signature is similar to KeymanEngine.setKeyboardForControl.

Done.

Comment thread web/src/app/browser/src/keymanEngine.ts Outdated
}

this.contextManager.setKeyboardForTextStore(elem._kmwAttachment.textStore, keyboard, languageCode);
this.contextManager.setKeyboardForTextStore(elem._kmwAttachment.textStore, keyboard ?? null, languageCode ?? null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer changes that leave these two parameters completely pass-through. Why change this line when an equally-simple change avoids the need for it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

constructInstance: (): null => null
};

describe('KeymanEngine.getKeyboardForControl', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No explicit .setKeyboardForControl tests here?

While I guess they're kinda tied, you should also be able to verify three things:

  1. A keystroke (either physical or via OSK) results in the correct output character
  2. Swapping to a second, still-global control swaps the current "active keyboard" reported by the engine to the global keyboard setting.
  3. Swapping back to the original control restores its setting and what the "current keyboard" reported by the engine is.

I thought we had some old automated tests that might have already been testing points 2 and 3, but I don't see them upon a search.

They did exist back in stable-16.0, but apparently they got erased at some point by accident during work toward stable-17.0. Here's a permalink to the relevant automated tests from before:

it("Keyboard Management (active control)", function() {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added e2e tests

Also add links to guide examples to test page.
@keyman-server keyman-server modified the milestones: B19S1, B19S2 Sep 12, 2026
Comment thread web/src/app/browser/src/keymanEngine.ts Outdated
Comment on lines +296 to +298
if (!elem.ownerDocument.defaultView) {
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems unlikely? But if we want to really play safe, we should:

Suggested change
if (!elem.ownerDocument.defaultView) {
return;
}
if (!elem?.ownerDocument?.defaultView) {
return;
}

That will cover cases where elem is not set.

In some ways, this may be misleading -- perhaps it would be better for an error to be raised, so the consumer knows that this failed. Either by returning false or by throw:

Suggested change
if (!elem.ownerDocument.defaultView) {
return;
}
if (!elem?.ownerDocument?.defaultView) {
return false;
}
Suggested change
if (!elem.ownerDocument.defaultView) {
return;
}
if (!elem?.ownerDocument?.defaultView) {
throw new Error('setKeyboardForControl: elem null or has no default View');
}

However, my primary thought here is that there are so many things we could consider for all the API endpoints -- this is a piecemeal validation step which will probably become inconsistent over time with other API endpoints, so really we should be thinking about parameter validation across the whole API -- types, object shape, return values / exceptions / console.warn / console.error, and implement that consistently. That would be a better outcome than micro patches. We see a similar issue with lines 299-302 below, where we have a console warning raised which is not really visible to the API consumer at runtime -- so becomes an invisible log message in 99% of cases.

Comment thread web/src/app/browser/src/keymanEngine.ts Outdated
if(!elem || !this.contextManager.isElementInIndependentMode(elem)) {
return null;
}
const keyboard = elem._kmwAttachment.keyboard;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if _kmwAttachment is not defined?

Suggested change
const keyboard = elem._kmwAttachment.keyboard;
const keyboard = elem._kmwAttachment?.keyboard ?? '';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also suggest that we use keyboardId for consistency. Assuming that is what it is?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If _kmwAttachment is not defined then isElementInIndependentMode will return false, so at this point here we know that it is defined.

Comment thread web/src/app/browser/src/keymanEngine.ts Outdated
if(!elem || !this.contextManager.isElementInIndependentMode(elem)) {
return null;
}
return elem._kmwAttachment.languageCode;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return elem._kmwAttachment.languageCode;
return elem._kmwAttachment?.languageCode ?? '';

Query: should this be returning '' for system-specified language (matching the shape of getKeyboardForControl) or null (which is simpler to reason on)?

@ermshiperete ermshiperete Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make it symmetrical to the values we pass to setKeyboardForControl it should return ''.

Done.

Comment thread web/src/app/browser/src/keymanEngine.ts Outdated
Comment on lines 331 to 332
* @return {string|null} The independently-managed keyboard for the control,
* or null if it is following the global keyboard setting.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be '' to mean 'independently-managed but set to system keyboard', I think according to the intention of the spec. We should make sure that is clear

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@ermshiperete
ermshiperete dismissed mcdurdin’s stale review September 16, 2026 13:44

Requesting re-review

Comment thread web/src/app/browser/src/contextManager.ts Outdated
Comment thread web/src/app/browser/src/contextManager.ts Outdated
Co-authored-by: Marc Durdin <marc@durdin.net>
@keymanapp-test-bot keymanapp-test-bot Bot removed the user-test-missing User tests have not yet been defined for the PR label Sep 17, 2026
@ermshiperete
ermshiperete merged commit 7b2f275 into master Sep 17, 2026
8 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Keyman Sep 17, 2026
@keyman-server

Copy link
Copy Markdown
Collaborator

Changes in this pull request will be available for download in Keyman version 19.0.287-alpha

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

bug(web): guide-examples.tests.ts appears to have a race bug(web): (re-)allow to set default keyboard to 'off'

4 participants