feat(telegram): Mini App embedding enhancements #136
@@ -49,6 +49,11 @@
|
|||||||
/* Telegram device safe-area top (the notch); TG's own nav controls sit between it and
|
/* Telegram device safe-area top (the notch); TG's own nav controls sit between it and
|
||||||
--tg-content-top, so the in-app header aligns to that band, 0 elsewhere. */
|
--tg-content-top, so the in-app header aligns to that band, 0 elsewhere. */
|
||||||
--tg-safe-top: 0px;
|
--tg-safe-top: 0px;
|
||||||
|
/* Telegram device safe-area bottom / sides (home indicator; landscape notch), 0 elsewhere —
|
||||||
|
the screen pads its bottom and left/right edges by these so content clears the cut-outs. */
|
||||||
|
--tg-safe-bottom: 0px;
|
||||||
|
--tg-safe-left: 0px;
|
||||||
|
--tg-safe-right: 0px;
|
||||||
--font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
|
--font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
|
||||||
"Noto Sans", "Liberation Sans", sans-serif;
|
"Noto Sans", "Liberation Sans", sans-serif;
|
||||||
--shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 6px 16px rgba(0, 0, 0, 0.06);
|
--shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 6px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
|||||||
@@ -101,6 +101,11 @@
|
|||||||
bottom input — chat, word-check — stays above an open soft keyboard without the page
|
bottom input — chat, word-check — stays above an open soft keyboard without the page
|
||||||
scrolling; falls back to the full height where the var is unset. */
|
scrolling; falls back to the full height where the var is unset. */
|
||||||
height: var(--vvh, 100%);
|
height: var(--vvh, 100%);
|
||||||
|
/* Clear the device safe-area cut-outs inside Telegram — the home indicator at the bottom and
|
||||||
|
the notch sides in landscape; all 0 elsewhere. The top inset is owned by the header. */
|
||||||
|
padding-bottom: var(--tg-safe-bottom, 0px);
|
||||||
|
padding-left: var(--tg-safe-left, 0px);
|
||||||
|
padding-right: var(--tg-safe-right, 0px);
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
telegramColorScheme,
|
telegramColorScheme,
|
||||||
telegramThemeParams,
|
telegramThemeParams,
|
||||||
telegramContentSafeAreaTop,
|
telegramContentSafeAreaTop,
|
||||||
telegramSafeAreaTop,
|
telegramSafeAreaInset,
|
||||||
telegramDisableVerticalSwipes,
|
telegramDisableVerticalSwipes,
|
||||||
telegramHaptic,
|
telegramHaptic,
|
||||||
telegramLaunch,
|
telegramLaunch,
|
||||||
@@ -528,17 +528,25 @@ function syncTelegramChrome(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* syncTelegramSafeArea mirrors Telegram's content-safe-area top inset (the height its native
|
* syncTelegramSafeArea mirrors Telegram's safe-area insets into CSS vars: the content-safe-area top
|
||||||
* nav overlays the viewport in fullscreen) into the --tg-content-top CSS var and toggles a
|
* (the height Telegram's native nav overlays the viewport in fullscreen) into --tg-content-top
|
||||||
* `tg-fullscreen` class, so the header can drop below the nav and centre the title in its
|
* (which also toggles the `tg-fullscreen` class so the header drops below the nav and centres the
|
||||||
* band. Called on launch and on Telegram's safe-area / fullscreen change events.
|
* title in its band), and the device safe-area insets — notch / status bar (top), home indicator
|
||||||
|
* (bottom) and the landscape notch sides (left / right) — into --tg-safe-top / --tg-safe-bottom /
|
||||||
|
* --tg-safe-left / --tg-safe-right, so the header, rack and screen edges clear the device cut-outs.
|
||||||
|
* Called on launch and on Telegram's safe-area / fullscreen change events.
|
||||||
*/
|
*/
|
||||||
function syncTelegramSafeArea(): void {
|
function syncTelegramSafeArea(): void {
|
||||||
if (typeof document === 'undefined') return;
|
if (typeof document === 'undefined') return;
|
||||||
|
const root = document.documentElement;
|
||||||
const top = telegramContentSafeAreaTop();
|
const top = telegramContentSafeAreaTop();
|
||||||
document.documentElement.style.setProperty('--tg-content-top', `${top}px`);
|
const safe = telegramSafeAreaInset();
|
||||||
document.documentElement.style.setProperty('--tg-safe-top', `${telegramSafeAreaTop()}px`);
|
root.style.setProperty('--tg-content-top', `${top}px`);
|
||||||
document.documentElement.classList.toggle('tg-fullscreen', top > 0);
|
root.style.setProperty('--tg-safe-top', `${safe.top}px`);
|
||||||
|
root.style.setProperty('--tg-safe-bottom', `${safe.bottom}px`);
|
||||||
|
root.style.setProperty('--tg-safe-left', `${safe.left}px`);
|
||||||
|
root.style.setProperty('--tg-safe-right', `${safe.right}px`);
|
||||||
|
root.classList.toggle('tg-fullscreen', top > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
telegramLaunch,
|
telegramLaunch,
|
||||||
telegramOpenExternalLink,
|
telegramOpenExternalLink,
|
||||||
telegramThemeParams,
|
telegramThemeParams,
|
||||||
|
telegramSafeAreaInset,
|
||||||
} from './telegram';
|
} from './telegram';
|
||||||
|
|
||||||
function stubWebApp(initData: string, startParam?: string) {
|
function stubWebApp(initData: string, startParam?: string) {
|
||||||
@@ -51,6 +52,14 @@ describe('telegram launch detection', () => {
|
|||||||
stubWebApp('query_id=abc');
|
stubWebApp('query_id=abc');
|
||||||
expect(telegramThemeParams()?.bg_color).toBe('#101418');
|
expect(telegramThemeParams()?.bg_color).toBe('#101418');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('telegramSafeAreaInset returns zeros outside Telegram and the SDK insets inside', () => {
|
||||||
|
expect(telegramSafeAreaInset()).toEqual({ top: 0, bottom: 0, left: 0, right: 0 });
|
||||||
|
vi.stubGlobal('window', {
|
||||||
|
Telegram: { WebApp: { initData: 'x', safeAreaInset: { top: 59, bottom: 34, left: 0, right: 0 } } },
|
||||||
|
});
|
||||||
|
expect(telegramSafeAreaInset()).toEqual({ top: 59, bottom: 34, left: 0, right: 0 });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('telegramOpenExternalLink', () => {
|
describe('telegramOpenExternalLink', () => {
|
||||||
|
|||||||
@@ -274,13 +274,15 @@ export function telegramContentSafeAreaTop(): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* telegramSafeAreaTop returns the device safe-area top inset (px) — the notch / status bar
|
* telegramSafeAreaInset returns the device safe-area insets (px) — the notch / status bar (top),
|
||||||
* (Bot API 8.0). Telegram's own nav controls sit in the band between it and
|
* the home indicator (bottom) and, in landscape, the notch sides (left / right) — from the SDK's
|
||||||
* telegramContentSafeAreaTop, so aligning our header to that band lines it up with them. 0
|
* safeAreaInset (Bot API 8.0). All 0 outside Telegram or on a client predating it, so callers can
|
||||||
* outside Telegram or on older clients.
|
* pad defensively. Telegram's own nav controls sit in the band between the top inset and
|
||||||
|
* telegramContentSafeAreaTop, so aligning our header to that band lines it up with them.
|
||||||
*/
|
*/
|
||||||
export function telegramSafeAreaTop(): number {
|
export function telegramSafeAreaInset(): { top: number; bottom: number; left: number; right: number } {
|
||||||
return webApp()?.safeAreaInset?.top ?? 0;
|
const i = webApp()?.safeAreaInset;
|
||||||
|
return { top: i?.top ?? 0, bottom: i?.bottom ?? 0, left: i?.left ?? 0, right: i?.right ?? 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user