Repository: vkruglikov/react-telegram-web-app Branch: master Commit: fc30a5c41ca4 Files: 93 Total size: 135.7 KB Directory structure: gitextract_e2fmb7zc/ ├── .changeset/ │ └── config.json ├── .eslintignore ├── .eslintrc.json ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ └── bug_report.md │ └── workflows/ │ ├── contributing.yml │ ├── release.yml │ ├── static.yml │ └── tests.yml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── demo/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public/ │ │ └── index.html │ ├── src/ │ │ ├── BackButtonDemo.tsx │ │ ├── ExpandDemo.tsx │ │ ├── HapticFeedbackDemo.tsx │ │ ├── MainButtonDemo.tsx │ │ ├── ScanQrPopupDemo.tsx │ │ ├── ShowPopupDemo.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ ├── react-app-env.d.ts │ │ ├── spy.mjs │ │ └── useBetaVersion.ts │ └── tsconfig.json ├── docs/ │ ├── .nojekyll │ ├── README.md │ └── interfaces/ │ ├── BackButtonProps.md │ ├── MainButtonProps.md │ ├── ScanQrPopupParams.md │ ├── SettingsButtonProps.md │ ├── ShowPopupButton.md │ ├── ShowPopupParams.md │ └── ThemeParams.md ├── global.d.ts ├── jest.config.json ├── package.json ├── src/ │ ├── BackButton.tsx │ ├── MainButton.tsx │ ├── SettingsButton.tsx │ ├── WebAppProvider.tsx │ ├── core/ │ │ ├── context.ts │ │ ├── index.ts │ │ ├── twa-types/ │ │ │ ├── WebApp.d.ts │ │ │ ├── WebAppVersion_6.1.d.ts │ │ │ ├── WebAppVersion_6.2.d.ts │ │ │ ├── WebAppVersion_6.4.d.ts │ │ │ ├── WebAppVersion_6.7.d.ts │ │ │ ├── WebAppVersion_6.9.d.ts │ │ │ ├── WebAppVersion_7.0.d.ts │ │ │ └── index.d.ts │ │ ├── useAsyncMode.ts │ │ ├── useSmoothButtonsTransition.ts │ │ └── useWebApp.ts │ ├── index.ts │ ├── useCloudStorage.ts │ ├── useExpand.ts │ ├── useHapticFeedback.ts │ ├── useInitData.ts │ ├── useReadTextFromClipboard.ts │ ├── useScanQrPopup.ts │ ├── useShowPopup.ts │ ├── useSwitchInlineQuery.ts │ ├── useThemeParams.ts │ └── useWebApp.ts ├── tests/ │ ├── BackButton.test.tsx │ ├── MainButton.test.tsx │ ├── __snapshots__/ │ │ └── package.test.ts.snap │ ├── core/ │ │ ├── __mocks__/ │ │ │ └── useWebApp.ts │ │ └── useSmoothButtonsTransition.test.ts │ ├── package.test.ts │ ├── setupTests.ts │ ├── useCloudStorage.test.ts │ ├── useExpand.test.tsx │ ├── useHapticFeedback.test.ts │ ├── useInitData.test.ts │ ├── useReadTextFromClipboard.test.ts │ ├── useScanQrPopup.test.ts │ ├── useShowPopup.test.ts │ ├── useSwitchInlineQuery.test.ts │ ├── useThemeParams.test.ts │ └── utils.ts ├── tsconfig.json └── typedoc.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .changeset/config.json ================================================ { "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [], "linked": [], "access": "restricted", "baseBranch": "master", "updateInternalDependencies": "patch", "ignore": [] } ================================================ FILE: .eslintignore ================================================ /tests/**/*.* ================================================ FILE: .eslintrc.json ================================================ { "root": true, "env": { "browser": true, "es2021": true }, "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react/recommended", "plugin:react-hooks/recommended" ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, "plugins": [ "@typescript-eslint", "react" ], "rules": { "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "error", "@typescript-eslint/no-explicit-any": ["warn", { "ignoreRestArgs": true }] } } ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- Package version: React version: Telegram.WebApp version: ## Steps To Reproduce 1. 2. Link to code example: ## The current behavior ## The expected behavior ================================================ FILE: .github/workflows/contributing.yml ================================================ name: Generate Contributors Images on: push: branches: - master jobs: build: runs-on: ubuntu-latest steps: - name: Generate Contributors Images uses: jaywcjlove/github-action-contributors@main id: contributors with: filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\]) avatarSize: 42 - name: Modify README.md uses: jaywcjlove/github-action-modify-file-content@main with: path: README.md body: '${{steps.contributors.outputs.htmlList}}' ================================================ FILE: .github/workflows/release.yml ================================================ name: Release package on: push: branches: - master paths-ignore: - demo/** jobs: build: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@master with: # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - name: Use Node.js 16.x uses: actions/setup-node@v3 with: node-version: 16.x - name: Install Dependencies run: npm install - name: Publish uses: changesets/action@master with: # This expects you to have a script called release which does a build for your packages and calls changeset publish publish: npm run release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} ================================================ FILE: .github/workflows/static.yml ================================================ # Simple workflow for deploying static content to GitHub Pages name: Deploy static content to Pages on: # Runs on pushes targeting the default branch push: branches: ['master'] paths: - demo/** # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write # Allow one concurrent deployment concurrency: group: 'pages' cancel-in-progress: true jobs: # Single deploy job since we're just deploying deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Use Node.js 16.x uses: actions/setup-node@v3 with: node-version: 16.x - name: Install Dependencies and Build working-directory: './demo' run: npm install && npm run build - name: Setup Pages uses: actions/configure-pages@v2 - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: path: './demo/build/.' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 ================================================ FILE: .github/workflows/tests.yml ================================================ name: Run tests on: push: paths: - src/** - tests/** - jest.config.json jobs: test: runs-on: ubuntu-latest name: Run tests steps: - name: Checkout Repo uses: actions/checkout@master with: # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - name: Use Node.js 16.x uses: actions/setup-node@v3 with: node-version: 16.x - name: Install Dependencies run: npm install - name: Run tests run: npm run test:all - name: Run package tests run: npm run build && npm run test:package ================================================ FILE: .gitignore ================================================ /lib node_modules .idea ================================================ FILE: .husky/pre-commit ================================================ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" npx lint-staged ================================================ FILE: .nvmrc ================================================ v16.14.2 ================================================ FILE: .prettierignore ================================================ lib demo/build package.json package-lock.json ================================================ FILE: .prettierrc ================================================ { "singleQuote": true, "trailingComma": "all", "useTabs": false, "arrowParens": "avoid", "overrides": [ { "files": "package.json", "options": { "useTabs": false, "parser": "json-stringify" } } ] } ================================================ FILE: CHANGELOG.md ================================================ # @vkruglikov/react-telegram-web-app ## 2.2.0 ### Minor Changes - e3bcaf0: Add SettingsButton ## 2.1.9 ### Patch Changes - 5f49e9b: test fix ## 2.1.8 ### Patch Changes - f187824: test fix ## 2.1.7 ### Patch Changes - 94b5e28: Added experimental `async` mode ## 2.1.6 ### Patch Changes - 6631ede: useInitData returns undefined if window is unavailable ## 2.1.5 ### Patch Changes - 1e3beb1: Update docs ## 2.1.4 ### Patch Changes - f705506: Fix signature bug ## 2.1.3 ### Patch Changes - e4b17fb: Fix types ## 2.1.2 ### Patch Changes - e856e14: Fix bug with types ## 2.3.0 ### Minor Changes - c28cc2e: useWebApp hook ## 2.2.0 ### Minor Changes - 0339c94: Added `useCloudStorage` that provides `CloudStorage` object as Promise functions - 1589011: Added useInitData hook ## 2.1.1 ### Patch Changes - 54f5400: Fix smoothButtonsTransition restore state after refresh page ## 2.1.0 ### Minor Changes - bcf3a0e: Added props `disabled` to MainButton component. It is just an alias on the `MainButtonProps.disable` Props `disable` marked as deprecated and will be removed ### Patch Changes - 05d7b34: - Reduced default value for `smoothButtonsTransitionMs` ## 2.0.1 ### Patch Changes - 24545c3: Fix package missed export types ## 2.0.0 ### Major Changes - 4084742: - Have added unnecessary provider `WebAppProvider` - Fixed all components to works inside `WebAppProvider` - Fix incorrect types `useExpand` - Added `smoothButtonsTransition` options to `WebAppProvider` ### Patch Changes - 3221966: Support dynamic webApp in context - 5e82b6c: Fix documentation format - 0bc91cf: Fix structure and types context - c402a76: Added documentation for WebAppProvider - 4f18299: Describe all WebApp types - 111549b: Added jest tests for package and public api ## 1.11.0 ### Minor Changes - af6ccfb: Added useExpand hook, that hook provided isExpanded state, and expand() handle. Remove unsafe_useExpand ## 1.10.1 ### Patch Changes - ef7d3e9: Change readme ## 1.10.0 ### Minor Changes - 8af00e3: Added unsafe_useExpand hook ## 1.9.1 ### Patch Changes - dd8ab05: Update readme ## 1.9.0 ### Minor Changes - 5b1f85f: Added hook useReadTextFromClipboard, that provide function that read text from clipboard - 2e16b48: Added hook useSwitchInlineQuery ## 1.8.0 ### Minor Changes - 458e93d: Added docs and demo, describes work useScanQrPopup hook ## 1.7.0 ### Minor Changes - c8b49cf: Added useScanQrPopup hook. This hook provide functions showScanQrPopup, closeScanQrPopup ## 1.6.0 ### Minor Changes - 9ed6405: Add useThemeParams hook ## 1.5.4 ### Patch Changes - 5a248e3: Fix .npmignore paths ## 1.5.3 ### Patch Changes - 7a5106c: Fix MainButton uniq effects ## 1.5.2 ### Patch Changes - 2006b71: Add docst and example with useHapticFeedback ## 1.5.1 ### Patch Changes - 92a21c9: Change interfeces ## 1.5.0 ### Minor Changes - dd146bc: Add useHapticFeedback hook ## 1.4.1 ### Patch Changes - 6a7c5b4: Add CRA demo webapp ## 1.4.0 ### Minor Changes - fdbfcc8: Fix window typed error ## 1.3.0 ### Minor Changes - 28c08e4: Add useShowPopup hook ## 1.2.0 ### Minor Changes - b7bfbcb: Add BackButton component ## 1.1.2 ### Patch Changes - 4e36d80: Update documentation ## 1.1.1 ### Patch Changes - 395c91f: Documentation fix ## 1.1.0 ### Minor Changes - 3f5bd48: Changes types, add documentation ## 1.0.3 ### Patch Changes - 81546ee: Add docs pages ## 1.0.2 ### Patch Changes - 6e8043f: Fix ci files, add README and LICENCE ## 1.0.1 ### Patch Changes - ea7807b: Inited project and added MainButton component ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or email address, without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at . All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Hi there! Interested in contributing to `react-telegram-web-app`? We'd love your help ## Goals The main goal of the project is to make it convenient to use Telegram Mini Apps in react applications ## Ways to contribute - [Install package](./README.md#-installation--get-started) and kick the tires. Does it work? Does it do what you'd expect? If not, [open an issue](https://github.com/vkruglikov/react-telegram-web-app/issues) and let us know. - Comment on some of the project's [open issues](https://github.com/vkruglikov/react-telegram-web-app/issues). Have you experienced the same problem? Know a workaround? Do you have a suggestion for how the feature could be better? - Browse through the [discussions](https://github.com/vkruglikov/react-telegram-web-app/discussions), and lend a hand answering questions. There's a good chance you've already experienced what another user is experiencing. ## Submitting a pull request ### Before you create pull request - Make sure your changes meet the project's goals - Before you do something, make sure that other members of the community really need it - Do not change the basic logic of Telegram Mini Apps, even if there are errors in it. Instead you need to open an issue at https://bugs.telegram.org/ ### Pull requests generally - The smaller the proposed change, the better. If you'd like to propose two unrelated changes, submit two pull requests. - The more information, the better. Make judicious use of the pull request body. Describe what changes were made, why you made them, and what impact they will have for users. - If this is your first pull request, it may help to [understand GitHub Flow](https://guides.github.com/introduction/flow/). ## Proposing updates to the documentation Documentation is generated using [typedoc](https://typedoc.org/guides/overview/). Look at the examples and describe the documentation sufficiently fully ## Respect the work of other developers - Don't change the basic styling settings without a good reason - Do not change basic project settings such as title and personal links ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2022 Kruglikov Valentin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # React components for Telegram MiniApp [![npm](https://img.shields.io/npm/v/@vkruglikov/react-telegram-web-app.svg)](https://www.npmjs.com/package/@vkruglikov/react-telegram-web-app) [![types](https://badgen.net/npm/types/@vkruglikov/react-telegram-web-app)](https://npmjs.org/package/@vkruglikov/react-telegram-web-app) [![GitHub Actions CI](https://github.com/vkruglikov/react-telegram-web-app/actions/workflows/release.yml/badge.svg)](https://github.com/vkruglikov/react-telegram-web-app/actions/workflows/release.yml) [![License](https://badgen.net/github/license/vkruglikov/react-telegram-web-app)](https://github.com/vkruglikov/react-telegram-web-app/blob/master/LICENSE) ![Tests](https://github.com/vkruglikov/react-telegram-web-app/actions/workflows/tests.yml/badge.svg) ## 🔴 Live Demo & Code Examples You can try open demo telegram bot with React WebApp [@react_telegram_web_app_bot](https://t.me/react_telegram_web_app_bot/demo). Also, you can look demo [source code](./demo/src). ## 🔧 Installation & Get started 1️⃣  **Foremost**, you have to do [initializing web apps](https://core.telegram.org/bots/webapps#initializing-mini-apps) step, because package has dependency of Telegram Web App context. 2️⃣  **Install** by running: `npm i @vkruglikov/react-telegram-web-app --save`. Today we support React^18. 3️⃣  **Try it out** by writing code. ```jsx import { MainButton, useShowPopup } from '@vkruglikov/react-telegram-web-app'; const Content = () => { const showPopup = useShowPopup(); const handleClick = () => showPopup({ message: 'Hello, I am popup', }); return ; }; ``` ## ✨ Short Documentation ### Components - [MainButton](./docs/README.md#mainbutton) - The component controls the main button, which is displayed at the bottom of the Web App in the Telegram interface. - [BackButton](./docs/README.md#backbutton) - This component controls the back button, which can be displayed in the header of the Web App in the Telegram interface. - [WebAppProvider](./docs/README.md#webappprovider) - WebAppProvider provide context with WebApp for components and hooks. You can try to pass an object with options - [SettingsButton](./docs/README.md#settingsbutton) Settings button (mvp) ```jsx import { WebAppProvider, MainButton, BackButton } from '@vkruglikov/react-telegram-web-app'; {/** Use components inside provider */} ``` ### Hooks - [useShowPopup](./docs/README.md#useshowpopup) - This hook provides `showPopup` function that shows a native popup. - [useHapticFeedback](./docs/README.md#usehapticfeedback) - This hook provides `impactOccurred`, `notificationOccurred` and `selectionChanged` functions that controls haptic feedback. - [useThemeParams](./docs/README.md#usethemeparams) - This hook provides `colorScheme` and `themeParams` object. - [useScanQrPopup](./docs/README.md#usescanqrpopup) - This hook provides `showScanQrPopup` and `closeScanQrPopup` functions. - [useReadTextFromClipboard](./docs/README.md#usereadtextfromclipboard) - This hook provides `readTextFromClipboard` function. - [useSwitchInlineQuery](./docs/README.md#useswitchinlinequery) - This hook provides `switchInlineQuery` function. - [useExpand](./docs/README.md#useexpand) - This hook provides `isExpanded` state, and `expand()` handle. - [useCloudStorage](./docs/README.md#usecloudstorage) - This hook provides `CloudStorage` object as Promise functions - [useInitData](./docs/README.md#useinitdata) - This hook provides `InitDataUnsafe` and `InitData` object - [useWebApp](./docs/README.md#usewebapp) - This hook just provides native `WebApp` object ## 🛣 Roadmap Here's what's coming up: - [ ] In the future, We would like to use us components also in Web application, without Telegram context. - [ ] All Telegram WebApp feature support - [x] Main Telegram WebApp feature support ## Contributors As always, thanks to our amazing contributors! Valentin Alxy Savin Devesh Pal Alexandr Gotovtsev Made with [contributors](https://github.com/jaywcjlove/github-action-contributors). ## Contributing - Read up about its [🛣 Roadmap](#-roadmap) and [official documentation](https://core.telegram.org/bots/webapps) Telegram Mini Apps - Have questions? Check out our [examples](#-live-demo--code-examples), [duscussions](https://github.com/vkruglikov/react-telegram-web-app/discussions) and [issues](https://github.com/vkruglikov/react-telegram-web-app/discussions) - [Fork](https://github.com/vkruglikov/react-telegram-web-app/fork) and [Contribute](./CONTRIBUTING.md) your own modifications ## 🥂 License [MIT](./LICENSE) ## 💻👞🙊📚 Join to discussions Create discussions, ask questions, share experiences and discuss ideas with everyone together https://github.com/vkruglikov/react-telegram-web-app/discussions ================================================ FILE: demo/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage /*.pem # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: demo/README.md ================================================ ## install Install certificate ```bash mkcert react-telegram-web-app.domain ``` Add domain to /etc/hosts as 127.0.0.1 ## OR you can edit .env file, and will configure how you want ================================================ FILE: demo/package.json ================================================ { "name": "demo", "version": "0.1.0", "private": false, "dependencies": { "@types/node": "^16.18.3", "@types/react": "^18.0.25", "@types/react-dom": "^18.0.9", "@vkruglikov/react-telegram-web-app": "^2.1.1", "antd": "^5.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", "typescript": "^4.9.3" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": [ "react-app" ] }, "homepage": "./", "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } ================================================ FILE: demo/public/index.html ================================================ react-telegram-web-app
================================================ FILE: demo/src/BackButtonDemo.tsx ================================================ import { Button, Form, Typography } from 'antd'; import { FC, useState } from 'react'; import { BackButton, useShowPopup } from '@vkruglikov/react-telegram-web-app'; const BackButtonDemo: FC = () => { const [buttonState, setButtonState] = useState<{ show: boolean; }>(); const showPopup = useShowPopup(); return ( <> BackButton
{buttonState?.show && ( { showPopup({ message: 'back button click', }); }} /> )}
); }; export default BackButtonDemo; ================================================ FILE: demo/src/ExpandDemo.tsx ================================================ import { Button, Form, Typography } from 'antd'; import { useExpand } from '@vkruglikov/react-telegram-web-app'; const ExpandDemo = () => { const [isExpanded, expand] = useExpand(); return ( <> useExpand
isExpanded: {`${isExpanded}`}
); }; export default ExpandDemo; ================================================ FILE: demo/src/HapticFeedbackDemo.tsx ================================================ import { Button, Form, Typography, Select } from 'antd'; import { FC, useState } from 'react'; import { ImpactOccurredFunction, NotificationOccurredFunction, useHapticFeedback, } from '@vkruglikov/react-telegram-web-app'; const HapticFeedbackDemo: FC = () => { const [impactOccurred, notificationOccurred, selectionChanged] = useHapticFeedback(); const [style, setStyle] = useState[0]>('light'); const [type, setType] = useState[0]>('error'); return ( <> useHapticFeedback
); }; export default HapticFeedbackDemo; ================================================ FILE: demo/src/MainButtonDemo.tsx ================================================ import { Button, Form, Input, Typography, Switch } from 'antd'; import { FC, useState } from 'react'; import { MainButton, MainButtonProps, } from '@vkruglikov/react-telegram-web-app'; const MainButtonDemo: FC<{ initialValues?: Partial & { show?: boolean }; }> = ({ initialValues }) => { const [buttonState, setButtonState] = useState< { show: boolean; } & Pick >({ text: 'BUTTON TEXT', show: false, progress: false, disable: false, ...initialValues, }); const onFinish = (values: any) => setButtonState(values); return ( <> MainButton
setButtonState({ ...buttonState, progress: value, }) } /> setButtonState({ ...buttonState, disable: value, }) } />
{buttonState?.show && }
); }; export default MainButtonDemo; ================================================ FILE: demo/src/ScanQrPopupDemo.tsx ================================================ import { Button, Form, Typography } from 'antd'; import { useScanQrPopup, useShowPopup, } from '@vkruglikov/react-telegram-web-app'; const ScanQrPopupDemo = () => { const [showQrPopup, closeQrPopup] = useScanQrPopup(); const showPopup = useShowPopup(); return ( <> useScanQrPopup
); }; export default ScanQrPopupDemo; ================================================ FILE: demo/src/ShowPopupDemo.tsx ================================================ import { Button, Form, Input, Typography } from 'antd'; import { FC, useState } from 'react'; import { ShowPopupParams, useShowPopup, } from '@vkruglikov/react-telegram-web-app'; const ShowPopupDemo: FC = () => { const showPopup = useShowPopup(); const [popupState, setPopupState] = useState< Pick >({ title: 'title', message: 'message', }); const onFinish = (values: any) => { setPopupState(values); showPopup({ ...values, buttons: [ { type: 'ok', }, { type: 'close', }, { type: 'destructive', text: 'destructive', }, ], }).catch(e => { showPopup({ title: 'error', message: e, }); }); }; return ( <> useShowPopup
{JSON.stringify([ { type: 'ok', }, { type: 'close', }, { type: 'destructive', text: 'destructive', }, ])}
); }; export default ShowPopupDemo; ================================================ FILE: demo/src/index.css ================================================ :root { --tg-theme-bg-color: #fff; --tg-theme-text-color: #0a0a0a; --tg-theme-hint-color: #929292; --tg-theme-link-color: #207ae4; --tg-theme-button-color: #5bc8fb; --tg-theme-button-text-color: #fffeec; --tg-theme-secondary-bg-color: #f3f2f9; --default-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; } body { background: var(--tg-theme-secondary-bg-color); padding: 20px 20px; margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } .App-logo { height: 40vmin; } @media (prefers-reduced-motion: no-preference) { .App-logo { animation: App-logo-spin infinite 20s linear; } } .betaVersion { padding: 10px 10px; margin-bottom: 20px; border: 1px solid #1777ff; } .App-header { display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .contentWrapper { background: var(--tg-theme-bg-color); color: var(--tg-theme-text-color) !important; border-radius: 10px; margin: 5px 0; padding: 20px; box-sizing: border-box; } .ant-input, .ant-select, .ant-select-item { background-color: unset !important; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ================================================ FILE: demo/src/index.tsx ================================================ import React, { DispatchWithoutAction, FC, useState } from 'react'; import ReactDOM from 'react-dom/client'; import { useThemeParams, WebAppProvider, } from '@vkruglikov/react-telegram-web-app'; import { ConfigProvider, theme } from 'antd'; import 'antd/dist/reset.css'; import './index.css'; import logo from './logo.svg'; import MainButtonDemo from './MainButtonDemo'; import BackButtonDemo from './BackButtonDemo'; import ShowPopupDemo from './ShowPopupDemo'; import HapticFeedbackDemo from './HapticFeedbackDemo'; import ScanQrPopupDemo from './ScanQrPopupDemo'; import ExpandDemo from './ExpandDemo'; import useBetaVersion from './useBetaVersion'; const DemoApp: FC<{ onChangeTransition: DispatchWithoutAction; }> = ({ onChangeTransition }) => { const [colorScheme, themeParams] = useThemeParams(); const [isBetaVersion, handleRequestBeta] = useBetaVersion(false); const [activeBtn, setActiveBtn] = useState(true); return (
logo
{isBetaVersion && (

WARNING: BETA VERSION

)} {!activeBtn ? ( ) : ( )}
); }; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement, ); const App = () => { const [smoothButtonsTransition, setSmoothButtonsTransition] = useState(false); return ( setSmoothButtonsTransition(state => !state)} /> ); }; root.render(); ================================================ FILE: demo/src/react-app-env.d.ts ================================================ /// ================================================ FILE: demo/src/spy.mjs ================================================ // @ts-ignore const WebView = window.Telegram.WebView; const postEvent = WebView.postEvent; const receiveEvent = WebView.postEvent; const element = document.createElement('div'); WebView.postEvent = (...args) => { const [eventType, callback, eventData] = args; const asd = document.createElement('div'); asd.innerHTML = `${eventType} -> ${JSON.stringify(eventData)}` element.appendChild(asd) element.scrollTop = element.scrollHeight; return postEvent(...args); }; WebView.receiveEvent = (...args) => { const [eventType, callback, eventData] = args; console.log('--ФЫВ------------', eventType, callback, eventData); return receiveEvent(...args); }; element.setAttribute( 'style', 'position:fixed;top:0;left:0;right:0;z-index:1000;background:#fff;height:80px;border-bottom:2px solid red;overflow: auto;', ); document.body.appendChild(element); ================================================ FILE: demo/src/useBetaVersion.ts ================================================ import { useHapticFeedback, useShowPopup, } from '@vkruglikov/react-telegram-web-app'; import { useRef, useState } from 'react'; const useBetaVersion = (initialState = false) => { const showPopup = useShowPopup(); const [, notification] = useHapticFeedback(); const [isBeta, setIsBeta] = useState( initialState || process.env.NODE_ENV === 'development', ); const isDevModeCounter = useRef(0); const handleRequestBeta = () => { if (++isDevModeCounter.current >= 10) { setIsBeta(!isBeta); isDevModeCounter.current = 0; showPopup({ message: `isDevMode: ${!isBeta}` }); notification('success'); } if (isDevModeCounter.current > 7) { showPopup({ message: `${10 - isDevModeCounter.current}`, }); } }; return [isBeta, handleRequestBeta] as const; }; export default useBetaVersion; ================================================ FILE: demo/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" }, "include": ["src"] } ================================================ FILE: docs/.nojekyll ================================================ TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. ================================================ FILE: docs/README.md ================================================ @vkruglikov/react-telegram-web-app # @vkruglikov/react-telegram-web-app ## Table of contents ### Interfaces - [BackButtonProps](interfaces/BackButtonProps.md) - [MainButtonProps](interfaces/MainButtonProps.md) - [ScanQrPopupParams](interfaces/ScanQrPopupParams.md) - [SettingsButtonProps](interfaces/SettingsButtonProps.md) - [ShowPopupButton](interfaces/ShowPopupButton.md) - [ShowPopupParams](interfaces/ShowPopupParams.md) - [ThemeParams](interfaces/ThemeParams.md) ### Type Aliases - [CloseScanQrPopupFunction](README.md#closescanqrpopupfunction) - [ColorScheme](README.md#colorscheme) - [GetItemFunction](README.md#getitemfunction) - [GetItemsFunction](README.md#getitemsfunction) - [GetKeysFunction](README.md#getkeysfunction) - [ImpactOccurredFunction](README.md#impactoccurredfunction) - [InitData](README.md#initdata) - [InitDataUnsafe](README.md#initdataunsafe) - [NotificationOccurredFunction](README.md#notificationoccurredfunction) - [Options](README.md#options) - [ReadTextFromClipboardFunction](README.md#readtextfromclipboardfunction) - [RemoveItemFunction](README.md#removeitemfunction) - [RemoveItemsFunction](README.md#removeitemsfunction) - [ScanQrPopupCallback](README.md#scanqrpopupcallback) - [SelectionChangedFunction](README.md#selectionchangedfunction) - [SetItemFunction](README.md#setitemfunction) - [ShowPopupFunction](README.md#showpopupfunction) - [ShowScanQrPopupFunction](README.md#showscanqrpopupfunction) - [SwitchInlineQueryFunction](README.md#switchinlinequeryfunction) - [WebAppChat](README.md#webappchat) - [WebAppProviderProps](README.md#webappproviderprops) - [WebAppUser](README.md#webappuser) ### Hooks - [useCloudStorage](README.md#usecloudstorage) - [useExpand](README.md#useexpand) - [useHapticFeedback](README.md#usehapticfeedback) - [useInitData](README.md#useinitdata) - [useReadTextFromClipboard](README.md#usereadtextfromclipboard) - [useScanQrPopup](README.md#usescanqrpopup) - [useShowPopup](README.md#useshowpopup) - [useSwitchInlineQuery](README.md#useswitchinlinequery) - [useThemeParams](README.md#usethemeparams) - [useWebApp](README.md#usewebapp) ### React Components - [BackButton](README.md#backbutton) - [MainButton](README.md#mainbutton) - [SettingsButton](README.md#settingsbutton) - [WebAppProvider](README.md#webappprovider) ## Type Aliases ### CloseScanQrPopupFunction Ƭ **CloseScanQrPopupFunction**: () => `void` #### Type declaration ▸ (): `void` A method that closes the native popup for scanning a QR code opened with the showScanQrPopup method ##### Returns `void` --- ### ColorScheme Ƭ **ColorScheme**: `"light"` \| `"dark"` \| `undefined` The color scheme currently used in the Telegram app. Either “light” or “dark”. Can `undefined`, if `window` is undefined. --- ### GetItemFunction Ƭ **GetItemFunction**: (`key`: `string`) => `Promise`<`string`\> #### Type declaration ▸ (`key`): `Promise`<`string`\> This function provides `getItem` method from [telegram!CloudStorage](https://core.telegram.org/bots/webapps#cloudstorage) as Promise **`Throws`** ##### Parameters | Name | Type | | :---- | :------- | | `key` | `string` | ##### Returns `Promise`<`string`\> --- ### GetItemsFunction Ƭ **GetItemsFunction**: (`keys`: `string`[]) => `Promise`<`string`[]\> #### Type declaration ▸ (`keys`): `Promise`<`string`[]\> This function provides `getItems` method from [telegram!CloudStorage](https://core.telegram.org/bots/webapps#cloudstorage) as Promise **`Throws`** ##### Parameters | Name | Type | | :----- | :--------- | | `keys` | `string`[] | ##### Returns `Promise`<`string`[]\> --- ### GetKeysFunction Ƭ **GetKeysFunction**: () => `Promise`<`string`[]\> #### Type declaration ▸ (): `Promise`<`string`[]\> This function provides `getKeys` method from [telegram!CloudStorage](https://core.telegram.org/bots/webapps#cloudstorage) as Promise **`Throws`** ##### Returns `Promise`<`string`[]\> --- ### ImpactOccurredFunction Ƭ **ImpactOccurredFunction**: (`style`: `"light"` \| `"medium"` \| `"heavy"` \| `"rigid"` \| `"soft"`) => `void` #### Type declaration ▸ (`style`): `void` A method tells that an impact occurred. The Telegram app may play the appropriate haptics based on style value passed. Style can be one of these values: - light, indicates a collision between small or lightweight UI objects, - medium, indicates a collision between medium-sized or medium-weight UI objects, - heavy, indicates a collision between large or heavyweight UI objects, - rigid, indicates a collision between hard or inflexible UI objects, - soft, indicates a collision between soft or flexible UI objects. [telegram!HapticFeedback](https://core.telegram.org/bots/webapps#hapticfeedback) ##### Parameters | Name | Type | | :------ | :------------------------------------------------------------ | | `style` | `"light"` \| `"medium"` \| `"heavy"` \| `"rigid"` \| `"soft"` | ##### Returns `void` --- ### InitData Ƭ **InitData**: `string` --- ### InitDataUnsafe Ƭ **InitDataUnsafe**: `Object` [telegram!WebAppInitData](https://core.telegram.org/bots/webapps#webappinitdata) #### Type declaration | Name | Type | | :---------------- | :---------------------------------------------------------------------- | | `auth_date` | `number` | | `can_send_after?` | `number` | | `chat?` | [`WebAppChat`](README.md#webappchat) | | `chat_instance?` | `string` | | `chat_type?` | `"sender"` \| `"private"` \| `"group"` \| `"supergroup"` \| `"channel"` | | `hash` | `string` | | `query_id?` | `string` | | `receiver?` | [`WebAppUser`](README.md#webappuser) | | `start_param?` | `string` | | `user?` | [`WebAppUser`](README.md#webappuser) | --- ### NotificationOccurredFunction Ƭ **NotificationOccurredFunction**: (`type`: `"error"` \| `"success"` \| `"warning"`) => `void` #### Type declaration ▸ (`type`): `void` A method tells that a task or action has succeeded, failed, or produced a warning. The Telegram app may play the appropriate haptics based on type value passed. Type can be one of these values: - error, indicates that a task or action has failed, - success, indicates that a task or action has completed successfully, - warning, indicates that a task or action produced a warning. [telegram!HapticFeedback](https://core.telegram.org/bots/webapps#hapticfeedback) ##### Parameters | Name | Type | | :----- | :-------------------------------------- | | `type` | `"error"` \| `"success"` \| `"warning"` | ##### Returns `void` --- ### Options Ƭ **Options**: `Object` This object describe options be able to set through WebAppProvider #### Type declaration | Name | Type | Description | | :--------------------------- | :-------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `async?` | `boolean` | - | | `smoothButtonsTransition?` | `boolean` | When is `true`, we can smooth button transitions due to show(), hide() calls. So when you use MainButton or BackButton on multiple pages, there will be no noticeable flickering of the button during transitions **`Default Value`** `false` | | `smoothButtonsTransitionMs?` | `number` | **`Default Value`** `10` **`Remarks`** | --- ### ReadTextFromClipboardFunction Ƭ **ReadTextFromClipboardFunction**: () => `Promise`<`string`\> #### Type declaration ▸ (): `Promise`<`string`\> This function provided Promise function that read text from clipboard ##### Returns `Promise`<`string`\> --- ### RemoveItemFunction Ƭ **RemoveItemFunction**: (`key`: `string`) => `Promise`<`void`\> #### Type declaration ▸ (`key`): `Promise`<`void`\> This function provides `removeItem` method from [telegram!CloudStorage](https://core.telegram.org/bots/webapps#cloudstorage) as Promise **`Throws`** ##### Parameters | Name | Type | | :---- | :------- | | `key` | `string` | ##### Returns `Promise`<`void`\> --- ### RemoveItemsFunction Ƭ **RemoveItemsFunction**: (`key`: `string`[]) => `Promise`<`void`\> #### Type declaration ▸ (`key`): `Promise`<`void`\> This function provides `removeItems` method from [telegram!CloudStorage](https://core.telegram.org/bots/webapps#cloudstorage) as Promise **`Throws`** ##### Parameters | Name | Type | | :---- | :--------- | | `key` | `string`[] | ##### Returns `Promise`<`void`\> --- ### ScanQrPopupCallback Ƭ **ScanQrPopupCallback**: (`text`: `string`) => `true` \| `void` #### Type declaration ▸ (`text`): `true` \| `void` If an optional callback parameter was passed, the callback function will be called and the text from the QR code will be passed as the first argument. Returning true inside this callback function causes the popup to be closed. ##### Parameters | Name | Type | | :----- | :------- | | `text` | `string` | ##### Returns `true` \| `void` --- ### SelectionChangedFunction Ƭ **SelectionChangedFunction**: () => `void` #### Type declaration ▸ (): `void` A method tells that the user has changed a selection. The Telegram app may play the appropriate haptics. [telegram!HapticFeedback](https://core.telegram.org/bots/webapps#hapticfeedback) ##### Returns `void` --- ### SetItemFunction Ƭ **SetItemFunction**: (`key`: `string`, `value`: `string`) => `Promise`<`void`\> #### Type declaration ▸ (`key`, `value`): `Promise`<`void`\> This function provides `setItem` method from [telegram!CloudStorage](https://core.telegram.org/bots/webapps#cloudstorage) as Promise **`Throws`** ##### Parameters | Name | Type | | :------ | :------- | | `key` | `string` | | `value` | `string` | ##### Returns `Promise`<`void`\> --- ### ShowPopupFunction Ƭ **ShowPopupFunction**: (`params`: [`ShowPopupParams`](interfaces/ShowPopupParams.md)) => `Promise`<`string`\> #### Type declaration ▸ (`params`): `Promise`<`string`\> This function provides Promise, and resolve the field id of the pressed button will be passed. **`Throws`** ##### Parameters | Name | Type | | :------- | :------------------------------------------------- | | `params` | [`ShowPopupParams`](interfaces/ShowPopupParams.md) | ##### Returns `Promise`<`string`\> Button id as string, it was described by [ShowPopupButton](interfaces/ShowPopupButton.md) --- ### ShowScanQrPopupFunction Ƭ **ShowScanQrPopupFunction**: (`params`: [`ScanQrPopupParams`](interfaces/ScanQrPopupParams.md), `callback?`: [`ScanQrPopupCallback`](README.md#scanqrpopupcallback)) => `void` #### Type declaration ▸ (`params`, `callback?`): `void` A method that shows a native popup for scanning a QR code described by the params argument of the type [ScanQrPopupParams](interfaces/ScanQrPopupParams.md). ##### Parameters | Name | Type | | :---------- | :----------------------------------------------------- | | `params` | [`ScanQrPopupParams`](interfaces/ScanQrPopupParams.md) | | `callback?` | [`ScanQrPopupCallback`](README.md#scanqrpopupcallback) | ##### Returns `void` --- ### SwitchInlineQueryFunction Ƭ **SwitchInlineQueryFunction**: (`query`: `string`, `chatType?`: (`"users"` \| `"bots"` \| `"groups"` \| `"channels"`)[]) => `void` #### Type declaration ▸ (`query`, `chatType?`): `void` This function that inserts the bot's username and the specified inline query in the current chat's input field You have to look original description switchInlineQuery in [telegram!WebApp](https://core.telegram.org/bots/webapps#initializing-mini-apps) for more information ##### Parameters | Name | Type | | :---------- | :------------------------------------------------------ | | `query` | `string` | | `chatType?` | (`"users"` \| `"bots"` \| `"groups"` \| `"channels"`)[] | ##### Returns `void` --- ### WebAppChat Ƭ **WebAppChat**: `Object` [telegram!WebAppChat](https://core.telegram.org/bots/webapps#webappchat) #### Type declaration | Name | Type | | :----------- | :----------------------------------------- | | `id` | `number` | | `photo_url?` | `string` | | `title` | `string` | | `type` | `"group"` \| `"supergroup"` \| `"channel"` | | `username?` | `string` | --- ### WebAppProviderProps Ƭ **WebAppProviderProps**: `PropsWithChildren`<{ `options?`: [`Options`](README.md#options) }\> --- ### WebAppUser Ƭ **WebAppUser**: `Object` [telegram!WebAppUser](https://core.telegram.org/bots/webapps#webappuser) #### Type declaration | Name | Type | | :-------------------------- | :-------- | | `added_to_attachment_menu?` | `true` | | `allows_write_to_pm?` | `true` | | `first_name` | `string` | | `id` | `number` | | `is_bot?` | `boolean` | | `is_premium?` | `boolean` | | `language_code?` | `string` | | `last_name?` | `string` | | `photo_url?` | `string` | | `username?` | `string` | ## Hooks ### useCloudStorage ▸ **useCloudStorage**(): `Object` This hook provides [telegram!CloudStorage](https://core.telegram.org/bots/webapps#cloudstorage) object with promises functions, so you don't have to pass `callback` argument You have to look original description CloudStorage object in [telegram!CloudStorage](https://core.telegram.org/bots/webapps#cloudstorage) #### Returns `Object` | Name | Type | | :----------- | :--------------------------------------------------- | | `getItem` | [`GetItemFunction`](README.md#getitemfunction) | | `getItems` | [`GetItemsFunction`](README.md#getitemsfunction) | | `getKeys` | [`GetKeysFunction`](README.md#getkeysfunction) | | `removeItem` | [`RemoveItemFunction`](README.md#removeitemfunction) | | `setItem` | [`SetItemFunction`](README.md#setitemfunction) | --- ### useExpand ▸ **useExpand**(): readonly [`undefined` \| `boolean`, `DispatchWithoutAction`] This hook provided isExpanded state, and expand() handle You have to look original description in [telegram!WebApp](https://core.telegram.org/bots/webapps#initializing-mini-apps) for more information `isExpanded` can be `undefined` ```tsx import { useExpand } from "@vkruglikov/react-telegram-web-app"; const [isExpanded, expand] = useExpand(); const handleClick = () => !isExpanded && expand(); ``` #### Returns readonly [`undefined` \| `boolean`, `DispatchWithoutAction`] --- ### useHapticFeedback ▸ **useHapticFeedback**(): readonly [[`ImpactOccurredFunction`](README.md#impactoccurredfunction), [`NotificationOccurredFunction`](README.md#notificationoccurredfunction), [`SelectionChangedFunction`](README.md#selectionchangedfunction)] This hook that provided [ImpactOccurredFunction](README.md#impactoccurredfunction), [NotificationOccurredFunction](README.md#notificationoccurredfunction) and [SelectionChangedFunction](README.md#selectionchangedfunction) functions that controls haptic feedback. You have to look original telegram description [telegram!HapticFeedback](https://core.telegram.org/bots/webapps#hapticfeedback), because it Hook implementing his. ```tsx import { useHapticFeedback } from '@vkruglikov/react-telegram-web-app'; const [impactOccurred, notificationOccurred, selectionChanged] = useHapticFeedback(); // const [,notificationOccurred] = useHapticFeedback(); impactOccurred('heavy'); notificationOccurred('success'); ``` #### Returns readonly [[`ImpactOccurredFunction`](README.md#impactoccurredfunction), [`NotificationOccurredFunction`](README.md#notificationoccurredfunction), [`SelectionChangedFunction`](README.md#selectionchangedfunction)] --- ### useInitData ▸ **useInitData**(): readonly [`undefined` \| [`InitDataUnsafe`](README.md#initdataunsafe), `undefined` \| `string`] This hook provides `initDataUnsafe` and `initData` You have to look original description in [telegram!WebApp](https://core.telegram.org/bots/webapps#initializing-mini-apps), because hook just return this. ```tsx import { useInitData } from '@vkruglikov/react-telegram-web-app'; const [initDataUnsafe] = useInitData(); const [initDataUnsafe, initData] = useInitData(); ``` #### Returns readonly [`undefined` \| [`InitDataUnsafe`](README.md#initdataunsafe), `undefined` \| `string`] --- ### useReadTextFromClipboard ▸ **useReadTextFromClipboard**(): [`ReadTextFromClipboardFunction`](README.md#readtextfromclipboardfunction) This hook that provided [ReadTextFromClipboardFunction](README.md#readtextfromclipboardfunction) Promise function that read text from clipboard. You have to look original description readTextFromClipboard in [telegram!WebApp](https://core.telegram.org/bots/webapps#initializing-mini-apps), because hook just implements his. ```tsx import { useReadTextFromClipboard } from '@vkruglikov/react-telegram-web-app'; const readText = useReadTextFromClipboard(); readText().then(console.log); // or await readText(); ``` #### Returns [`ReadTextFromClipboardFunction`](README.md#readtextfromclipboardfunction) --- ### useScanQrPopup ▸ **useScanQrPopup**(): readonly [[`ShowScanQrPopupFunction`](README.md#showscanqrpopupfunction), [`CloseScanQrPopupFunction`](README.md#closescanqrpopupfunction)] The hook provided showScanQrPopup function of the type [ShowScanQrPopupFunction](README.md#showscanqrpopupfunction) and closeScanQrPopup [CloseScanQrPopupFunction](README.md#closescanqrpopupfunction). #### Returns readonly [[`ShowScanQrPopupFunction`](README.md#showscanqrpopupfunction), [`CloseScanQrPopupFunction`](README.md#closescanqrpopupfunction)] --- ### useShowPopup ▸ **useShowPopup**(): [`ShowPopupFunction`](README.md#showpopupfunction) The hook provided showPopup function of the type [ShowPopupFunction](README.md#showpopupfunction). The function that shows a native popup described by the params argument of the type [ShowPopupParams](interfaces/ShowPopupParams.md). ```tsx import { useShowPopup } from '@vkruglikov/react-telegram-web-app'; const showPopup = useShowPopup(); showPopup({ message: 'Hello world' }).then(buttonId => console.log(buttonId)); ``` #### Returns [`ShowPopupFunction`](README.md#showpopupfunction) --- ### useSwitchInlineQuery ▸ **useSwitchInlineQuery**(): [`SwitchInlineQueryFunction`](README.md#switchinlinequeryfunction) This hook that provided [SwitchInlineQueryFunction](README.md#switchinlinequeryfunction) You have to look original description switchInlineQuery in [telegram!WebApp](https://core.telegram.org/bots/webapps#initializing-mini-apps), because hook just implements his. #### Returns [`SwitchInlineQueryFunction`](README.md#switchinlinequeryfunction) --- ### useThemeParams ▸ **useThemeParams**(): readonly [[`ColorScheme`](README.md#colorscheme), [`ThemeParams`](interfaces/ThemeParams.md)] The hook provided colorScheme and themeParams values of the type [ColorScheme](README.md#colorscheme) and [ThemeParams](interfaces/ThemeParams.md). ```tsx import { useThemeParams } from '@vkruglikov/react-telegram-web-app'; const [colorScheme, themeParams] = useThemeParams(); console.log(colorScheme === 'dark'); console.log({ text_color: themeParams.text_color, button_color: themeParams.button_color, bg_color: themeParams.bg_color, }); ``` #### Returns readonly [[`ColorScheme`](README.md#colorscheme), [`ThemeParams`](interfaces/ThemeParams.md)] --- ### useWebApp ▸ **useWebApp**(): `any` This hook just provides native [telegram!WebApp](https://core.telegram.org/bots/webapps#initializing-mini-apps) object ```tsx import { useWebApp } from '@vkruglikov/react-telegram-web-app'; const WebApp = useWebApp(); console.log(WebApp.version); ``` #### Returns `any` ## React Components ### BackButton ▸ **BackButton**(`props`): `null` Renders a [telegram!BackButton](https://core.telegram.org/bots/webapps#backbutton) component in React app as [react!Component](https://reactjs.org/docs/react-component.html) ```tsx import { BackButton } from '@vkruglikov/react-telegram-web-app'; console.log('Hello, I am back button!')} />; ``` #### Parameters | Name | Type | | :------ | :------------------------------------------------- | | `props` | [`BackButtonProps`](interfaces/BackButtonProps.md) | #### Returns `null` --- ### MainButton ▸ **MainButton**(`props`): `null` Renders a [telegram!MainButton](https://core.telegram.org/bots/webapps#mainbutton) component in React app as [react!Component](https://reactjs.org/docs/react-component.html) ```tsx import { MainButton } from '@vkruglikov/react-telegram-web-app'; console.log('Hello, I am button!')} />; ``` #### Parameters | Name | Type | | :------ | :------------------------------------------------- | | `props` | [`MainButtonProps`](interfaces/MainButtonProps.md) | #### Returns `null` --- ### SettingsButton ▸ **SettingsButton**(`props`): `null` Renders a [telegram!SettingsButton](https://core.telegram.org/bots/webapps#settingsbutton) component in React app as [react!Component](https://reactjs.org/docs/react-component.html) ```tsx import { SettingsButton } from '@vkruglikov/react-telegram-web-app'; console.log('Hello, I am settings button!')} />; ``` #### Parameters | Name | Type | | :------ | :--------------------------------------------------------- | | `props` | [`SettingsButtonProps`](interfaces/SettingsButtonProps.md) | #### Returns `null` --- ### WebAppProvider ▸ **WebAppProvider**(`props`): `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> WebAppProvider provide context with WebApp for components and hooks. Necessary to use only if you want to override `options` ```tsx import { WebAppProvider } from "@vkruglikov/react-telegram-web-app"; // You can pass options {@link Options} ``` #### Parameters | Name | Type | | :------ | :----------------------------------------------------- | | `props` | [`WebAppProviderProps`](README.md#webappproviderprops) | #### Returns `ReactElement`<`any`, `string` \| `JSXElementConstructor`<`any`\>\> ================================================ FILE: docs/interfaces/BackButtonProps.md ================================================ [@vkruglikov/react-telegram-web-app](../README.md) / BackButtonProps # Interface: BackButtonProps The props type of [`BackButton`](../README.md#backbutton). ## Table of contents ### Properties - [onClick](BackButtonProps.md#onclick) ## Properties ### onClick • `Optional` **onClick**: () => `void` #### Type declaration ▸ (): `void` The back button press event handler ##### Returns `void` ================================================ FILE: docs/interfaces/MainButtonProps.md ================================================ [@vkruglikov/react-telegram-web-app](../README.md) / MainButtonProps # Interface: MainButtonProps The props type of [`MainButton`](../README.md#mainbutton). ## Table of contents ### Properties - [color](MainButtonProps.md#color) - [disabled](MainButtonProps.md#disabled) - [onClick](MainButtonProps.md#onclick) - [progress](MainButtonProps.md#progress) - [text](MainButtonProps.md#text) - [textColor](MainButtonProps.md#textcolor) ## Properties ### color • `Optional` **color**: `string` Current button color. **`Default Value`** Set to themeParams.button_color by default --- ### disabled • `Optional` **disabled**: `boolean` The button disable state. **`Default Value`** Set to `false` y defaults --- ### onClick • `Optional` **onClick**: () => `void` #### Type declaration ▸ (): `void` The button press event handler ##### Returns `void` --- ### progress • `Optional` **progress**: `boolean` The button progress state indicator. **`Default Value`** Set to `false` by default --- ### text • `Optional` **text**: `string` Current button text **`Default Value`** Set to `CONTINUE` by default --- ### textColor • `Optional` **textColor**: `string` Current button text color **`Default Value`** Set to themeParams.button_text_color by default ================================================ FILE: docs/interfaces/ScanQrPopupParams.md ================================================ [@vkruglikov/react-telegram-web-app](../README.md) / ScanQrPopupParams # Interface: ScanQrPopupParams This object describes the native popup for scanning QR codes. You have to see original interface [telegram!ScanQrPopupParams](https://core.telegram.org/bots/webapps#scanqrpopupparams). ## Table of contents ### Properties - [text](ScanQrPopupParams.md#text) ## Properties ### text • **text**: `string` The text to be displayed under the 'Scan QR' heading, 0-64 characters. ================================================ FILE: docs/interfaces/SettingsButtonProps.md ================================================ [@vkruglikov/react-telegram-web-app](../README.md) / SettingsButtonProps # Interface: SettingsButtonProps The props type of [`SettingsButton`](../README.md#settingsbutton). ## Table of contents ### Properties - [onClick](SettingsButtonProps.md#onclick) ## Properties ### onClick • `Optional` **onClick**: () => `void` #### Type declaration ▸ (): `void` ##### Returns `void` ================================================ FILE: docs/interfaces/ShowPopupButton.md ================================================ [@vkruglikov/react-telegram-web-app](../README.md) / ShowPopupButton # Interface: ShowPopupButton You have to see original interface [telegram!PopupButton](https://core.telegram.org/bots/webapps#popupbutton). ## Hierarchy - `Record`<`string`, `unknown`\> ↳ **`ShowPopupButton`** ## Table of contents ### Properties - [id](ShowPopupButton.md#id) - [text](ShowPopupButton.md#text) - [type](ShowPopupButton.md#type) ## Properties ### id • `Optional` **id**: `string` Optional. Identifier of the button, 0-64 characters. Set to empty string by default. If the button is pressed, its id is returned in the callback and the popupClosed event. --- ### text • `Optional` **text**: `string` Optional. The text to be displayed on the button, 0-64 characters. Required if type is default or destructive. Irrelevant for other types. --- ### type • `Optional` **type**: `string` Optional. Type of the button. Set to default by default. ================================================ FILE: docs/interfaces/ShowPopupParams.md ================================================ [@vkruglikov/react-telegram-web-app](../README.md) / ShowPopupParams # Interface: ShowPopupParams You have to see original interface [telegram!PopupParams](https://core.telegram.org/bots/webapps#popupparams). ## Hierarchy - `Record`<`string`, `unknown`\> ↳ **`ShowPopupParams`** ## Table of contents ### Properties - [buttons](ShowPopupParams.md#buttons) - [message](ShowPopupParams.md#message) - [title](ShowPopupParams.md#title) ## Properties ### buttons • `Optional` **buttons**: [`ShowPopupButton`](ShowPopupButton.md)[] Optional. List of buttons to be displayed in the popup, 1-3 buttons --- ### message • **message**: `string` The message to be displayed in the body of the popup, 1-256 characters. --- ### title • `Optional` **title**: `string` Optional. The text to be displayed in the popup title, 0-64 characters. ================================================ FILE: docs/interfaces/ThemeParams.md ================================================ [@vkruglikov/react-telegram-web-app](../README.md) / ThemeParams # Interface: ThemeParams This object contains the user's current theme settings. This object implement original Telegram WebApp type of [telegram!ThemeParams](https://core.telegram.org/bots/webapps#themeparams) ## Table of contents ### Properties - [bg_color](ThemeParams.md#bg_color) - [button_color](ThemeParams.md#button_color) - [button_text_color](ThemeParams.md#button_text_color) - [hint_color](ThemeParams.md#hint_color) - [link_color](ThemeParams.md#link_color) - [secondary_bg_color](ThemeParams.md#secondary_bg_color) - [text_color](ThemeParams.md#text_color) ## Properties ### bg_color • `Optional` **bg_color**: `string` Background color in the #RRGGBB format. --- ### button_color • `Optional` **button_color**: `string` Button color in the #RRGGBB format. --- ### button_text_color • `Optional` **button_text_color**: `string` Button text color in the #RRGGBB format. --- ### hint_color • `Optional` **hint_color**: `string` Hint text color in the #RRGGBB format. --- ### link_color • `Optional` **link_color**: `string` Link color in the #RRGGBB format. --- ### secondary_bg_color • `Optional` **secondary_bg_color**: `string` Secondary background color in the #RRGGBB format. --- ### text_color • `Optional` **text_color**: `string` Main text color in the #RRGGBB format. ================================================ FILE: global.d.ts ================================================ import { WebApp } from './src/core/twa-types'; declare global { interface Window { Telegram?: { WebApp: WebApp; }; TelegramWebviewProxy?: any; } } ================================================ FILE: jest.config.json ================================================ { "transform": { "\\.[jt]sx?$": "ts-jest" }, "testEnvironment": "jsdom", "setupFilesAfterEnv": ["./tests/setupTests.ts"] } ================================================ FILE: package.json ================================================ { "name": "@vkruglikov/react-telegram-web-app", "version": "2.2.0", "description": "React components for Telegram WebApp", "source": "./src/index.ts", "type": "module", "keywords": [ "react", "telegram", "telegram-bot" ], "exports": { "types": "./lib/index.d.ts", "require": "./lib/react-telegram-web-app.cjs", "default": "./lib/react-telegram-web-app.modern.js" }, "files": [ "/lib" ], "main": "lib/react-telegram-web-app.cjs", "module": "lib/react-telegram-web-app.module.js", "unpkg": "lib/react-telegram-web-app.umd.js", "types": "lib/index.d.ts", "scripts": { "build": "rm -rf ./lib/*; microbundle build --sourcemap=false && cp -R src/core/twa-types lib/core/twa-types", "dev": "npm run build && microbundle watch --compress=false", "prepare": "husky install", "changeset": "changeset", "docs": "typedoc && prettier --write ./docs", "release": "npm run test:all && npm run build && npm run test:package && changeset publish", "format": "prettier --write .", "eslint": "npx eslint src", "test": "NODE_ENV=test jest", "test:all": "NODE_ENV=test jest --testPathIgnorePatterns package", "test:package": "NODE_ENV=test jest package" }, "repository": { "type": "git", "url": "git+https://github.com/vkruglikov/react-telegram-web-app.git" }, "publishConfig": { "access": "public" }, "author": "Valentin Kruglikov dev.n@bk.ru", "license": "MIT", "bugs": { "url": "https://github.com/vkruglikov/react-telegram-web-app/issues" }, "homepage": "https://github.com/vkruglikov/react-telegram-web-app#readme", "devDependencies": { "@changesets/cli": "^2.25.2", "@testing-library/react": "^14.0.0", "@types/jest": "^29.5.2", "@types/react": "^18", "@types/react-test-renderer": "^18.0.0", "@typescript-eslint/eslint-plugin": "^6.1.0", "@typescript-eslint/parser": "^6.1.0", "eslint": "^8.45.0", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "husky": "^8.0.2", "jest": "^29.6.1", "jest-environment-jsdom": "^29.6.1", "lint-staged": "^13.0.3", "microbundle": "^0.15.1", "prettier": "2.8.0", "react-test-renderer": "^18.2.0", "ts-jest": "^29.1.1", "typedoc": "^0.24.8", "typedoc-plugin-markdown": "^3.15.3", "typescript": "^5.1.6" }, "peerDependencies": { "react": "^18", "react-dom": "^18" }, "lint-staged": { "*.(tsx|ts)": [ "eslint", "prettier --write" ], "*.md": [ "prettier --write" ] } } ================================================ FILE: src/BackButton.tsx ================================================ import { useContext, useEffect, useId } from 'react'; import { useWebApp, useSmoothButtonsTransition, systemContext } from './core'; /** * The props type of {@link BackButton | `BackButton`}. */ export interface BackButtonProps { /** The back button press event handler */ onClick?: () => void; } /** * Renders a {@link telegram!BackButton} component in React app as {@link react!Component} * * ```tsx * import { BackButton } from "@vkruglikov/react-telegram-web-app"; * * console.log('Hello, I am back button!')} * /> * ``` * @param props * @group React Components */ const BackButton = ({ onClick }: BackButtonProps): null => { const system = useContext(systemContext); const buttonId = useId(); const WebApp = useWebApp(); const BackButton = WebApp?.BackButton; useEffect(() => { if (!onClick || !BackButton) { return; } BackButton.onClick(onClick); return () => { BackButton.offClick(onClick); }; }, [onClick, BackButton]); useSmoothButtonsTransition({ show: BackButton?.show, hide: BackButton?.hide, currentShowedIdRef: system.BackButton, id: buttonId, }); return null; }; export default BackButton; ================================================ FILE: src/MainButton.tsx ================================================ import { useContext, useEffect, useId } from 'react'; import { useWebApp, useSmoothButtonsTransition, systemContext } from './core'; /** * The props type of {@link MainButton | `MainButton`}. */ export interface MainButtonProps { /** * Current button text * @defaultValue Set to `CONTINUE` by default */ text?: string; /** * The button progress state indicator. * @defaultValue Set to `false` by default */ progress?: boolean; /** * Just an alias on the {@link MainButtonProps.disabled} * @deprecated Use {@link MainButtonProps.disabled} instead, will be removed * @ignore */ disable?: boolean; /** * The button disable state. * @defaultValue Set to `false` y defaults */ disabled?: boolean; /** The button press event handler */ onClick?: () => void; /** * Current button color. * @defaultValue Set to themeParams.button_color by default */ color?: string; /** * Current button text color * @defaultValue Set to themeParams.button_text_color by default */ textColor?: string; } /** * Renders a {@link telegram!MainButton} component in React app as {@link react!Component} * * ```tsx * import { MainButton } from "@vkruglikov/react-telegram-web-app"; * * console.log('Hello, I am button!')} * /> * ``` * @param props * @group React Components */ const MainButton = ({ text = 'CONTINUE', progress = false, disable: disable_old, disabled: disable_new = false, color, textColor, onClick, }: MainButtonProps): null => { const system = useContext(systemContext); const buttonId = useId(); const WebApp = useWebApp(); const MainButton = WebApp?.MainButton; const themeParams = WebApp?.themeParams; const disabled = disable_old || disable_new; useEffect(() => { MainButton?.setParams({ color: color || themeParams?.button_color || '#fff', }); }, [color, themeParams, MainButton]); useEffect(() => { MainButton?.setParams({ text_color: textColor || themeParams?.button_text_color || '#000', }); }, [MainButton, themeParams, textColor]); useEffect(() => { MainButton?.setText(text); }, [text, MainButton]); useEffect(() => { if (disabled) { MainButton?.disable(); } else if (!disabled) { MainButton?.enable(); } }, [disabled, MainButton]); useEffect(() => { if (progress) { MainButton?.showProgress(false); } else if (!progress) { MainButton?.hideProgress(); } }, [progress, MainButton]); useEffect(() => { if (!onClick) { return; } MainButton?.onClick(onClick); return () => { MainButton?.offClick(onClick); }; }, [onClick, MainButton]); useSmoothButtonsTransition({ show: MainButton?.show, hide: MainButton?.hide, currentShowedIdRef: system.MainButton, id: buttonId, }); return null; }; export default MainButton; ================================================ FILE: src/SettingsButton.tsx ================================================ import { useEffect } from 'react'; import { useWebApp } from './core'; /** * The props type of {@link SettingsButton | `SettingsButton`}. */ export interface SettingsButtonProps { onClick?: () => void; } /** * Renders a {@link telegram!SettingsButton} component in React app as {@link react!Component} * * ```tsx * import { SettingsButton } from "@vkruglikov/react-telegram-web-app"; * * console.log('Hello, I am settings button!')} * /> * ``` * @param props * @group React Components */ const SettingsButton = ({ onClick }: SettingsButtonProps): null => { const WebApp = useWebApp(); useEffect(() => { if (!onClick || !WebApp?.SettingsButton) { return; } WebApp.SettingsButton.onClick(onClick); return () => { WebApp.SettingsButton.offClick(onClick); }; }, [onClick, WebApp]); useEffect(() => { if (!WebApp?.SettingsButton) { return; } WebApp.SettingsButton.show?.(); return () => { WebApp.SettingsButton.hide?.(); }; }, [WebApp]); return null; }; export default SettingsButton; ================================================ FILE: src/WebAppProvider.tsx ================================================ import React, { PropsWithChildren, ReactElement, useEffect, useMemo, } from 'react'; import { webAppContext, optionsContext, systemContext, Options, DEFAULT_OPTIONS, createSystemContextValue, getWebAppFromGlobal, useAsyncMode, } from './core'; export type WebAppProviderProps = PropsWithChildren<{ options?: Options; }>; /** * WebAppProvider provide context with WebApp for components and hooks. * Necessary to use only if you want to override `options` * * ```tsx * import { WebAppProvider } from "@vkruglikov/react-telegram-web-app"; * * * * * * // You can pass options {@link Options} * * * * ``` * @param props * @group React Components */ const WebAppProvider = ({ children, options, }: WebAppProviderProps): ReactElement => { const isLoadedWithAsyncMode = useAsyncMode(!!options?.async); const mergedOptions = useMemo( () => ({ ...DEFAULT_OPTIONS, ...options, }), [options], ); const systemValue = useMemo(createSystemContextValue, []); const globalWebApp = useMemo(getWebAppFromGlobal, [isLoadedWithAsyncMode]); useEffect(() => { if (!options?.smoothButtonsTransition) return; const forceHideButtons = () => { globalWebApp?.MainButton?.hide(); globalWebApp?.BackButton?.hide(); globalWebApp?.SettingsButton?.hide(); }; window.addEventListener('beforeunload', forceHideButtons); return () => window.removeEventListener('beforeunload', forceHideButtons); }, [globalWebApp, options?.smoothButtonsTransition]); return ( {children} ); }; export default WebAppProvider; ================================================ FILE: src/core/context.ts ================================================ import { createContext, MutableRefObject } from 'react'; export const getWebAppFromGlobal = () => typeof window !== 'undefined' && window?.Telegram?.WebApp ? window.Telegram.WebApp : null; export const webAppContext = createContext< ReturnType >(getWebAppFromGlobal()); /** * This object describe options be able to set through WebAppProvider */ export type Options = { /** * When is `true`, we can smooth button transitions due to show(), hide() calls. * So when you use MainButton or BackButton on multiple pages, there will be * no noticeable flickering of the button during transitions * @defaultValue `false` */ smoothButtonsTransition?: boolean; async?: boolean; /** * @defaultValue `10` * @remarks */ smoothButtonsTransitionMs?: number; }; export const DEFAULT_OPTIONS: Options = { smoothButtonsTransition: false, smoothButtonsTransitionMs: 10, }; export const optionsContext = createContext(DEFAULT_OPTIONS); type SystemContext = { MainButton: MutableRefObject; BackButton: MutableRefObject; }; export const createSystemContextValue = () => ({ MainButton: { current: null }, BackButton: { current: null }, }); export const systemContext = createContext( createSystemContextValue(), ); ================================================ FILE: src/core/index.ts ================================================ export { default as useSmoothButtonsTransition } from './useSmoothButtonsTransition'; export { default as useWebApp } from './useWebApp'; export { default as useAsyncMode } from './useAsyncMode'; export * from './context'; ================================================ FILE: src/core/twa-types/WebApp.d.ts ================================================ export declare namespace TelegramWebApp { /** * {@link https://core.telegram.org/bots/webapps#themeparams} */ interface ThemeParams { bg_color?: string; text_color?: string; hint_color?: string; link_color?: string; button_color?: string; button_text_color?: string; } /** * {@link https://core.telegram.org/bots/webapps#mainbutton} */ interface MainButton { text: string; color: string; textColor: string; isVisible: boolean; isActive: boolean; readonly isProgressVisible: boolean; setText(text: string): MainButton; show(): MainButton; hide(): MainButton; enable(): MainButton; disable(): MainButton; showProgress(leaveActive?: boolean): MainButton; hideProgress(): MainButton; onClick(callback: () => void); setParams(params: { text?: MainButton['text']; color?: MainButton['color']; text_color?: MainButton['textColor']; is_active?: MainButton['isActive']; is_visible?: MainButton['isVisible']; }): MainButton; } /** * {@link https://core.telegram.org/bots/webapps#webappuser} */ interface WebAppUser { id: number; is_bot?: boolean; first_name: string; last_name?: string; username?: string; language_code?: string; photo_url?: string; } /** * {@link https://core.telegram.org/bots/webapps#webappinitdata} */ interface WebAppInitData { query_id?: string; user?: WebAppUser; receiver?: WebAppUser; chat_type?: 'sender' | 'private' | 'group' | 'supergroup' | 'channel'; chat_instance?: string; start_param?: string; auth_date: number; hash: string; } interface Event { onEvent( eventType: 'viewportChanged', eventHandler: (payload: { isStateStable: boolean }) => void, ); onEvent( eventType: 'themeChanged' | 'mainButtonClicked', eventHandler: () => void, ); offEvent( eventType: 'viewportChanged' | 'themeChanged' | 'mainButtonClicked', eventHandler: (...args: any[]) => void, ); } /** * {@link https://core.telegram.org/bots/webapps#initializing-web-apps} */ interface WebApp extends Event { initData: string; initDataUnsafe: WebAppInitData; platform: string; colorScheme: 'dark' | 'light'; themeParams: ThemeParams; isExpanded: boolean; viewportHeight: number; viewportStableHeight: number; MainButton: MainButton; sendData(data: unknown); ready(); expand(); close(); } } ================================================ FILE: src/core/twa-types/WebAppVersion_6.1.d.ts ================================================ import { TelegramWebApp } from './WebApp'; export declare namespace TelegramWebAppVersion6_1 { interface ThemeParams extends TelegramWebApp.ThemeParams { secondary_bg_color?: string; } /** * {@link https://core.telegram.org/bots/webapps#backbutton} */ interface BackButton { isVisible: boolean; onClick(cb: () => void): BackButton; offClick(cb: () => void): BackButton; show(): BackButton; hide(): BackButton; } interface MainButton extends TelegramWebApp.MainButton { offClick(text: () => void): MainButton; } /** * {@link https://core.telegram.org/bots/webapps#webappchat} */ interface WebAppChat { id: number; type: 'group' | 'supergroup' | 'channel'; title: string; username?: string; photo_url?: string; } interface WebAppInitData extends TelegramWebApp.WebAppInitData { chat?: WebAppChat; can_send_after?: number; } /** * {@link https://core.telegram.org/bots/webapps#hapticfeedback} */ interface HapticFeedback { impactOccurred( style: 'light' | 'medium' | 'heavy' | 'rigid' | 'soft', ): HapticFeedback; notificationOccurred(type: 'error' | 'success' | 'warning'): HapticFeedback; selectionChanged(): HapticFeedback; } interface Event { onEvent( eventType: 'backButtonClicked' | 'settingsButtonClicked', eventHandler: () => void, ); onEvent( eventType: 'invoiceClosed', eventHandler: (payload: { url: string; status: 'paid' | 'cancelled' | 'failed' | 'pending'; }) => void, ); offEvent( eventType: | 'backButtonClicked' | 'settingsButtonClicked' | 'invoiceClosed', eventHandler: (...args: any[]) => void, ); } interface WebApp extends TelegramWebApp.WebApp, Event { themeParams: ThemeParams; initDataUnsafe: WebAppInitData; MainButton: MainButton; version: string; headerColor: string; backgroundColor: string; BackButton: BackButton; HapticFeedback: HapticFeedback; isVersionAtLeast(version: string | number): boolean; setHeaderColor(color: 'bg_color' | 'secondary_bg_color' | string); setBackgroundColor(color: 'bg_color' | 'secondary_bg_color' | string); openLink(url: string); openTelegramLink(url: string); openInvoice( url: string, callback?: (status: 'paid' | 'cancelled' | 'failed' | 'pending') => void, ); } } ================================================ FILE: src/core/twa-types/WebAppVersion_6.2.d.ts ================================================ import { TelegramWebAppVersion6_1 } from './WebAppVersion_6.1'; import { TelegramWebApp } from './WebApp'; export declare namespace TelegramWebAppVersion6_2 { /** * {@link https://core.telegram.org/bots/webapps#webappuser} */ interface WebAppUser extends TelegramWebApp.WebAppUser { is_premium?: true; } /** * {@link https://core.telegram.org/bots/webapps#webappinitdata} */ interface WebAppInitData extends TelegramWebAppVersion6_1.WebAppInitData { user?: WebAppUser; receiver?: WebAppUser; } /** * {@link https://core.telegram.org/bots/webapps#popupbutton} */ interface PopupButton { id?: string; type?: string; text?: string; } /** * {@link https://core.telegram.org/bots/webapps#popupparams} */ interface PopupParams { title?: string; message: string; buttons?: PopupButton[]; } interface Event { onEvent( eventType: 'popupClosed', eventHandler: (payload: { button_id: string }) => void, ); offEvent(eventType: 'popupClosed', eventHandler: (...args: any[]) => void); } interface WebApp extends TelegramWebAppVersion6_1.WebApp, Event { initDataUnsafe: WebAppInitData; isClosingConfirmationEnabled: string; enableClosingConfirmation(); disableClosingConfirmation(); showPopup(params: PopupParams, callback?: (id: string) => void); showAlert(message: string, callback?: () => void); showConfirm(message: string, callback?: (isOk: boolean) => void); } } ================================================ FILE: src/core/twa-types/WebAppVersion_6.4.d.ts ================================================ import { TelegramWebAppVersion6_2 } from './WebAppVersion_6.2'; export declare namespace TelegramWebAppVersion6_4 { /** * {@link https://core.telegram.org/bots/webapps#scanqrpopupparams} */ interface ScanQrPopupParams { text?: string; } interface Event { onEvent(eventType: 'qrTextReceived', eventHandler: (data: string) => void); onEvent( eventType: 'clipboardTextReceived', eventHandler: (payload: { data: string | null }) => void, ); offEvent( eventType: 'qrTextReceived' | 'clipboardTextReceived', eventHandler: (...args: any[]) => void, ); } interface WebApp extends TelegramWebAppVersion6_2.WebApp, Event { openLink(url: string, options?: { try_instant_view: true }); platform: string; showScanQrPopup( params: ScanQrPopupParams, callback?: (data: string) => void | boolean, ); closeScanQrPopup(); readTextFromClipboard(callback?: (data: string) => void); } } ================================================ FILE: src/core/twa-types/WebAppVersion_6.7.d.ts ================================================ import { TelegramWebAppVersion6_4 } from './WebAppVersion_6.4'; export declare namespace TelegramWebAppVersion6_7 { interface WebApp extends TelegramWebAppVersion6_4.WebApp { switchInlineQuery(query: string, choose_chat_types?: unknown); } } ================================================ FILE: src/core/twa-types/WebAppVersion_6.9.d.ts ================================================ import { TelegramWebAppVersion6_7 } from './WebAppVersion_6.7'; import { TelegramWebAppVersion6_2 } from './WebAppVersion_6.2'; export declare namespace TelegramWebAppVersion6_9 { interface WebAppUser extends TelegramWebAppVersion6_2.WebAppUser { added_to_attachment_menu?: true; allows_write_to_pm?: true; } interface Event { onEvent( eventType: 'writeAccessRequested', eventHandler: (data: { status: 'allowed' | 'cancelled' }) => void, ); onEvent( eventType: 'contactRequested', eventHandler: (payload: { data: 'sent' | 'cancelled' }) => void, ); offEvent( eventType: 'writeAccessRequested' | 'contactRequested', eventHandler: (...args: any[]) => void, ); } type StorageKey = string; type CloudStorageCallback = | ((error: Error) => void) | ((error: null, result: T) => void); /** * {@link https://core.telegram.org/bots/webapps#cloudstorage} */ interface CloudStorage { setItem( key: StorageKey, value: string, callback?: CloudStorageCallback, ): void; getItem(key: StorageKey, callback: CloudStorageCallback): void; getItems( keys: StorageKey[], callback: CloudStorageCallback, ): void; removeItem(key: StorageKey, callback?: CloudStorageCallback): void; removeItems( keys: StorageKey[], callback?: CloudStorageCallback, ): void; getKeys(callback: CloudStorageCallback): void; } interface WebApp extends TelegramWebAppVersion6_7.WebApp, Event { CloudStorage: CloudStorage; } } ================================================ FILE: src/core/twa-types/WebAppVersion_7.0.d.ts ================================================ export declare namespace TelegramWebAppVersion7_0 { interface WebApp extends TelegramWebAppVersion6_9.WebApp { // TODO Fix types SettingsButton SettingsButton?: any; } } ================================================ FILE: src/core/twa-types/index.d.ts ================================================ import { TelegramWebApp } from './WebApp'; import { TelegramWebAppVersion6_1 } from './WebAppVersion_6.1'; import { TelegramWebAppVersion6_2 } from './WebAppVersion_6.2'; import { TelegramWebAppVersion6_4 } from './WebAppVersion_6.4'; import { TelegramWebAppVersion6_7 } from './WebAppVersion_6.7'; import { TelegramWebAppVersion6_9 } from './WebAppVersion_6.9'; import { TelegramWebAppVersion7_0 } from './WebAppVersion_7.0'; export type WebApp = TelegramWebApp.WebApp & Partial & Partial & Partial & Partial & Partial & Partial; ================================================ FILE: src/core/useAsyncMode.ts ================================================ import { useEffect, useState } from 'react'; const useAsyncMode = (enabled: boolean) => { const [isLoaded, setIsLoaded] = useState(false); useEffect(() => { if (!enabled) return; if (window.Telegram?.WebApp) { setIsLoaded(true); return; } const nativeProxyPostHandle = typeof window !== 'undefined' && window.TelegramWebviewProxy && window.TelegramWebviewProxy.postEvent; if (window.TelegramWebviewProxy) { window.TelegramWebviewProxy.postEvent = (...args: any[]) => { nativeProxyPostHandle?.(...args); if (window.Telegram?.WebApp) { setIsLoaded(true); window.TelegramWebviewProxy.postEvent = nativeProxyPostHandle; } }; } else { window.TelegramWebviewProxy = { postEvent: (...args: any[]) => { nativeProxyPostHandle?.(...args); if (window.Telegram?.WebApp) { setIsLoaded(true); delete window.TelegramWebviewProxy; } }, }; } }, [enabled]); return isLoaded; }; export default useAsyncMode; ================================================ FILE: src/core/useSmoothButtonsTransition.ts ================================================ import { MutableRefObject, useContext, useEffect } from 'react'; import { optionsContext } from './context'; const _noop = () => {}; const useSmoothButtonsTransition = ({ id, show = _noop, hide = _noop, currentShowedIdRef, }: { id: string; show: typeof _noop | undefined; hide: typeof _noop | undefined; currentShowedIdRef: MutableRefObject; }) => { const { smoothButtonsTransition, smoothButtonsTransitionMs } = useContext(optionsContext); useEffect(() => { show(); currentShowedIdRef.current = id; return () => { if (smoothButtonsTransition) { currentShowedIdRef.current = null; setTimeout(() => { if (currentShowedIdRef.current) return; hide(); }, smoothButtonsTransitionMs); } else { hide(); currentShowedIdRef.current = null; } }; }, [ hide, id, currentShowedIdRef, show, smoothButtonsTransition, smoothButtonsTransitionMs, ]); }; export default useSmoothButtonsTransition; ================================================ FILE: src/core/useWebApp.ts ================================================ import { useContext } from 'react'; import { webAppContext } from './context'; /** * @private * @ignore */ const useWebApp = () => { const context = useContext(webAppContext); return context; }; export default useWebApp; ================================================ FILE: src/index.ts ================================================ export { default as MainButton, MainButtonProps } from './MainButton'; export { default as BackButton, BackButtonProps } from './BackButton'; export { default as SettingsButton, SettingsButtonProps, } from './SettingsButton'; export { default as useShowPopup, ShowPopupFunction, ShowPopupParams, ShowPopupButton, } from './useShowPopup'; export { default as useHapticFeedback, ImpactOccurredFunction, NotificationOccurredFunction, SelectionChangedFunction, } from './useHapticFeedback'; export { default as useThemeParams, ThemeParams, ColorScheme, } from './useThemeParams'; export { default as useScanQrPopup, ScanQrPopupCallback, ScanQrPopupParams, ShowScanQrPopupFunction, CloseScanQrPopupFunction, } from './useScanQrPopup'; export { default as useReadTextFromClipboard, ReadTextFromClipboardFunction, } from './useReadTextFromClipboard'; export { default as useSwitchInlineQuery, SwitchInlineQueryFunction, } from './useSwitchInlineQuery'; export { default as useExpand } from './useExpand'; export { default as useCloudStorage, GetKeysFunction, GetItemFunction, GetItemsFunction, RemoveItemFunction, SetItemFunction, RemoveItemsFunction, } from './useCloudStorage'; export { default as WebAppProvider, WebAppProviderProps, } from './WebAppProvider'; export { default as useInitData, WebAppChat, WebAppUser, InitData, InitDataUnsafe, } from './useInitData'; export type { Options } from './core'; export { default as useWebApp } from './useWebApp'; ================================================ FILE: src/useCloudStorage.ts ================================================ import { useWebApp } from './core'; import { useCallback, useMemo } from 'react'; /** * This function provides `getItem` method from {@link telegram!CloudStorage} as Promise * @throws */ export type GetItemFunction = (key: string) => Promise; /** * This function provides `setItem` method from {@link telegram!CloudStorage} as Promise * @throws */ export type SetItemFunction = (key: string, value: string) => Promise; /** * This function provides `getItems` method from {@link telegram!CloudStorage} as Promise * @throws */ export type GetItemsFunction = (keys: string[]) => Promise; /** * This function provides `removeItem` method from {@link telegram!CloudStorage} as Promise * @throws */ export type RemoveItemFunction = (key: string) => Promise; /** * This function provides `removeItems` method from {@link telegram!CloudStorage} as Promise * @throws */ export type RemoveItemsFunction = (key: string[]) => Promise; /** * This function provides `getKeys` method from {@link telegram!CloudStorage} as Promise * @throws */ export type GetKeysFunction = () => Promise; /** * This hook provides {@link telegram!CloudStorage} object with promises functions, * so you don't have to pass `callback` argument * You have to look original description CloudStorage object in {@link telegram!CloudStorage} * @group Hooks */ const useCloudStorage = (): { getItem: GetItemFunction; setItem: SetItemFunction; getItems: GetItemsFunction; removeItem: RemoveItemFunction; getKeys: GetKeysFunction; } => { const cloudStorage = useWebApp()?.CloudStorage; const getItem: GetItemFunction = useCallback( key => new Promise((resolve, reject) => { cloudStorage?.getItem(key, (error, value) => { if (!error) { resolve(value); } else { reject(error); } }); }), [cloudStorage], ); const setItem: SetItemFunction = useCallback( (key, value) => new Promise((resolve, reject) => { cloudStorage?.setItem(key, value, (error, state) => { if (!error && state) { resolve(); } else { reject(error); } }); }), [cloudStorage], ); const getItems: GetItemsFunction = useCallback( key => new Promise((resolve, reject) => { cloudStorage?.getItems(key, (error, value) => { if (!error && value) { resolve(value); } else { reject(error); } }); }), [cloudStorage], ); const removeItem: RemoveItemFunction = useCallback( key => new Promise((resolve, reject) => { cloudStorage?.removeItem(key, (error, state) => { if (!error && state) { resolve(); } else { reject(error); } }); }), [cloudStorage], ); const removeItems: RemoveItemsFunction = useCallback( key => new Promise((resolve, reject) => { cloudStorage?.removeItems(key, (error, state) => { if (!error && state) { resolve(); } else { reject(error); } }); }), [cloudStorage], ); const getKeys: GetKeysFunction = useCallback( () => new Promise((resolve, reject) => { cloudStorage?.getKeys((error, state) => { if (!error && state) { resolve(state); } else { reject(error); } }); }), [cloudStorage], ); return useMemo( () => ({ getItem, setItem, getItems, removeItem, removeItems, getKeys, }), // Осознанно в зависимостях только cloudStorage // eslint-disable-next-line react-hooks/exhaustive-deps [cloudStorage], ); }; export default useCloudStorage; ================================================ FILE: src/useExpand.ts ================================================ import { DispatchWithoutAction, useCallback, useEffect, useState } from 'react'; import { useWebApp } from './core'; /** * This hook provided isExpanded state, and expand() handle * You have to look original description in {@link telegram!WebApp} for more information * * `isExpanded` can be `undefined` * * ```tsx * import { useExpand } from "@vkruglikov/react-telegram-web-app"; * * const [isExpanded, expand] = useExpand(); * const handleClick = () => !isExpanded && expand(); * * * ``` * * @privateRemarks * Api doesn't provide event for listening isExpanded, so we use * viewportChanged, but it is an unsafe way * * @group Hooks */ const useExpand = (): readonly [boolean | undefined, DispatchWithoutAction] => { const WebApp = useWebApp(); const [isExpanded, setIsExpanded] = useState(WebApp?.isExpanded); useEffect(() => { if (!WebApp) return; const handleEvent = (payload: { isStateStable: boolean }) => { if (payload.isStateStable) { setIsExpanded(WebApp.isExpanded); } }; WebApp.onEvent('viewportChanged', handleEvent); return () => WebApp.offEvent('viewportChanged', handleEvent); }, [WebApp]); const handleExpand = useCallback(() => WebApp?.expand?.(), [WebApp]); return [isExpanded, handleExpand] as const; }; export default useExpand; ================================================ FILE: src/useHapticFeedback.ts ================================================ import { useWebApp } from './core'; import { useCallback } from 'react'; /** * A method tells that an impact occurred. The Telegram app may play the appropriate haptics based on style value passed. Style can be one of these values: * - light, indicates a collision between small or lightweight UI objects, * - medium, indicates a collision between medium-sized or medium-weight UI objects, * - heavy, indicates a collision between large or heavyweight UI objects, * - rigid, indicates a collision between hard or inflexible UI objects, * - soft, indicates a collision between soft or flexible UI objects. * {@link telegram!HapticFeedback} */ export type ImpactOccurredFunction = ( style: 'light' | 'medium' | 'heavy' | 'rigid' | 'soft', ) => void; /** * A method tells that a task or action has succeeded, failed, or produced a warning. The Telegram app may play the appropriate haptics based on type value passed. Type can be one of these values: * - error, indicates that a task or action has failed, * - success, indicates that a task or action has completed successfully, * - warning, indicates that a task or action produced a warning. * {@link telegram!HapticFeedback} */ export type NotificationOccurredFunction = ( type: 'error' | 'success' | 'warning', ) => void; /** * A method tells that the user has changed a selection. The Telegram app may play the appropriate haptics. * {@link telegram!HapticFeedback} */ export type SelectionChangedFunction = () => void; /** * This hook that provided {@link ImpactOccurredFunction}, {@link NotificationOccurredFunction} and {@link SelectionChangedFunction} functions that controls haptic feedback. * You have to look original telegram description {@link telegram!HapticFeedback}, because it Hook implementing his. * * ```tsx * import { useHapticFeedback } from "@vkruglikov/react-telegram-web-app"; * * const [impactOccurred, notificationOccurred, selectionChanged] = * useHapticFeedback(); * // const [,notificationOccurred] = useHapticFeedback(); * * impactOccurred('heavy'); * notificationOccurred('success'); * ``` * * @group Hooks */ const useHapticFeedback = (): readonly [ ImpactOccurredFunction, NotificationOccurredFunction, SelectionChangedFunction, ] => { const WebApp = useWebApp(); const HapticFeedback = WebApp?.HapticFeedback; const impactOccurred: ImpactOccurredFunction = useCallback( (...args) => HapticFeedback?.impactOccurred(...args), [HapticFeedback], ); const notificationOccurred: NotificationOccurredFunction = useCallback( (...args) => HapticFeedback?.notificationOccurred(...args), [HapticFeedback], ); const selectionChanged: SelectionChangedFunction = useCallback( (...args) => HapticFeedback?.selectionChanged(...args), [HapticFeedback], ); return [impactOccurred, notificationOccurred, selectionChanged] as const; }; export default useHapticFeedback; ================================================ FILE: src/useInitData.ts ================================================ import { useWebApp } from './core'; /** * {@link telegram!WebAppChat} */ export type WebAppChat = { id: number; type: 'group' | 'supergroup' | 'channel'; title: string; username?: string; photo_url?: string; }; /** * {@link telegram!WebAppUser} */ export type WebAppUser = { id: number; is_bot?: boolean; first_name: string; last_name?: string; username?: string; language_code?: string; photo_url?: string; is_premium?: boolean; added_to_attachment_menu?: true; allows_write_to_pm?: true; }; export type InitData = string; /** * {@link telegram!WebAppInitData} */ export type InitDataUnsafe = { query_id?: string; user?: WebAppUser; receiver?: WebAppUser; chat_type?: 'sender' | 'private' | 'group' | 'supergroup' | 'channel'; chat_instance?: string; start_param?: string; auth_date: number; hash: string; chat?: WebAppChat; can_send_after?: number; }; /** * This hook provides `initDataUnsafe` and `initData` * You have to look original description in {@link telegram!WebApp}, because hook just return this. * * ```tsx * import { useInitData } from "@vkruglikov/react-telegram-web-app"; * * const [initDataUnsafe] = useInitData(); * const [initDataUnsafe, initData] = useInitData(); * * ``` * @group Hooks */ const useInitData = (): readonly [ InitDataUnsafe | undefined, InitData | undefined, ] => { const WebApp = useWebApp(); return [WebApp?.initDataUnsafe, WebApp?.initData] as const; }; export default useInitData; ================================================ FILE: src/useReadTextFromClipboard.ts ================================================ import { useCallback } from 'react'; import { useWebApp } from './core'; /** * This function provided Promise function that read text from clipboard * @return {Promise} */ export type ReadTextFromClipboardFunction = () => Promise; /** * This hook that provided {@link ReadTextFromClipboardFunction} Promise function that read text from clipboard. * You have to look original description readTextFromClipboard in {@link telegram!WebApp}, because hook just implements his. * * ```tsx * import { useReadTextFromClipboard } from "@vkruglikov/react-telegram-web-app"; * * const readText = useReadTextFromClipboard(); * * readText().then(console.log); * // or * await readText() * ``` * * @return {ReadTextFromClipboardFunction} * @group Hooks */ const useReadTextFromClipboard = (): ReadTextFromClipboardFunction => { const WebApp = useWebApp(); return useCallback( () => new Promise(resolve => { WebApp?.readTextFromClipboard?.(resolve); }), [WebApp], ); }; export default useReadTextFromClipboard; ================================================ FILE: src/useScanQrPopup.ts ================================================ import { useWebApp } from './core'; import { useCallback } from 'react'; /** * If an optional callback parameter was passed, the callback function will be called and the text from the QR * code will be passed as the first argument. * Returning true inside this callback function causes the popup to be closed. */ export type ScanQrPopupCallback = (text: string) => true | void; /** * This object describes the native popup for scanning QR codes. * You have to see original interface {@link telegram!ScanQrPopupParams}. */ export interface ScanQrPopupParams { /** * The text to be displayed under the 'Scan QR' heading, 0-64 characters. */ text: string; } /** * A method that shows a native popup for scanning a QR code described * by the params argument of the type {@link ScanQrPopupParams}. */ export type ShowScanQrPopupFunction = ( params: ScanQrPopupParams, callback?: ScanQrPopupCallback, ) => void; /** * A method that closes the native popup for scanning a QR code opened with the showScanQrPopup method */ export type CloseScanQrPopupFunction = () => void; /** * The hook provided showScanQrPopup function of the type {@link ShowScanQrPopupFunction} and closeScanQrPopup {@link CloseScanQrPopupFunction}. * @group Hooks */ const useScanQrPopup = (): readonly [ ShowScanQrPopupFunction, CloseScanQrPopupFunction, ] => { const WebApp = useWebApp(); const showScanQrPopup: ShowScanQrPopupFunction = useCallback( (...args) => WebApp?.showScanQrPopup?.(...args), [WebApp], ); const closeScanQrPopup: CloseScanQrPopupFunction = useCallback( () => WebApp?.closeScanQrPopup?.(), [WebApp], ); return [showScanQrPopup, closeScanQrPopup] as const; }; export default useScanQrPopup; ================================================ FILE: src/useShowPopup.ts ================================================ import { useCallback } from 'react'; import { useWebApp } from './core'; /** * You have to see original interface {@link telegram!PopupButton}. */ export interface ShowPopupButton extends Record { /** * Optional. Identifier of the button, 0-64 characters. * Set to empty string by default. * If the button is pressed, its id is returned in the callback and the popupClosed event. */ id?: string; /** * Optional. Type of the button. * Set to default by default. */ type?: 'default' | 'ok' | 'close' | 'cancel' | 'destructive' | string; /** * Optional. The text to be displayed on the button, 0-64 characters. * Required if type is default or destructive. * Irrelevant for other types. */ text?: string; } /** * You have to see original interface {@link telegram!PopupParams}. */ export interface ShowPopupParams extends Record { /** * Optional. The text to be displayed in the popup title, 0-64 characters. */ title?: string; /** * The message to be displayed in the body of the popup, 1-256 characters. */ message: string; /** * Optional. List of buttons to be displayed in the popup, 1-3 buttons */ buttons?: ShowPopupButton[]; } /** * This function provides Promise, and resolve the field id of the pressed button will be passed. * @return Button id as string, it was described by {@link ShowPopupButton} * @throws */ export type ShowPopupFunction = (params: ShowPopupParams) => Promise; /** * The hook provided showPopup function of the type {@link ShowPopupFunction}. * The function that shows a native popup described by the params argument of the type {@link ShowPopupParams}. * * ```tsx * import { useShowPopup } from "@vkruglikov/react-telegram-web-app"; * * const showPopup = useShowPopup(); * * showPopup({ message: 'Hello world' }).then((buttonId) => console.log(buttonId)); * ``` * * @group Hooks */ const useShowPopup: () => ShowPopupFunction = () => { const WebApp = useWebApp(); return useCallback( params => new Promise((resolve, reject) => { try { WebApp?.showPopup?.(params, (buttonId: string) => { resolve(buttonId); }); } catch (e) { reject(e); } }), [WebApp], ); }; export default useShowPopup; ================================================ FILE: src/useSwitchInlineQuery.ts ================================================ import { useWebApp } from './core'; import { useCallback } from 'react'; /** * This function that inserts the bot's username and the specified inline query in the current chat's input field * You have to look original description switchInlineQuery in {@link telegram!WebApp} for more information */ export type SwitchInlineQueryFunction = ( query: string, chatType?: ('users' | 'bots' | 'groups' | 'channels')[], ) => void; /** * This hook that provided {@link SwitchInlineQueryFunction} * You have to look original description switchInlineQuery in {@link telegram!WebApp}, because hook just implements his. * @return {SwitchInlineQueryFunction} * @group Hooks */ const useSwitchInlineQuery = (): SwitchInlineQueryFunction => { const WebApp = useWebApp(); return useCallback( (...args) => WebApp?.switchInlineQuery?.(...args), [WebApp], ); }; export default useSwitchInlineQuery; ================================================ FILE: src/useThemeParams.ts ================================================ import { useEffect, useState } from 'react'; import { useWebApp } from './core'; /** * This object contains the user's current theme settings. * This object implement original Telegram WebApp type of {@link telegram!ThemeParams} */ export interface ThemeParams { /** * Background color in the #RRGGBB format. */ bg_color?: string; /** * Main text color in the #RRGGBB format. */ text_color?: string; /** * Hint text color in the #RRGGBB format. */ hint_color?: string; /** * Link color in the #RRGGBB format. */ link_color?: string; /** * Button color in the #RRGGBB format. */ button_color?: string; /** * Button text color in the #RRGGBB format. */ button_text_color?: string; /** * Secondary background color in the #RRGGBB format. */ secondary_bg_color?: string; } /** * The color scheme currently used in the Telegram app. Either “light” or “dark”. * Can `undefined`, if `window` is undefined. */ export type ColorScheme = 'light' | 'dark' | undefined; /** * The hook provided colorScheme and themeParams values of the type {@link ColorScheme} and {@link ThemeParams}. * * ```tsx * import { useThemeParams } from "@vkruglikov/react-telegram-web-app"; * * const [colorScheme, themeParams] = useThemeParams(); * * console.log(colorScheme === 'dark'); * console.log({ * text_color: themeParams.text_color, * button_color: themeParams.button_color, * bg_color: themeParams.bg_color, * }); * ``` * @group Hooks */ const useThemeParams: () => readonly [ColorScheme, ThemeParams] = () => { const WebApp = useWebApp(); const [colorScheme, setColor] = useState(WebApp?.colorScheme); const [themeParams, setThemeParams] = useState( WebApp?.themeParams || {}, ); useEffect(() => { if (!WebApp) return; const eventHandler = () => { setColor(WebApp.colorScheme); setThemeParams(WebApp.themeParams); }; WebApp.onEvent('themeChanged', eventHandler); return () => { WebApp.offEvent('themeChanged', eventHandler); }; }, [WebApp]); return [colorScheme, themeParams] as const; }; export default useThemeParams; ================================================ FILE: src/useWebApp.ts ================================================ import { useWebApp as _useWebApp } from './core'; /** * This hook just provides native {@link telegram!WebApp} object * * ```tsx * import { useWebApp } from "@vkruglikov/react-telegram-web-app"; * * const WebApp = useWebApp(); * * console.log(WebApp.version); * ``` * @group Hooks */ const useWebApp = () => _useWebApp() as any; export default useWebApp; ================================================ FILE: tests/BackButton.test.tsx ================================================ import * as React from 'react'; import BackButton from '../src/BackButton'; import { useWebApp } from '../src/core'; import { renderComponentTree } from './utils'; describe('BackButton', () => { it('checks call show(), hide() WebApp.BackButton api', () => { const WebApp = useWebApp(); renderComponentTree(() => ); expect(WebApp?.BackButton?.show).toBeCalledTimes(1); expect(WebApp?.BackButton?.hide).toBeCalledTimes(1); }); it('checks correct bind cached onClick', () => { const WebApp = useWebApp(); const handleClick = jest.fn(); renderComponentTree(() => ); expect(WebApp?.BackButton?.onClick).toBeCalledTimes(1); expect(WebApp?.BackButton?.offClick).toBeCalledTimes(1); expect(WebApp?.BackButton?.onClick).toBeCalledWith(handleClick); expect(WebApp?.BackButton?.offClick).toBeCalledWith(handleClick); }); it('checks correct bind uncached onClick', () => { const WebApp = useWebApp(); renderComponentTree(() => ); expect(WebApp?.BackButton?.onClick).toBeCalledTimes(3); expect(WebApp?.BackButton?.offClick).toBeCalledTimes(3); }); }); ================================================ FILE: tests/MainButton.test.tsx ================================================ import * as React from 'react'; import MainButton from '../src/MainButton'; import { useWebApp } from '../src/core'; import { renderComponentTree } from './utils'; describe('MainButton', () => { it('checks call show(), hide() WebApp.MainButton api', () => { const WebApp = useWebApp(); renderComponentTree(() => ); expect(WebApp?.MainButton.show).toBeCalledTimes(1); expect(WebApp?.MainButton.hide).toBeCalledTimes(1); }); it('checks correct bind cached onClick', () => { const WebApp = useWebApp(); const handleClick = jest.fn(); renderComponentTree(() => ); expect(WebApp?.MainButton.onClick).toBeCalledTimes(1); expect(WebApp?.MainButton.offClick).toBeCalledTimes(1); expect(WebApp?.MainButton.onClick).toBeCalledWith(handleClick); expect(WebApp?.MainButton.offClick).toBeCalledWith(handleClick); }); it('checks correct bind uncached onClick', () => { const WebApp = useWebApp(); renderComponentTree(() => ); expect(WebApp?.MainButton.onClick).toBeCalledTimes(3); expect(WebApp?.MainButton.offClick).toBeCalledTimes(3); }); it('checks call showProgress(), hideProgress() WebApp.MainButton api', () => { const WebApp = useWebApp(); const updates = [ , , , ]; renderComponentTree(() => updates.shift() || ); expect(WebApp?.MainButton.showProgress).toBeCalledTimes(2); expect(WebApp?.MainButton.hideProgress).toBeCalledTimes(1); }); it('checks call disable(), enable() WebApp.MainButton api', () => { const WebApp = useWebApp(); const updates = [ , , , ]; renderComponentTree(() => updates.shift() || ); expect(WebApp?.MainButton.disable).toBeCalledTimes(2); expect(WebApp?.MainButton.enable).toBeCalledTimes(1); }); it('checks call setText() WebApp.MainButton api', () => { const WebApp = useWebApp(); const updates = [ , , , ]; renderComponentTree(() => updates.shift() || ); expect((WebApp?.MainButton.setText as jest.Mock).mock.calls).toEqual([ ['Hello'], ['CONTINUE'], ['My friend'], ]); }); it('checks textColor props', () => { const WebApp = useWebApp(); const updates = [ , , , ]; renderComponentTree(() => updates.shift() || ); expect(WebApp?.MainButton.setParams).toBeCalledWith( expect.objectContaining({ text_color: '#fff', }), ); expect(WebApp?.MainButton.setParams).toBeCalledWith( expect.objectContaining({ text_color: '#000', }), ); expect(WebApp?.MainButton.setParams).toBeCalledWith( expect.objectContaining({ text_color: '#0f0', }), ); }); it('checks color props', () => { const WebApp = useWebApp(); const updates = [ , , , ]; renderComponentTree(() => updates.shift() || ); expect(WebApp?.MainButton.setParams).toBeCalledWith( expect.objectContaining({ color: '#f0f', }), ); expect(WebApp?.MainButton.setParams).toBeCalledWith( expect.objectContaining({ color: '#fff', }), ); expect(WebApp?.MainButton.setParams).toBeCalledWith( expect.objectContaining({ color: '#0f0', }), ); }); }); ================================================ FILE: tests/__snapshots__/package.test.ts.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Package /lib should have correct /lib structure 1`] = ` [ "/BackButton.d.ts", "/MainButton.d.ts", "/SettingsButton.d.ts", "/WebAppProvider.d.ts", [ "/core/context.d.ts", "/core/index.d.ts", [ "/core/twa-types/WebApp.d.ts", "/core/twa-types/WebAppVersion_6.1.d.ts", "/core/twa-types/WebAppVersion_6.2.d.ts", "/core/twa-types/WebAppVersion_6.4.d.ts", "/core/twa-types/WebAppVersion_6.7.d.ts", "/core/twa-types/WebAppVersion_6.9.d.ts", "/core/twa-types/WebAppVersion_7.0.d.ts", "/core/twa-types/index.d.ts", ], "/core/useAsyncMode.d.ts", "/core/useSmoothButtonsTransition.d.ts", "/core/useWebApp.d.ts", ], "/index.d.ts", "/react-telegram-web-app.cjs", "/react-telegram-web-app.modern.js", "/react-telegram-web-app.module.js", "/react-telegram-web-app.umd.js", "/useCloudStorage.d.ts", "/useExpand.d.ts", "/useHapticFeedback.d.ts", "/useInitData.d.ts", "/useReadTextFromClipboard.d.ts", "/useScanQrPopup.d.ts", "/useShowPopup.d.ts", "/useSwitchInlineQuery.d.ts", "/useThemeParams.d.ts", "/useWebApp.d.ts", ] `; exports[`Package /lib should have exports from modules 1`] = ` { "BackButton": [Function], "MainButton": [Function], "SettingsButton": [Function], "WebAppProvider": [Function], "useCloudStorage": [Function], "useExpand": [Function], "useHapticFeedback": [Function], "useInitData": [Function], "useReadTextFromClipboard": [Function], "useScanQrPopup": [Function], "useShowPopup": [Function], "useSwitchInlineQuery": [Function], "useThemeParams": [Function], "useWebApp": [Function], } `; ================================================ FILE: tests/core/__mocks__/useWebApp.ts ================================================ const instance = { BackButton: { show: jest.fn(), hide: jest.fn(), onClick: jest.fn(), offClick: jest.fn(), }, MainButton: { show: jest.fn(), hide: jest.fn(), setParams: jest.fn(), setText: jest.fn(), disable: jest.fn(), enable: jest.fn(), onClick: jest.fn(), offClick: jest.fn(), showProgress: jest.fn(), hideProgress: jest.fn(), }, HapticFeedback: { impactOccurred: jest.fn(), selectionChanged: jest.fn(), notificationOccurred: jest.fn(), }, themeParams: {}, onEvent: jest.fn(), offEvent: jest.fn(), isExpanded: undefined, expand: jest.fn(), readTextFromClipboard: jest.fn(), showScanQrPopup: jest.fn(), closeScanQrPopup: jest.fn(), showPopup: jest.fn(), switchInlineQuery: jest.fn(), colorScheme: undefined, }; export default () => instance; ================================================ FILE: tests/core/useSmoothButtonsTransition.test.ts ================================================ import { useSmoothButtonsTransition } from '../../src/core'; import { act, renderHook } from '@testing-library/react'; describe('useSmoothButtonsTransition', () => { afterEach(() => { jest.useRealTimers(); }); it.each([ [{ smoothButtonsTransition: false, smoothButtonsTransitionMs: undefined }], [{ smoothButtonsTransition: true, smoothButtonsTransitionMs: 50 }], [{ smoothButtonsTransition: true, smoothButtonsTransitionMs: 100 }], [{ smoothButtonsTransition: false, smoothButtonsTransitionMs: 100 }], [{ smoothButtonsTransition: undefined, smoothButtonsTransitionMs: 100 }], ])( 'checks correct call show(),hide() with options %p', ({ smoothButtonsTransition, smoothButtonsTransitionMs }) => { jest.spyOn(require('react'), 'useContext').mockImplementation(() => ({ smoothButtonsTransition, smoothButtonsTransitionMs, })); const setTimeoutSpy = jest .spyOn(global, 'setTimeout') .mockImplementation(handler => { handler(); return undefined as unknown as ReturnType; }); const initialProps = { show: jest.fn(), hide: jest.fn(), currentShowedIdRef: { current: null, }, id: 'idButton', }; const { rerender, unmount } = renderHook(useSmoothButtonsTransition, { initialProps, }); expect(initialProps.show).toBeCalledTimes(1); expect(initialProps.hide).toBeCalledTimes(0); expect(initialProps.currentShowedIdRef.current).toBe('idButton'); act(() => { rerender(initialProps); }); expect(initialProps.currentShowedIdRef.current).toBe('idButton'); act(() => { rerender(initialProps); }); unmount(); expect(initialProps.currentShowedIdRef.current).toBe(null); expect(initialProps.show).toBeCalledTimes(1); expect(initialProps.hide).toBeCalledTimes(1); expect(setTimeoutSpy).toBeCalledTimes(smoothButtonsTransition ? 1 : 0); if (smoothButtonsTransition) { expect(setTimeoutSpy).toBeCalledWith( expect.any(Function), smoothButtonsTransitionMs, ); } }, ); }); ================================================ FILE: tests/package.test.ts ================================================ import * as fsAsync from 'fs/promises'; import * as fs from 'fs'; import * as path from 'path'; const BUILD_PATH = path.resolve(__dirname, '../lib'); const COMMON_JS_MODULE = 'react-telegram-web-app.cjs'; const walk = async (dirPath: string): Promise => Promise.all( await fsAsync.readdir(dirPath, { withFileTypes: true }).then(entries => entries.map(entry => { const childPath = path.join(dirPath, entry.name); return entry.isDirectory() ? walk(childPath) : childPath.replace(BUILD_PATH, ''); }), ), ); describe('Package /lib', () => { beforeEach(() => { jest.resetModules(); }); it('should have correct /lib structure', async () => { expect(await walk(BUILD_PATH)).toMatchSnapshot(); }); it('should have exports from modules', () => { const indexModule = require(path.join(BUILD_PATH, COMMON_JS_MODULE)); expect(indexModule).toMatchSnapshot(); }); }); describe('Public API documentation', () => { it('should have describe in README.md', () => { const indexModule = Object.keys( require(path.join(BUILD_PATH, COMMON_JS_MODULE)), ); const mdFile = fs.readFileSync( path.resolve(__dirname, '../README.md'), 'utf8', ); expect( indexModule .map(name => `[${name}](./docs/README.md#${name.toLowerCase()})`) .filter(name => !mdFile.includes(name)), ).toStrictEqual([]); }); }); ================================================ FILE: tests/setupTests.ts ================================================ jest.mock( '../src/core/useWebApp', () => require('./core/__mocks__/useWebApp').default, ); global.beforeEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); }); ================================================ FILE: tests/useCloudStorage.test.ts ================================================ import { renderHook } from '@testing-library/react'; import useCloudStorage from '../src/useCloudStorage'; // TODO Написать юниты на хук describe('useCloudStorage', () => { it('TODO', () => { renderHook(useCloudStorage); }); }); ================================================ FILE: tests/useExpand.test.tsx ================================================ import { act, renderHook } from '@testing-library/react'; import { useWebApp } from '../src/core'; import { WebApp } from '../src/core/twa-types'; import useExpand from '../src/useExpand'; describe('useExpand', () => { it.each([ [true, true], [false, false], [undefined, undefined], ])( 'checks WebApp.isExpanded = %p hook return isExpanded default value = %p', (isExpanded, expectedIsExpanded) => { jest.replaceProperty( useWebApp() as WebApp, 'isExpanded', isExpanded as boolean, ); const { result } = renderHook(useExpand); act(() => { expect(result.current[0]).toBe(expectedIsExpanded); }); }, ); /** * TODO перенести тест перебинда в тест useWebApp */ it('checks correct rebind event handler', () => { const { rerender, unmount } = renderHook(useExpand); act(() => { rerender(); }); /** * After one rerender, should be called onEvent once */ expect(useWebApp()?.onEvent).toBeCalledTimes(1); expect(useWebApp()?.offEvent).toBeCalledTimes(0); expect(useWebApp()?.onEvent).toBeCalledWith( 'viewportChanged', expect.any(Function), ); (useWebApp()?.onEvent as jest.Mock).mockClear(); act(() => { rerender(); }); expect(useWebApp()?.onEvent).toBeCalledTimes(0); expect(useWebApp()?.offEvent).toBeCalledTimes(0); (useWebApp()?.offEvent as jest.Mock).mockClear(); act(() => { unmount(); }); expect(useWebApp()?.onEvent).toBeCalledTimes(0); expect(useWebApp()?.offEvent).toBeCalledTimes(1); expect(useWebApp()?.offEvent).toBeCalledWith( 'viewportChanged', expect.any(Function), ); }); it('checks correct change state isExpanded', () => { jest.replaceProperty(useWebApp() as WebApp, 'isExpanded', true); const fireEventHandler = (state: boolean) => { jest.replaceProperty(useWebApp() as WebApp, 'isExpanded', state); act(() => { (useWebApp()?.onEvent as jest.Mock).mock.lastCall?.[1]?.({ isStateStable: true, }); rerender(); }); }; const { result, rerender } = renderHook(useExpand); act(() => { expect(result.current[0]).toBe(true); }); fireEventHandler(false); expect(result.current[0]).toBe(false); fireEventHandler(true); expect(result.current[0]).toBe(true); }); it('checks correct call WebApp.expand() from hook', () => { const { result } = renderHook(useExpand); act(() => { result.current[1](); }); expect(useWebApp()?.expand as jest.Mock).toBeCalledTimes(1); }); }); ================================================ FILE: tests/useHapticFeedback.test.ts ================================================ import { act, renderHook } from '@testing-library/react'; import useHapticFeedback from '../src/useHapticFeedback'; import { useWebApp } from '../src/core'; describe('useHapticFeedback', () => { it('checks correct call WebApp.HapticFeedback api', () => { const { result } = renderHook(useHapticFeedback); const [impactOccurred, notificationOccurred, selectionChanged] = result.current; act(() => { impactOccurred('soft'); notificationOccurred('warning'); selectionChanged(); }); expect(useWebApp()?.HapticFeedback?.impactOccurred).toBeCalledTimes(1); expect(useWebApp()?.HapticFeedback?.impactOccurred).toBeCalledWith('soft'); expect(useWebApp()?.HapticFeedback?.notificationOccurred).toBeCalledTimes( 1, ); expect(useWebApp()?.HapticFeedback?.notificationOccurred).toBeCalledWith( 'warning', ); expect(useWebApp()?.HapticFeedback?.selectionChanged).toBeCalledTimes(1); expect(useWebApp()?.HapticFeedback?.selectionChanged).toBeCalledWith(); }); }); ================================================ FILE: tests/useInitData.test.ts ================================================ import { renderHook } from '@testing-library/react'; import useCloudStorage from '../src/useCloudStorage'; // TODO Написать юниты на хук describe('useInitData', () => { it('TODO', () => { renderHook(useCloudStorage); }); }); ================================================ FILE: tests/useReadTextFromClipboard.test.ts ================================================ import { renderHook } from '@testing-library/react'; import useReadTextFromClipboard from '../src/useReadTextFromClipboard'; import { useWebApp } from '../src/core'; import { WebApp } from '../src/core/twa-types'; describe('useReadTextFromClipboard', () => { it('checks correct call WebApp.readTextFromClipboard api', async () => { const { result } = renderHook(useReadTextFromClipboard); const readTextFromClickBoard = result.current; jest .spyOn(useWebApp() as WebApp, 'readTextFromClipboard') .mockImplementation(resolve => { resolve!('TEST_CLICK'); }); const text = await readTextFromClickBoard(); expect(text).toBe('TEST_CLICK'); }); }); ================================================ FILE: tests/useScanQrPopup.test.ts ================================================ import { renderHook } from '@testing-library/react'; import useScanQrPopup from '../src/useScanQrPopup'; import { useWebApp } from '../src/core'; describe('useScanQrPopup', () => { it('checks correct call WebApp.showScanQrPopup api', () => { const { result } = renderHook(useScanQrPopup); const [show] = result.current; show({ text: 'Text under QR', }); expect(useWebApp()?.showScanQrPopup).toBeCalledTimes(1); expect(useWebApp()?.showScanQrPopup).toBeCalledWith({ text: 'Text under QR', }); }); it('checks correct call WebApp.closeScanQrPopup api', () => { const { result } = renderHook(useScanQrPopup); const [_, close] = result.current; close(); expect(useWebApp()?.closeScanQrPopup).toBeCalledTimes(1); expect(useWebApp()?.closeScanQrPopup).toBeCalledWith(); }); }); ================================================ FILE: tests/useShowPopup.test.ts ================================================ import { renderHook } from '@testing-library/react'; import useShowPopup from '../src/useShowPopup'; import { useWebApp } from '../src/core'; import { WebApp } from '../src/core/twa-types'; describe('useShowPopup', () => { it('checks correct call WebApp.showPopup api', async () => { const { result } = renderHook(useShowPopup); const showPopup = result.current; const spyShowPopup = jest .spyOn(useWebApp() as WebApp, 'showPopup') .mockImplementation((_, callback) => { callback!('buttonId'); }); const params = { title: 'title', message: 'message', buttons: [ { id: 'buttonId', type: 'cancel', text: 'textButton', }, ], }; const button = await showPopup(params); expect(spyShowPopup).toBeCalledWith(params, expect.any(Function)); expect(button).toBe('buttonId'); }); }); ================================================ FILE: tests/useSwitchInlineQuery.test.ts ================================================ import { renderHook } from '@testing-library/react'; import { useWebApp } from '../src/core'; import useSwitchInlineQuery from '../src/useSwitchInlineQuery'; describe('useSwitchInlineQuery', () => { it('checks correct call WebApp.switchInlineQuery api', () => { const { result } = renderHook(useSwitchInlineQuery); const switchInlineQuery = result.current; switchInlineQuery('Test string'); expect(useWebApp()!.switchInlineQuery).toBeCalledWith('Test string'); switchInlineQuery('Test string 2', ['groups']); expect(useWebApp()!.switchInlineQuery).toBeCalledWith('Test string 2', [ 'groups', ]); expect(useWebApp()!.switchInlineQuery).toBeCalledTimes(2); }); }); ================================================ FILE: tests/useThemeParams.test.ts ================================================ import { act, renderHook } from '@testing-library/react'; import { useWebApp } from '../src/core'; import { WebApp } from '../src/core/twa-types'; import useThemeParams from '../src/useThemeParams'; describe('useThemeParams', () => { it('checks is correct initial value return', () => { jest.replaceProperty(useWebApp() as WebApp, 'colorScheme', 'dark'); jest.replaceProperty(useWebApp() as WebApp, 'themeParams', { bg_color: '#fff', }); const { result } = renderHook(useThemeParams); const [colorScheme, themeParams] = result.current; expect(colorScheme).toBe('dark'); expect(themeParams).toEqual({ bg_color: '#fff', }); }); it('checks correct return value after fire onEvent', function () { let handleOnEvent = () => {}; jest .spyOn(useWebApp() as WebApp, 'onEvent') .mockImplementation((_, handler) => { handleOnEvent = handler; }); const { result } = renderHook(useThemeParams); expect(result.current[0]).toBe(undefined); expect(result.current[1]).toEqual({}); expect(useWebApp()?.onEvent).toBeCalledWith( 'themeChanged', expect.any(Function), ); expect(useWebApp()?.onEvent).toBeCalledTimes(1); jest.replaceProperty(useWebApp() as WebApp, 'colorScheme', 'dark'); jest.replaceProperty(useWebApp() as WebApp, 'themeParams', { bg_color: '#000', }); act(() => { handleOnEvent(); }); act(() => { expect(result.current[0]).toBe('dark'); expect(result.current[1]).toEqual({ bg_color: '#000', }); }); }); }); ================================================ FILE: tests/utils.ts ================================================ import * as React from 'react'; import { ReactTestRenderer } from 'react-test-renderer'; import * as renderer from 'react-test-renderer'; export const renderComponentTree = (fabric: () => React.ReactElement) => { let tree: ReactTestRenderer | undefined = undefined; renderer.act(() => { tree = renderer.create(fabric()); }); renderer.act(() => { tree!.update(fabric()); }); renderer.act(() => { tree!.update(fabric()); }); renderer.act(() => { tree!.unmount(); }); return tree as unknown as ReactTestRenderer; }; ================================================ FILE: tsconfig.json ================================================ { "include": ["src", "global.d.ts"], "exclude": ["tests"], "compilerOptions": { "skipLibCheck": true, "target": "ESNext", "module": "ESNext", "moduleResolution": "node", "esModuleInterop": true, "jsx": "react", "jsxFactory": "React.createElement", "jsxFragmentFactory": "React.Fragment", "noImplicitReturns": true, "noImplicitThis": true, "noImplicitAny": true, "noUncheckedIndexedAccess": true, "importHelpers": true, "strictNullChecks": true } } ================================================ FILE: typedoc.json ================================================ { "entryPoints": ["src/index.ts"], "githubPages": true, "plugin": ["typedoc-plugin-markdown"], "out": "docs", "readme": "none", "disableSources": true, "externalSymbolLinkMappings": { "telegram": { "CloudStorage": "https://core.telegram.org/bots/webapps#cloudstorage", "MainButton": "https://core.telegram.org/bots/webapps#mainbutton", "SettingsButton": "https://core.telegram.org/bots/webapps#settingsbutton", "ScanQrPopupParams": "https://core.telegram.org/bots/webapps#scanqrpopupparams", "BackButton": "https://core.telegram.org/bots/webapps#backbutton", "PopupParams": "https://core.telegram.org/bots/webapps#popupparams", "PopupButton": "https://core.telegram.org/bots/webapps#popupbutton", "HapticFeedback": "https://core.telegram.org/bots/webapps#hapticfeedback", "ThemeParams": "https://core.telegram.org/bots/webapps#themeparams", "WebApp": "https://core.telegram.org/bots/webapps#initializing-mini-apps", "WebAppInitData": "https://core.telegram.org/bots/webapps#webappinitdata", "WebAppUser": "https://core.telegram.org/bots/webapps#webappuser", "WebAppChat": "https://core.telegram.org/bots/webapps#webappchat" }, "react": { "Component": "https://reactjs.org/docs/react-component.html" } } }