Skip to content

Commit 2e0293c

Browse files
committed
Experimental brower keyboard API
1 parent ef652c0 commit 2e0293c

File tree

1 file changed

+90
-1
lines changed

1 file changed

+90
-1
lines changed

packages/keyboard/src/index.ts

+90-1
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212
* @module keyboard
1313
*/
1414

15-
import { IKeyboardLayout } from './core';
15+
import { IKeyboardLayout, KeycodeLayout } from './core';
1616
export { IKeyboardLayout, KeycodeLayout } from './core';
1717

1818
export { EN_US } from './layouts';
1919
import { EN_US } from './layouts';
2020
import * as Layouts from './layouts';
21+
import { MODIFIER_KEYS } from './special-keys';
2122

2223
export const KeyboardLayouts = Object.values(Layouts);
2324

@@ -42,9 +43,40 @@ export function getKeyboardLayout(): IKeyboardLayout {
4243
* to a layout which is appropriate for the user's system.
4344
*/
4445
export function setKeyboardLayout(layout: IKeyboardLayout): void {
46+
try {
47+
Private.unsubscribeBrowserUpdates();
48+
} catch (e) {
49+
// Ignore exceptions in experimental code
50+
}
4551
Private.keyboardLayout = layout;
4652
}
4753

54+
/**
55+
* Whether the browser supports inspecting the keyboard layout.
56+
*
57+
* @alpha
58+
*/
59+
export function hasBrowserLayout(): boolean {
60+
return !!(navigator as any)?.keyboard?.getLayoutMap;
61+
}
62+
63+
/**
64+
* Use the keyboard layout of the browser if it supports it.
65+
*
66+
* @alpha
67+
* @returns Whether the browser supports inspecting the keyboard layout.
68+
*/
69+
export async function useBrowserLayout(): Promise<boolean> {
70+
const keyboardApi = (navigator as any)?.keyboard;
71+
if (!(await Private.updateBrowserLayout())) {
72+
return false;
73+
}
74+
if (keyboardApi?.addEventListener) {
75+
keyboardApi.addEventListener('layoutchange', Private.updateBrowserLayout);
76+
}
77+
return true;
78+
}
79+
4880
/**
4981
* The namespace for the module implementation details.
5082
*/
@@ -53,4 +85,61 @@ namespace Private {
5385
* The global keyboard layout instance.
5486
*/
5587
export let keyboardLayout = EN_US;
88+
89+
/**
90+
* Polyfill until Object.fromEntries is available
91+
*/
92+
function fromEntries<T>(entries: Iterable<[string, T]>) {
93+
const ret = {} as { [key: string]: T };
94+
for (const [key, value] of entries) {
95+
ret[key] = value;
96+
}
97+
return ret;
98+
}
99+
100+
/**
101+
* Get the current browser keyboard layout, or null if unsupported.
102+
*
103+
* @returns The keyboard layout of the browser at this moment if supported, otherwise null.
104+
*/
105+
export async function getBrowserKeyboardLayout(): Promise<
106+
IKeyboardLayout | undefined
107+
> {
108+
const keyboardApi = (navigator as any)?.keyboard;
109+
if (!keyboardApi) {
110+
return undefined;
111+
}
112+
const browserMap = await keyboardApi.getLayoutMap();
113+
if (!browserMap) {
114+
return undefined;
115+
}
116+
return new KeycodeLayout(
117+
'browser',
118+
{},
119+
MODIFIER_KEYS,
120+
fromEntries(browserMap.entries())
121+
);
122+
}
123+
124+
/**
125+
* Set the active layout to that of the browser at this instant.
126+
*/
127+
export async function updateBrowserLayout(): Promise<boolean> {
128+
const initial = await getBrowserKeyboardLayout();
129+
if (!initial) {
130+
return false;
131+
}
132+
keyboardLayout = initial;
133+
return true;
134+
}
135+
136+
/**
137+
* Unsubscribe any browser updates
138+
*/
139+
export function unsubscribeBrowserUpdates(): void {
140+
const keyboardApi = (navigator as any)?.keyboard;
141+
if (keyboardApi?.removeEventListener) {
142+
keyboardApi.removeEventListener(updateBrowserLayout);
143+
}
144+
}
56145
}

0 commit comments

Comments
 (0)