Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eleven-ducks-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@telegram-apps/sdk": minor
---

Add functionality related to the mini apps active state.
5 changes: 5 additions & 0 deletions .changeset/strong-scissors-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@telegram-apps/bridge": minor
---

Add `visibility_changed` event.
23 changes: 22 additions & 1 deletion apps/docs/packages/telegram-apps-sdk/2-x/components/mini-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ if (miniApp.setBackgroundColor.isAvailable()) {
```

```ts [Functions]
import {
import {
setMiniAppBackgroundColor,
miniAppBackgroundColor,
} from '@telegram-apps/sdk';
Expand All @@ -172,6 +172,27 @@ if (setMiniAppBackgroundColor.isAvailable()) {

:::

## Active State

The mini application becomes inactive if it is wrapped into the bottom native Telegram client tray
or if the currently active tab of the mini apps browser is changed to another one.

To track if the mini application is currently active, use the `isActive` signal.

::: code-group

```ts [Variable]
miniApp.isActive();
```

```ts [Functions]
import { isMiniAppActive } from '@telegram-apps/sdk';

isMiniAppActive()
```

:::

## Methods

### `close`
Expand Down
13 changes: 13 additions & 0 deletions apps/docs/platform/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,19 @@ user started dragging the application or called the expansion method.
> application window. You should probably use a stable height instead of the current one, or handle
> this problem in another way.

### `visibility_changed`

Available since: **v8.0**

Active state assumes that the native Telegram client is currently working with the
current mini application. It is important to note that this is not related to the
mini application’s visibility, but rather its selection among other currently opened
mini applications.

| Field | Type | Description |
|------------|-----------|---------------------------------------------------|
| is_visible | `boolean` | Indicates if the application is currently active. |

### `write_access_requested`

Available since: **v6.9**
Expand Down
16 changes: 16 additions & 0 deletions packages/bridge/src/events/types/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,22 @@ export interface Events {
*/
is_state_stable: boolean;
};
/**
* Occurs whenever the mini app becomes active or inactive.
*
* Active state assumes that the native Telegram client is currently working with the
* current mini application. It is important to note that this is not related to the
* mini application’s visibility, but rather its selection among other currently opened
* mini applications.
* @since v8.0
* @see https://docs.telegram-mini-apps.com/platform/events#visibility_changed
*/
visibility_changed: {
/**
* Indicates if the application is currently active.
*/
is_visible: boolean;
};
/**
* Application received write access request status.
* @since v6.9
Expand Down
2 changes: 2 additions & 0 deletions packages/sdk/src/scopes/components/mini-app/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ export {
} from './methods.js';
export {
backgroundColor as miniAppBackgroundColor,
backgroundColorRGB as miniAppBackgroundColorRGB,
bottomBarColor as miniAppBottomBarColor,
bottomBarColorRGB as miniAppBottomBarColorRGB,
headerColor as miniAppHeaderColor,
headerColorRGB as miniAppHeaderColorRGB,
isMounted as isMiniAppMounted,
isCssVarsBound as isMiniAppCssVarsBound,
isDark as isMiniAppDark,
isActive as isMiniAppActive,
state as miniAppState,
} from './signals.js';
export type {
Expand Down
17 changes: 16 additions & 1 deletion packages/sdk/src/scopes/components/mini-app/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ import {
deleteCssVar,
setCssVar,
supports,
on,
off,
type EventListener,
type RGB,
type BottomBarColor,
type BackgroundColor, MethodName,
type BackgroundColor,
type MethodName,
} from '@telegram-apps/bridge';
import { isRGB } from '@telegram-apps/transformers';
import { isPageReload } from '@telegram-apps/navigation';
Expand All @@ -31,6 +35,7 @@ import {
headerColorRGB,
bottomBarColorRGB,
backgroundColorRGB,
isActive,
} from './signals.js';
import type { GetCssVarNameFn, HeaderColor, State } from './types.js';

Expand All @@ -39,6 +44,7 @@ type StorageValue = State;
const SET_BG_COLOR_METHOD = 'web_app_set_background_color';
const SET_BOTTOM_BAR_COLOR_METHOD = 'web_app_set_bottom_bar_color';
const SET_HEADER_COLOR_METHOD = 'web_app_set_header_color';
const VISIBILITY_CHANGED_EVENT = 'visibility_changed';
const COMPONENT_NAME = 'miniApp';

const isSupportedSchema = {
Expand Down Expand Up @@ -138,6 +144,11 @@ export const close = wrapBasic('close', (returnBack?: boolean): void => {
postEvent('web_app_close', { return_back: returnBack });
});

const onVisibilityChanged: EventListener<'visibility_changed'> = (data) => {
isActive.set(data.is_visible);
saveState();
};

/**
* Mounts the component.
*
Expand Down Expand Up @@ -165,6 +176,9 @@ export const mount = wrapSupported(
setBackgroundColor.ifAvailable(s ? s.backgroundColor : 'bg_color');
setBottomBarColor.ifAvailable(s ? s.bottomBarColor : 'bottom_bar_bg_color');
setHeaderColor.ifAvailable(s ? s.headerColor : 'bg_color');
isActive.set(s ? s.isActive : true);

on(VISIBILITY_CHANGED_EVENT, onVisibilityChanged);

isMounted.set(true);
}
Expand Down Expand Up @@ -280,5 +294,6 @@ export const setHeaderColor = wrapComplete(
* Unmounts the component, removing the listener, saving the component state in the local storage.
*/
export function unmount(): void {
off(VISIBILITY_CHANGED_EVENT, onVisibilityChanged);
isMounted.set(false);
}
6 changes: 6 additions & 0 deletions packages/sdk/src/scopes/components/mini-app/signals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,17 @@ export const isDark = computed(() => {
return color ? isColorDark(color) : false;
});

/**
* Signal indicating if the mini app is currently active.
*/
export const isActive = signal(true);

/**
* Complete component state.
*/
export const state = computed<State>(() => ({
backgroundColor: backgroundColor(),
bottomBarColor: bottomBarColor(),
headerColor: headerColor(),
isActive: isActive(),
}));
1 change: 1 addition & 0 deletions packages/sdk/src/scopes/components/mini-app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface State {
backgroundColor: BackgroundColor;
bottomBarColor: BottomBarColor;
headerColor: HeaderColor;
isActive: boolean;
}

export interface GetCssVarNameFn {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
CSA_CHANGED_EVENT,
FS_CHANGED_EVENT,
SA_CHANGED_EVENT,
VIEWPORT_CHANGED_EVENT
VIEWPORT_CHANGED_EVENT,
} from '../../const.js';
import { isMounted, mountPromise, mountError } from '../../signals/mounting.js';
import { getStateFromStorage, setState } from '../../signals/state.js';
Expand All @@ -20,7 +20,12 @@ import { requestSafeAreaInsets } from '../static/requestSafeAreaInsets.js';
import { requestViewport } from '../static/requestViewport.js';
import type { State } from '../../types.js';

import { onContentSafeAreaChanged, onFullscreenChanged, onSafeAreaChanged, onViewportChanged } from './shared.js';
import {
onContentSafeAreaChanged,
onFullscreenChanged,
onSafeAreaChanged,
onViewportChanged,
} from './shared.js';

/**
* Mounts the Viewport component.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { off } from '@telegram-apps/bridge';

import { CSA_CHANGED_EVENT, FS_CHANGED_EVENT, SA_CHANGED_EVENT, VIEWPORT_CHANGED_EVENT } from '../../const.js';
import {
CSA_CHANGED_EVENT,
FS_CHANGED_EVENT,
SA_CHANGED_EVENT,
VIEWPORT_CHANGED_EVENT,
} from '../../const.js';
import { isMounted, mountPromise } from '../../signals/mounting.js';

import { onContentSafeAreaChanged, onFullscreenChanged, onSafeAreaChanged, onViewportChanged } from './shared.js';
import {
onContentSafeAreaChanged,
onFullscreenChanged,
onSafeAreaChanged,
onViewportChanged,
} from './shared.js';

/**
* Unmounts the Viewport.
Expand Down
23 changes: 0 additions & 23 deletions playgrounds/react/src/components/App.tsx

This file was deleted.

16 changes: 16 additions & 0 deletions playgrounds/react/src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useLaunchParams, miniApp } from '@telegram-apps/sdk-react';
import { AppRoot } from '@telegram-apps/telegram-ui';
import { Outlet } from 'react-router-dom';

export function Layout() {
const lp = useLaunchParams();

return (
<AppRoot
appearance={miniApp.isDark() ? 'dark' : 'light'}
platform={['macos', 'ios'].includes(lp.platform) ? 'ios' : 'base'}
>
<Outlet/>
</AppRoot>
);
}
4 changes: 2 additions & 2 deletions playgrounds/react/src/components/Root.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TonConnectUIProvider } from '@tonconnect/ui-react';

import { App } from '@/components/App.tsx';
import { ErrorBoundary } from '@/components/ErrorBoundary.tsx';
import Router from "@/navigation/Router.tsx";

function ErrorBoundaryError({ error }: { error: unknown }) {
return (
Expand All @@ -26,7 +26,7 @@ export function Root() {
<TonConnectUIProvider
manifestUrl={new URL('/tonconnect-manifest.json', window.location.href).toString()}
>
<App/>
<Router/>
</TonConnectUIProvider>
</ErrorBoundary>
);
Expand Down
25 changes: 25 additions & 0 deletions playgrounds/react/src/navigation/Router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {createBrowserRouter, RouterProvider} from "react-router-dom";
import {Layout} from "@/components/Layout.tsx";
import {IndexPage} from "@/pages/IndexPage/IndexPage.tsx";
import {InitDataPage} from "@/pages/InitDataPage.tsx";
import {LaunchParamsPage} from "@/pages/LaunchParamsPage.tsx";
import {ThemeParamsPage} from "@/pages/ThemeParamsPage.tsx";
import {ViewportParamsPage} from "@/pages/ViewportParamsPage.tsx";
import {TONConnectPage} from "@/pages/TONConnectPage/TONConnectPage.tsx";

export default function Router() {
const router = createBrowserRouter([{
path: "/",
element: <Layout/>,
children: [
{index: true, element: <IndexPage/>},
{path: "/init-data", element: <InitDataPage/>},
{path: "/launch-params", element: <LaunchParamsPage/>},
{path: "/theme-params", element: <ThemeParamsPage/>},
{path: "/viewport-params", element: <ViewportParamsPage/>},
{path: '/ton-connect', element: <TONConnectPage/>}
],
}]);

return <RouterProvider router={router}/>;
}
46 changes: 0 additions & 46 deletions playgrounds/react/src/navigation/routes.tsx

This file was deleted.