Repository navigation
Expand file tree
/
Copy pathsoftwareKeyboard.ts
More file actions
83 lines (74 loc) · 2.81 KB
/
Copy pathsoftwareKeyboard.ts
File metadata and controls
83 lines (74 loc) · 2.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
type VirtualKeyboard = EventTarget & {
readonly boundingRect: Pick<DOMRectReadOnly, "height">;
readonly overlaysContent: boolean;
};
type KeyboardWindow = Pick<Window, "innerHeight" | "visualViewport"> & {
readonly navigator: Navigator & { readonly virtualKeyboard?: VirtualKeyboard };
};
const KEYBOARD_THRESHOLD = 150;
function viewportSnapshot(viewport: VisualViewport) {
return {
width: Math.round(viewport.width),
height: Math.round(viewport.height),
scale: viewport.scale,
};
}
export function createSoftwareKeyboardStore(win: KeyboardWindow | undefined, isDesktop: boolean) {
const listeners = new Set<() => void>();
let viewportOpen = false;
const viewport = isDesktop ? undefined : win?.visualViewport;
const keyboard = isDesktop ? undefined : win?.navigator.virtualKeyboard;
let previous = viewport ? viewportSnapshot(viewport) : undefined;
const getSnapshot = () => {
// Chromium can expose the API while withholding geometry outside overlay mode.
// Keep the browser's automatic viewport resizing instead of enabling overlays.
if (keyboard && (keyboard.overlaysContent || keyboard.boundingRect.height > 0)) {
return keyboard.boundingRect.height > 0;
}
return viewportOpen;
};
const notify = () => {
for (const listener of listeners) listener();
};
const onViewportResize = () => {
if (!viewport || !win) return;
const current = viewportSnapshot(viewport);
// Adapted from Ionic's keyboard assist, MIT, Copyright (c) 2015-present Drifty Co.
// https://github.com/ionic-team/ionic-framework/blob/main/core/src/utils/keyboard/keyboard.ts
const scaledHeightDifference = previous
? (previous.height - current.height) * current.scale
: 0;
if (Math.abs(current.height - win.innerHeight / current.scale) <= 1) {
viewportOpen = false;
} else if (
!viewportOpen &&
previous?.width === current.width &&
scaledHeightDifference > KEYBOARD_THRESHOLD
) {
viewportOpen = true;
}
previous = current;
notify();
};
const subscribe = (listener: () => void) => {
if (isDesktop || !win) return () => {};
if (listeners.size === 0) {
if (viewport) {
previous = viewportSnapshot(viewport);
// A composer can mount after the keyboard has already opened.
viewportOpen = win.innerHeight - previous.height * previous.scale > KEYBOARD_THRESHOLD;
viewport.addEventListener("resize", onViewportResize);
}
keyboard?.addEventListener("geometrychange", notify);
}
listeners.add(listener);
return () => {
listeners.delete(listener);
if (listeners.size === 0) {
viewport?.removeEventListener("resize", onViewportResize);
keyboard?.removeEventListener("geometrychange", notify);
}
};
};
return { getSnapshot, subscribe };
}