npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@capgo/capacitor-screen-orientation

v8.4.3

Published

Screen orientation plugin with support for bypassing orientation lock

Readme

@capgo/capacitor-screen-orientation

🤖 Building with an AI coding agent? Install Capgo skills so Claude Code, Codex, Cursor, Copilot, or Gemini CLI know how to install and use @capgo/capacitor-screen-orientation in your Capacitor app: npx skills add Cap-go/capgo-skills. Then ask your agent: "Add @capgo/capacitor-screen-orientation to my Capacitor app and wire it up." Agent setup guides · Hosted MCP

Screen orientation plugin with support for detecting true physical device orientation

Features

  • 📱 Full Screen Orientation API support
  • 🔍 Detect true physical device orientation using motion sensors (even when orientation lock is enabled)
    • iOS: Uses Core Motion framework
    • Android: Uses accelerometer sensor
  • 🔒 Detect if orientation lock is enabled by comparing physical vs UI orientation
  • 🔄 Real-time orientation change detection
  • 🎯 Lock orientation to specific modes
  • 📖 Foldable devices (Galaxy Z Fold/Flip, Pixel Fold, Surface Duo, iPhone Duo): posture, hinge, size classes, and live fold updates
  • 🌐 Web platform support

Important Note: This plugin can detect the physical orientation of the device using motion sensors, but it cannot bypass the UI orientation lock. The screen will still respect the user's orientation lock setting. This is useful for knowing how the device is physically held vs. how the UI is displayed.

Foldable devices

Foldables give you more than a wider canvas: the hinge, cover screen, and posture change how people hold the phone. This plugin reports that state in JavaScript so you can adapt layouts, controls, and media without guessing from window size alone. It targets Galaxy Z Fold and Flip, Pixel Fold, Surface Duo, and iPhone Duo (iOS 27.1+), alongside the orientation APIs already in this package.

What you get

  • Device and posture: isDeviceFoldable(), getFoldState() (flat / half-opened, posture flat / tabletop / book, hinge bounds, separating fold, occluded hinge area when applicable)
  • Hinge angle: getHingeAngle() and hingeAngleChange (native sensor on Android; iPhone Duo on iOS 27.1+; null on web)
  • Layout signals: getSizeClass() and sizeClassChange (Apple compact/regular plus Material width/height classes)
  • iPhone Duo extras: activeDisplay (inner / outer), getReservedRegions(), getBarPlacement(), setVerticalBarBehavior()
  • Live updates: foldStateChange when posture or hinge geometry changes the reported fold state (rotation alone may not fire if devicePosture is available but usable viewport segments are not)

Good fits

  • Two-pane layouts that follow the hinge instead of a fixed breakpoint
  • Toolbar, gamepad, or reader chrome that moves when the user folds to tabletop or book
  • Cover-screen vs inner-screen flows on foldables and iPhone Duo
  • Size-class driven UI that reacts when the user opens the device fully

Quick start

import { ScreenOrientation } from '@capgo/capacitor-screen-orientation';

const fold = await ScreenOrientation.getFoldState();
console.log(fold.posture, fold.hingeBounds);

await ScreenOrientation.addListener('foldStateChange', (state) => {
  document.documentElement.dataset.posture = state.posture;
});

iPhone Duo (iOS 27.1+)

| Pose | posture | Hinge (typical) | | --- | --- | --- | | Open flat, or closed on the outer display | flat | 180 open, 0 closed | | Held half-open like a book | book | vertical hinge, about 20 to 160 | | Propped half-open like a laptop | tabletop | horizontal hinge |

Non-folding iPhones and iOS before 27.1 stay flat with no hinge. Android uses Jetpack WindowManager; web uses the Device Posture API and viewport segments when the browser exposes them (hinge angle stays null on web).

Fold API (full types below): isDeviceFoldable(), getFoldState(), getHingeAngle(), getReservedRegions(), getBarPlacement(), setVerticalBarBehavior(), getSizeClass(), listeners foldStateChange, hingeAngleChange, sizeClassChange.

Documentation

The most complete doc is available here: https://capgo.app/docs/plugins/screen-orientation/

Compatibility

| Plugin version | Capacitor compatibility | Maintained | | -------------- | ----------------------- | ---------- | | v8.*.* | v8.*.* | ✅ | | v7.*.* | v7.*.* | On demand | | v6.*.* | v6.*.* | ❌ | | v5.*.* | v5.*.* | ❌ |

Note: The major version of this plugin follows the major version of Capacitor. Use the version that matches your Capacitor installation (e.g., plugin v8 for Capacitor 8). Only the latest major version is actively maintained.

Install

You can use our AI-Assisted Setup to install the plugin. Add the Capgo skills to your AI tool using the following command:

npx skills add https://github.com/cap-go/capacitor-skills --skill capacitor-plugins

Then use the following prompt:

Use the `capacitor-plugins` skill from `cap-go/capacitor-skills` to install the `@capgo/capacitor-screen-orientation` plugin in my project.

If you prefer Manual Setup, install the plugin by running the following commands and follow the platform-specific instructions below:

npm install @capgo/capacitor-screen-orientation
npx cap sync

iOS Configuration

To detect physical device orientation using motion sensors on iOS, you need to add the Motion Usage Description to your Info.plist:

<key>NSMotionUsageDescription</key>
<string>This app uses motion sensors to detect the true physical orientation of your device.</string>

Locking the screen orientation only applies to the Capacitor view controller by default. To also apply the lock to presented view controllers (for example, ones shown by the Browser plugin), add this to your app's AppDelegate.swift:

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
  return UIInterfaceOrientationMask(rawValue: (self.window!.rootViewController as! CAPBridgeViewController).supportedInterfaceOrientations.rawValue)
}

Make sure UISupportedInterfaceOrientations in Info.plist includes every orientation you want to lock to (for example landscape left/right when locking to landscape).

Usage

import { ScreenOrientation } from '@capgo/capacitor-screen-orientation';

// Get current orientation
const result = await ScreenOrientation.orientation();
console.log('Current orientation:', result.type);

// Lock to landscape
await ScreenOrientation.lock({ orientation: 'landscape' });

// Lock UI orientation and track physical orientation via motion sensors
// Note: UI will still respect user's orientation lock, but you'll get physical orientation events
await ScreenOrientation.lock({
  orientation: 'portrait',
  bypassOrientationLock: true
});

// Listen for orientation changes
const listener = await ScreenOrientation.addListener(
  'screenOrientationChange',
  (result) => {
    console.log('Orientation changed:', result.type);
  }
);

// Unlock orientation
await ScreenOrientation.unlock();

// Start motion-based tracking to detect physical device orientation
// This uses motion sensors to track how the device is actually held
await ScreenOrientation.startOrientationTracking({
  bypassOrientationLock: true
});

// Check if device orientation lock is enabled
// Compares physical device orientation (from sensors) with UI orientation (from system)
const lockStatus = await ScreenOrientation.isOrientationLocked();
if (lockStatus.locked) {
  console.log('Orientation lock is ON!');
  console.log('Physical device orientation:', lockStatus.physicalOrientation);
  console.log('UI orientation:', lockStatus.uiOrientation);
} else {
  console.log('Orientation lock is OFF or motion tracking not active');
}

// Stop motion-based tracking
await ScreenOrientation.stopOrientationTracking();

// Remove listener
await listener.remove();

// iPhone Duo and Android foldables. Flat on phones that do not fold.
const { posture, activeDisplay } = await ScreenOrientation.getFoldState();
const { angle, status } = await ScreenOrientation.getHingeAngle();
const { verticalBarEdge, inset } = await ScreenOrientation.getBarPlacement();
const { widthClass } = await ScreenOrientation.getSizeClass();

API

Capacitor Screen Orientation Plugin interface.

Provides methods to detect and control screen orientation, with support for detecting true physical device orientation using motion sensors.

orientation()

orientation() => Promise<ScreenOrientationResult>

Get the current screen orientation.

Returns the current orientation of the device screen.

Returns: Promise<ScreenOrientationResult>

Since: 1.0.0


lock(...)

lock(options: OrientationLockOptions) => Promise<void>

Lock the screen orientation to a specific type.

Locks the screen to the specified orientation. On iOS, if bypassOrientationLock is true, it will also start tracking physical device orientation using motion sensors.

Note: The UI will still respect the user's orientation lock setting. Motion tracking allows you to detect how the device is physically held even when the UI doesn't rotate.

On iOS 15 there is no public API to force a rotation, so the lock restricts the allowed orientations and the UI rotates into them when the device orientation allows it. On iOS 16 and later the rotation is requested immediately.

| Param | Type | Description | | ------------- | ------------------------------------------------------------------------- | ------------------------------------ | | options | OrientationLockOptions | Options for locking the orientation. |

Since: 1.0.0


unlock()

unlock() => Promise<void>

Unlock the screen orientation.

Allows the screen to rotate freely based on device position. Also stops any motion-based orientation tracking if it was enabled.

Since: 1.0.0


startOrientationTracking(...)

startOrientationTracking(options?: StartOrientationTrackingOptions | undefined) => Promise<void>

Start tracking device orientation using motion sensors.

This method is useful when you want to track the device's physical orientation independently from the screen orientation lock. It uses Core Motion on iOS to detect orientation changes.

| Param | Type | Description | | ------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------ | | options | StartOrientationTrackingOptions | Options for starting orientation tracking. |

Since: 1.0.0


stopOrientationTracking()

stopOrientationTracking() => Promise<void>

Stop tracking device orientation using motion sensors.

Stops the motion-based orientation tracking if it was started.

Since: 1.0.0


isOrientationLocked()

isOrientationLocked() => Promise<OrientationLockStatusResult>

Check if device orientation lock is currently enabled.

This method compares the physical device orientation (from motion sensors) with the UI orientation. If they differ, orientation lock is enabled.

Note: This requires motion tracking to be active via startOrientationTracking() or lock() with bypassOrientationLock: true. Works on both iOS (Core Motion) and Android (Accelerometer).

Returns: Promise<OrientationLockStatusResult>

Since: 1.0.0


isDeviceFoldable()

isDeviceFoldable() => Promise<DeviceFoldableResult>

Whether this device folds, and whether it can stand half-open like a laptop.

On web, both flags are false when the browser lacks fold APIs or the current reading shows no fold (devicePosture.type is continuous with a single viewport segment). foldable becomes true when posture is folded or the viewport splits into two segments. On web, supportsTabletop reflects the current reading (for example, false in a book pose) rather than permanent device capability.

On Android, both flags are false on phones that do not fold. On iOS, both are false except on iPhone Duo (iOS 27.1 or later).

Returns: Promise<DeviceFoldableResult>

Since: 8.2.0


getFoldState()

getFoldState() => Promise<FoldState>

Read the current fold.

Resolves to a flat state when the device has no fold. On web, uses navigator.devicePosture and window.viewport.segments when the browser provides them.

Returns: Promise<FoldState>

Since: 8.2.0


getHingeAngle()

getHingeAngle() => Promise<HingeAngleResult>

Read the hinge angle in degrees.

0 is closed and 180 is flat. angle is null without a hinge sensor.

Returns: Promise<HingeAngleResult>

Since: 8.2.0


getReservedRegions()

getReservedRegions() => Promise<{ regions: ReservedRegion[]; }>

Read the fold and anything covering the screen.

iPhone Duo reports the fold, the vertical status bar area, and the under-display camera. Resolves to an empty list on Android, web, and iOS before 27.1.

Returns: Promise<{ regions: ReservedRegion[]; }>

Since: 8.2.0


getBarPlacement()

getBarPlacement() => Promise<BarPlacement>

Read where iPhone Duo puts native tab bars and toolbars.

verticalBarEdge is null when bars stay horizontal, and always on Android and web.

Returns: Promise<BarPlacement>

Since: 8.2.0


setVerticalBarBehavior(...)

setVerticalBarBehavior(options: { behavior: 'automatic' | 'disabled'; }) => Promise<{ applied: boolean; }>

Choose whether iPhone Duo may move this app's bars to the side.

Resolves to { applied: false } unless the app's bridge view controller opts in. Android and web always resolve to { applied: false }.

| Param | Type | Description | | ------------- | ----------------------------------------------------- | ------------------------------------------------------------------------ | | options | { behavior: 'automatic' | 'disabled'; } | automatic lets the system move bars. disabled keeps them horizontal. |

Returns: Promise<{ applied: boolean; }>

Since: 8.2.0


getSizeClass()

getSizeClass() => Promise<SizeClass>

Read the window size classes.

Returns: Promise<SizeClass>

Since: 8.2.0


addListener('screenOrientationChange', ...)

addListener(eventName: 'screenOrientationChange', listenerFunc: (result: ScreenOrientationResult) => void) => Promise<PluginListenerHandle>

| Param | Type | | ------------------ | ------------------------------------------------------------------------------------------------ | | eventName | 'screenOrientationChange' | | listenerFunc | (result: ScreenOrientationResult) => void |

Returns: Promise<PluginListenerHandle>


addListener('foldStateChange', ...)

addListener(eventName: 'foldStateChange', listenerFunc: (state: FoldState) => void) => Promise<PluginListenerHandle>

Listen for fold changes.

On a foldable this also fires when the device rotates, because the hinge bounds rotate with the window.

| Param | Type | Description | | ------------------ | ------------------------------------------------------------------- | ------------------------------------------ | | eventName | 'foldStateChange' | The event name. Must be 'foldStateChange'. | | listenerFunc | (state: FoldState) => void | Callback invoked with the new fold state. |

Returns: Promise<PluginListenerHandle>

Since: 8.2.0


addListener('hingeAngleChange', ...)

addListener(eventName: 'hingeAngleChange', listenerFunc: (event: HingeAngleResult) => void) => Promise<PluginListenerHandle>

Listen for hinge angle changes.

On Android the hinge sensor runs only while at least one listener is registered. On iOS this fires on iPhone Duo (iOS 27.1 or later). On web, hinge angle stays null; use foldStateChange for posture updates from the Device Posture API.

| Param | Type | Description | | ------------------ | --------------------------------------------------------------------------------- | ------------------------------------------- | | eventName | 'hingeAngleChange' | The event name. Must be 'hingeAngleChange'. | | listenerFunc | (event: HingeAngleResult) => void | Callback invoked with the angle in degrees. |

Returns: Promise<PluginListenerHandle>

Since: 8.2.0


addListener('sizeClassChange', ...)

addListener(eventName: 'sizeClassChange', listenerFunc: (sizeClass: SizeClass) => void) => Promise<PluginListenerHandle>

Listen for size class changes, such as unfolding, rotating, or resizing.

| Param | Type | Description | | ------------------ | ----------------------------------------------------------------------- | ------------------------------------------ | | eventName | 'sizeClassChange' | The event name. Must be 'sizeClassChange'. | | listenerFunc | (sizeClass: SizeClass) => void | Callback invoked with the new size class. |

Returns: Promise<PluginListenerHandle>

Since: 8.2.0


removeAllListeners()

removeAllListeners() => Promise<void>

Remove all listeners for this plugin.

Removes all registered event listeners.

Since: 1.0.0


getPluginVersion()

getPluginVersion() => Promise<{ version: string; }>

Get the native plugin version.

Returns the current version of the native plugin implementation.

Returns: Promise<{ version: string; }>

Since: 1.0.0


Interfaces

ScreenOrientationResult

Result returned by the orientation() method.

| Prop | Type | Description | Since | | ---------- | ----------------------------------------------------------- | ----------------------------- | ----- | | type | OrientationType | The current orientation type. | 1.0.0 |

OrientationLockOptions

Options for locking the screen orientation.

| Prop | Type | Description | Default | Since | | --------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- | | orientation | OrientationLockType | The orientation type to lock to. | | 1.0.0 | | bypassOrientationLock | boolean | Whether to track physical device orientation using motion sensors. When true, uses device motion sensors to detect the true physical orientation of the device, even when the device orientation lock is enabled. Important: This does NOT bypass the UI orientation lock. The screen will still respect the user's orientation lock setting. This option only affects orientation detection/tracking - you'll receive orientation change events based on how the device is physically held, but the UI will not rotate if orientation lock is enabled. Supported on iOS (Core Motion) and Android (Accelerometer). | false | 1.0.0 |

StartOrientationTrackingOptions

Options for starting orientation tracking using motion sensors.

| Prop | Type | Description | Default | Since | | --------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- | | bypassOrientationLock | boolean | Whether to track physical device orientation using motion sensors. When true, uses device motion sensors to detect the true physical orientation of the device, even when the device orientation lock is enabled. Important: This does NOT bypass the UI orientation lock. This only enables detection of the physical orientation. Supported on iOS (Core Motion) and Android (Accelerometer). | false | 1.0.0 |

OrientationLockStatusResult

Result returned by the isOrientationLocked() method.

| Prop | Type | Description | Since | | ------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | locked | boolean | Whether the device orientation lock is currently enabled. This is determined by comparing the physical device orientation (from motion sensors) with the UI orientation. If they differ, orientation lock is enabled. Available on iOS (Core Motion) and Android (Accelerometer) when motion tracking is active. | 1.0.0 | | physicalOrientation | OrientationType | The physical orientation of the device from motion sensors. Available when motion tracking is active (iOS and Android). | 1.0.0 | | uiOrientation | OrientationType | The current UI orientation reported by the system. | 1.0.0 |

DeviceFoldableResult

Whether this device can fold.

| Prop | Type | Description | Since | | ---------------------- | -------------------- | --------------------------------------------- | ----- | | foldable | boolean | Whether the device has a fold. | 8.2.0 | | supportsTabletop | boolean | Whether it can stand half-open like a laptop. | 8.2.0 |

FoldState

Current fold of the window.

Phones that do not fold, and iOS before 27.1, resolve to a flat state. iPhone Duo reports book, tabletop, or flat, and which display is in front.

| Prop | Type | Description | Since | | ---------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ----- | | state | FoldStateValue | Posture of the fold. | 8.2.0 | | isSeparating | boolean | Whether the fold splits the web view into two areas. | 8.2.0 | | posture | FoldPosture | How the device is held. | 8.2.0 | | hingeOrientation | HingeOrientation | Direction of the hinge. Omitted when there is no fold. | 8.2.0 | | hingeBounds | FoldBounds | Position of the fold in CSS pixels. Omitted when there is no fold. | 8.2.0 | | occludedBounds | FoldBounds | Area the hinge covers. Present when the hinge has a physical gap. | 8.2.0 | | activeDisplay | 'inner' | 'outer' | Which display is showing the app on iPhone Duo. Omitted on Android, web, and iOS before 27.1. | 8.2.0 | | hingeMargins | { top: number; right: number; bottom: number; left: number; } | Space the system keeps clear around the fold, in CSS pixels. iPhone Duo reports 20 on each side of a vertical fold. | 8.2.0 | | cameraBounds | FoldBounds[] | Areas covering the display, such as the iPhone Duo camera. Omitted when there are none. | 8.2.0 |

FoldBounds

A rectangle in CSS pixels, relative to the web view.

| Prop | Type | | ------------ | ------------------- | | x | number | | y | number | | width | number | | height | number |

HingeAngleResult

Angle between the two halves of a foldable, in degrees.

0 is closed and 180 is flat. angle is null when the device has no hinge sensor.

| Prop | Type | Description | Since | | ------------ | --------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----- | | angle | number | null | Hinge angle in degrees, or null when no sensor is available. | 8.2.0 | | status | 'closed' | 'partiallyOpen' | 'fullyOpen' | null | How far open iPhone Duo is, as the system sees it. null on Android and web. | 8.2.0 |

ReservedRegion

A region the system reserves on iPhone Duo.

division is the fold. occlusion is something covering the display, such as the camera.

| Prop | Type | Description | Since | | -------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ----- | | kind | 'division' | 'occlusion' | What the region is. | 8.2.0 | | isActive | boolean | Whether the region applies right now. An inactive division is a flat fold. | 8.2.0 | | x | number | Left edge, in CSS pixels. | 8.2.0 | | y | number | Top edge, in CSS pixels. | 8.2.0 | | width | number | Width in CSS pixels. | 8.2.0 | | height | number | Height in CSS pixels. | 8.2.0 | | margins | { top: number; right: number; bottom: number; left: number; } | Space to keep clear around the region, in CSS pixels. | 8.2.0 |

BarPlacement

Where iPhone Duo places native bars.

| Prop | Type | Description | Since | | --------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------- | ----- | | verticalBarEdge | 'leading' | 'trailing' | null | The edge the system moves tab bars and toolbars to. null when they stay horizontal. | 8.2.0 | | inset | number | How much room the vertical bar takes, in points. 0 when bars stay horizontal. | 8.2.0 |

SizeClass

Window size classes.

horizontal and vertical follow Apple's compact/regular split. On Android and web, regular starts at 600 CSS pixels wide and 480 tall. widthClass and heightClass follow Material window size classes.

| Prop | Type | Description | Since | | ----------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----- | | horizontal | 'compact' | 'regular' | Width size class. compact on a phone, regular on the inner display of a foldable, a tablet, or a window at least 600 CSS pixels wide. | 8.2.0 | | vertical | 'compact' | 'regular' | Height size class. regular starts at 480 CSS pixels. | 8.2.0 | | widthClass | 'compact' | 'medium' | 'expanded' | 'large' | 'extraLarge' | Material window width class. | 8.2.0 | | heightClass | 'compact' | 'medium' | 'expanded' | Material window height class. | 8.2.0 |

PluginListenerHandle

| Prop | Type | | ------------ | ----------------------------------------- | | remove | () => Promise<void> |

Type Aliases

OrientationType

Orientation type that describes the orientation state of the device.

'portrait-primary' | 'portrait-secondary' | 'landscape-primary' | 'landscape-secondary'

OrientationLockType

Orientation lock type that can be used to lock the device orientation.

'any' | 'natural' | 'landscape' | 'portrait' | 'portrait-primary' | 'portrait-secondary' | 'landscape-primary' | 'landscape-secondary'

FoldStateValue

How open a foldable is.

closed is reserved. A device shut onto its cover display reports flat.

'flat' | 'half-opened'

FoldPosture

How a half-open foldable is held.

tabletop is a horizontal hinge, like a laptop. book is a vertical hinge.

'flat' | 'tabletop' | 'book'

HingeOrientation

Direction of the hinge relative to the window.

'horizontal' | 'vertical'

Foldable support adapted from erkamyaman/capacitor-foldable (MIT, Copyright (c) Erkam Yaman).