@livx.cc/native-kit 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livx.cc/native-kit",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Isomorphic native-capabilities kit for PWAs — same API in browser and in an appwrap native shell. Zero dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "Elya Livshitz",
@@ -12,6 +12,7 @@ import { DeviceModule } from '../modules/device';
12
12
  import { GeoModule } from '../modules/geo';
13
13
  import { HapticsModule } from '../modules/haptics';
14
14
  import { HealthModule } from '../modules/health';
15
+ import { KeyboardModule } from '../modules/keyboard';
15
16
  import { LifecycleModule } from '../modules/lifecycle';
16
17
  import { MediaModule } from '../modules/media';
17
18
  import { MotionModule } from '../modules/motion';
@@ -70,6 +71,7 @@ export class NativeKit {
70
71
  public readonly haptics = new HapticsModule(this);
71
72
  public readonly share = new ShareModule(this);
72
73
  public readonly screen = new ScreenModule(this);
74
+ public readonly keyboard = new KeyboardModule(this);
73
75
  public readonly storage = new StorageModule(this);
74
76
  public readonly toast = new ToastModule(this);
75
77
  public readonly ui = new UiModule(this);
@@ -54,6 +54,7 @@ export class WebAdapter implements NativeKitAdapter {
54
54
  photos: 'web',
55
55
  network: 'web',
56
56
  screen: 'wakeLock' in n ? 'web' : 'none',
57
+ keyboard: (window as any).visualViewport ? 'web' : 'none', // VisualViewport resize ≈ keyboard show/hide
57
58
  dialogs: 'web',
58
59
  reviews: 'none',
59
60
  themeColor: 'web', // the browser honors <meta name="theme-color"> itself
@@ -180,6 +181,11 @@ export class WebAdapter implements NativeKitAdapter {
180
181
  case 'screen.orientation.current':
181
182
  return (((screen as any)?.orientation?.type ?? '').startsWith('landscape') ? 'landscape' : 'portrait') as T;
182
183
 
184
+ case 'keyboard.hide':
185
+ // No programmatic IME on web — blurring the focused field dismisses it.
186
+ (document.activeElement as HTMLElement | null)?.blur?.();
187
+ return undefined as T;
188
+
183
189
  case 'reviews.requestReview':
184
190
  throw new KitError('UNSUPPORTED', 'No in-app review prompt on web');
185
191
 
@@ -371,6 +377,22 @@ export class WebAdapter implements NativeKitAdapter {
371
377
  so.addEventListener('change', fire);
372
378
  return () => so.removeEventListener('change', fire);
373
379
  }
380
+ if (event === 'keyboard.show' || event === 'keyboard.hide') {
381
+ const vv = (window as any).visualViewport;
382
+ if (!vv) return () => {};
383
+ let shown = false;
384
+ const onResize = () => {
385
+ // The keyboard shrinks the visual viewport; the hidden band ≈ its height.
386
+ const height = Math.max(0, window.innerHeight - vv.height - vv.offsetTop);
387
+ const nowShown = height > 120; // threshold filters URL-bar/toolbar resizes
388
+ if (nowShown === shown) return;
389
+ shown = nowShown;
390
+ if (event === 'keyboard.show' && nowShown) cb({ height: Math.round(height) });
391
+ if (event === 'keyboard.hide' && !nowShown) cb(undefined);
392
+ };
393
+ vv.addEventListener('resize', onResize);
394
+ return () => vv.removeEventListener('resize', onResize);
395
+ }
374
396
  if (event === 'app.pause' || event === 'app.resume') {
375
397
  const handler = () => {
376
398
  const paused = document.visibilityState === 'hidden';
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export type {
22
22
  export type { ImpactStyle, NotifyType } from './modules/haptics';
23
23
  export type { SharePayload, ShareFile } from './modules/share';
24
24
  export type { Orientation, OrientationLock } from './modules/screen';
25
+ export type { KeyboardInfo } from './modules/keyboard';
25
26
  export type { DeviceInfo } from './modules/device';
26
27
  export type { ScheduleOptions } from './modules/notifications';
27
28
  export type { PushMessage, PushPlatform, PushToken } from './modules/push';
@@ -0,0 +1,44 @@
1
+ import type { NativeKit } from '../core/NativeKit';
2
+ import type { Unsubscribe } from '../core/types';
3
+
4
+ /** Payload of {@link KeyboardModule.onShow}: the on-screen keyboard's height in CSS pixels,
5
+ * so the page can pad/scroll content above it. */
6
+ export interface KeyboardInfo {
7
+ /** Keyboard height in CSS px (0 when unknown). */
8
+ height: number;
9
+ }
10
+
11
+ /**
12
+ * Software-keyboard control + observation — ONE API across platforms.
13
+ *
14
+ * The classic webview pain: the keyboard slides up and covers the focused input.
15
+ * Native emits `show` with the keyboard height (CSS px) and `hide`, so the page can
16
+ * lift content above it; `hide()` dismisses the keyboard programmatically.
17
+ *
18
+ * Native (iOS `UIKeyboardWillShow/Hide`, Android global-layout/IME insets) reports
19
+ * `capability === 'native'`. Web maps to the VisualViewport API where present
20
+ * (`'web'`), else `'none'`. Branch on {@link capability}, not try/catch.
21
+ */
22
+ export class KeyboardModule {
23
+ constructor(private kit: NativeKit) {}
24
+
25
+ /** 'native' on a shell · 'web' where VisualViewport exists · else 'none'. */
26
+ get capability() {
27
+ return this.kit.capability('keyboard');
28
+ }
29
+
30
+ /** Dismiss the software keyboard (resign first responder / hide the IME). */
31
+ hide(): Promise<void> {
32
+ return this.kit.invoke('keyboard.hide');
33
+ }
34
+
35
+ /** Keyboard shown — payload carries its height in CSS pixels. */
36
+ onShow(cb: (info: KeyboardInfo) => void): Unsubscribe {
37
+ return this.kit.on('keyboard.show', (p) => cb(p as KeyboardInfo));
38
+ }
39
+
40
+ /** Keyboard dismissed. */
41
+ onHide(cb: () => void): Unsubscribe {
42
+ return this.kit.on('keyboard.hide', () => cb());
43
+ }
44
+ }