From e6ed0b108c8036df75fee6c397f994b4f19db2b8 Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Thu, 30 Jan 2025 19:19:55 +0500 Subject: [PATCH 1/2] fix(sdk): fix problems related to signals' tuples and batched updates --- .../scopes/components/back-button/back-button.ts | 6 +++--- .../sdk/src/scopes/components/biometry/signals.ts | 2 +- .../closing-behavior/closing-behavior.ts | 4 ++-- .../src/scopes/components/init-data/init-data.ts | 2 +- .../src/scopes/components/main-button/methods.ts | 2 +- .../sdk/src/scopes/components/mini-app/signals.ts | 14 +++++++------- .../scopes/components/secondary-button/methods.ts | 10 +++------- .../components/settings-button/settings-button.ts | 6 +++--- .../components/swipe-behavior/swipe-behavior.ts | 6 +++--- .../src/scopes/components/theme-params/signals.ts | 4 ++-- .../sdk/src/scopes/components/viewport/css-vars.ts | 6 +++--- .../src/scopes/components/viewport/fullscreen.ts | 4 ++-- .../sdk/src/scopes/components/viewport/signals.ts | 4 ++-- packages/sdk/src/scopes/defineMountFn.ts | 2 +- packages/sdk/src/scopes/defineNonConcurrentFn.ts | 2 +- 15 files changed, 35 insertions(+), 39 deletions(-) diff --git a/packages/sdk/src/scopes/components/back-button/back-button.ts b/packages/sdk/src/scopes/components/back-button/back-button.ts index 8f21e061c..ef5d867fa 100644 --- a/packages/sdk/src/scopes/components/back-button/back-button.ts +++ b/packages/sdk/src/scopes/components/back-button/back-button.ts @@ -29,7 +29,7 @@ export const [_isMounted, isMounted] = createSignalsTuple(false); */ export const isSupported = createIsSupported(SETUP_METHOD_NAME); -const wrapComplete = createWrapComplete(COMPONENT_NAME, isMounted, SETUP_METHOD_NAME); +const wrapComplete = createWrapComplete(COMPONENT_NAME, _isMounted, SETUP_METHOD_NAME); const wrapSupported = createWrapSupported(COMPONENT_NAME, SETUP_METHOD_NAME); /** @@ -62,14 +62,14 @@ export const hide = wrapComplete('hide', (): void => { * } */ export const mount = wrapSupported('mount', (): void => { - if (!isMounted()) { + if (!_isMounted()) { setVisibility(isPageReload() && getStorageValue(COMPONENT_NAME) || false); _isMounted.set(true); } }); function setVisibility(value: boolean): void { - if (value !== isVisible()) { + if (value !== _isVisible()) { postEvent(SETUP_METHOD_NAME, { is_visible: value }); setStorageValue(COMPONENT_NAME, value); _isVisible.set(value); diff --git a/packages/sdk/src/scopes/components/biometry/signals.ts b/packages/sdk/src/scopes/components/biometry/signals.ts index f36e19cc8..364bc77c8 100644 --- a/packages/sdk/src/scopes/components/biometry/signals.ts +++ b/packages/sdk/src/scopes/components/biometry/signals.ts @@ -17,4 +17,4 @@ export const [_state, state] = createSignalsTuple({ /** * Signal indicating biometry is available. */ -export const isAvailable = createComputed(() => state().available); +export const isAvailable = createComputed(() => _state().available); diff --git a/packages/sdk/src/scopes/components/closing-behavior/closing-behavior.ts b/packages/sdk/src/scopes/components/closing-behavior/closing-behavior.ts index b0650746a..79af5f0ac 100644 --- a/packages/sdk/src/scopes/components/closing-behavior/closing-behavior.ts +++ b/packages/sdk/src/scopes/components/closing-behavior/closing-behavior.ts @@ -62,7 +62,7 @@ export const enableConfirmation = wrapMounted('enableConfirmation', (): void => * } */ export const mount = wrapBasic('mount', (): void => { - if (!isMounted()) { + if (!_isMounted()) { setClosingConfirmation( isPageReload() && getStorageValue(COMPONENT_NAME) || false, ); @@ -71,7 +71,7 @@ export const mount = wrapBasic('mount', (): void => { }); function setClosingConfirmation(value: boolean): void { - if (value !== isConfirmationEnabled()) { + if (value !== _isConfirmationEnabled()) { postEvent('web_app_setup_closing_behavior', { need_confirmation: value }); setStorageValue(COMPONENT_NAME, value); _isConfirmationEnabled.set(value); diff --git a/packages/sdk/src/scopes/components/init-data/init-data.ts b/packages/sdk/src/scopes/components/init-data/init-data.ts index ad891001b..2de1d26ca 100644 --- a/packages/sdk/src/scopes/components/init-data/init-data.ts +++ b/packages/sdk/src/scopes/components/init-data/init-data.ts @@ -12,7 +12,7 @@ export const [_state, state] = function fromState(key: K): Computed { return createComputed(() => { - const s = state(); + const s = _state(); return s ? s[key] : undefined; }); } diff --git a/packages/sdk/src/scopes/components/main-button/methods.ts b/packages/sdk/src/scopes/components/main-button/methods.ts index fbdbf9ee3..a73c7c07e 100644 --- a/packages/sdk/src/scopes/components/main-button/methods.ts +++ b/packages/sdk/src/scopes/components/main-button/methods.ts @@ -33,7 +33,7 @@ const wrapMounted = createWrapMounted(COMPONENT_NAME, isMounted); * } */ export const mount = wrapBasic('mount', (): void => { - if (!isMounted()) { + if (!_isMounted()) { const prev = isPageReload() && getStorageValue(COMPONENT_NAME); prev && internalState.set(prev); _isMounted.set(true); diff --git a/packages/sdk/src/scopes/components/mini-app/signals.ts b/packages/sdk/src/scopes/components/mini-app/signals.ts index ac39beb6d..b0ff91463 100644 --- a/packages/sdk/src/scopes/components/mini-app/signals.ts +++ b/packages/sdk/src/scopes/components/mini-app/signals.ts @@ -36,7 +36,7 @@ export const [_backgroundColor, backgroundColor] = * This value requires the Theme Params component to be mounted to extract a valid RGB value * of the color key. */ -export const backgroundColorRGB = rgbBasedOn(backgroundColor); +export const backgroundColorRGB = rgbBasedOn(_backgroundColor); /** @@ -52,7 +52,7 @@ export const [_bottomBarColor, bottomBarColor] = * of the color key. */ export const bottomBarColorRGB = createComputed(() => { - const color = bottomBarColor(); + const color = _bottomBarColor(); return isRGB(color) ? color : color === 'bottom_bar_bg_color' @@ -75,7 +75,7 @@ export const [_headerColor, headerColor] = createSignalsTuple('bg_c * This value requires the Theme Params component to be mounted to extract a valid RGB value * of the color key. */ -export const headerColorRGB = rgbBasedOn(headerColor); +export const headerColorRGB = rgbBasedOn(_headerColor); /** * True if CSS variables are currently bound. @@ -99,8 +99,8 @@ export const [_isActive, isActive] = createSignalsTuple(true); * Complete component state. */ export const state = createComputed(() => ({ - backgroundColor: backgroundColor(), - bottomBarColor: bottomBarColor(), - headerColor: headerColor(), - isActive: isActive(), + backgroundColor: _backgroundColor(), + bottomBarColor: _bottomBarColor(), + headerColor: _headerColor(), + isActive: _isActive(), })); diff --git a/packages/sdk/src/scopes/components/secondary-button/methods.ts b/packages/sdk/src/scopes/components/secondary-button/methods.ts index 4a0d956c1..899196b14 100644 --- a/packages/sdk/src/scopes/components/secondary-button/methods.ts +++ b/packages/sdk/src/scopes/components/secondary-button/methods.ts @@ -13,6 +13,7 @@ import { createWrapSupported } from '@/scopes/wrappers/createWrapSupported.js'; import { internalState, isMounted, _isMounted, state } from './signals.js'; import type { State } from './types.js'; +import { removeUndefined } from '@/utils/removeUndefined.js'; type StorageValue = State; @@ -39,7 +40,7 @@ export const isSupported = createIsSupported(SETUP_METHOD_NAME); * } */ export const mount = wrapSupported('mount', (): void => { - if (!isMounted()) { + if (!_isMounted()) { const prev = isPageReload() && getStorageValue(COMPONENT_NAME); prev && internalState.set(prev); _isMounted.set(true); @@ -108,12 +109,7 @@ export const offClick = wrapSupported( export const setParams = wrapComplete( 'setParams', (updates: Partial): void => { - internalState.set({ - ...internalState(), - ...Object.fromEntries( - Object.entries(updates).filter(([, v]) => v !== undefined), - ), - }); + internalState.set({ ...internalState(), ...removeUndefined(updates) }); setStorageValue(COMPONENT_NAME, internalState()); // We should not commit changes until the payload is correct. Some version of Telegram will diff --git a/packages/sdk/src/scopes/components/settings-button/settings-button.ts b/packages/sdk/src/scopes/components/settings-button/settings-button.ts index ef3d8eea1..b5379e0cd 100644 --- a/packages/sdk/src/scopes/components/settings-button/settings-button.ts +++ b/packages/sdk/src/scopes/components/settings-button/settings-button.ts @@ -32,7 +32,7 @@ export const [_isMounted, isMounted] = createSignalsTuple(false); export const isSupported = createIsSupported(SETUP_METHOD_NAME); const wrapSupported = createWrapSupported(COMPONENT_NAME, SETUP_METHOD_NAME); -const wrapComplete = createWrapComplete(COMPONENT_NAME, isMounted, SETUP_METHOD_NAME); +const wrapComplete = createWrapComplete(COMPONENT_NAME, _isMounted, SETUP_METHOD_NAME); /** * Hides the Settings Button. @@ -62,14 +62,14 @@ export const hide = wrapComplete('hide', (): void => { * } */ export const mount = wrapSupported('mount', (): void => { - if (!isMounted()) { + if (!_isMounted()) { setVisibility(isPageReload() && getStorageValue(COMPONENT_NAME) || false); _isMounted.set(true); } }); function setVisibility(value: boolean): void { - if (value !== isVisible()) { + if (value !== _isVisible()) { postEvent(SETUP_METHOD_NAME, { is_visible: value }); setStorageValue(COMPONENT_NAME, value); _isVisible.set(value); diff --git a/packages/sdk/src/scopes/components/swipe-behavior/swipe-behavior.ts b/packages/sdk/src/scopes/components/swipe-behavior/swipe-behavior.ts index 26df2a0ce..d17915f79 100644 --- a/packages/sdk/src/scopes/components/swipe-behavior/swipe-behavior.ts +++ b/packages/sdk/src/scopes/components/swipe-behavior/swipe-behavior.ts @@ -30,7 +30,7 @@ export const isSupported = createIsSupported(SETUP_METHOD_NAME); export const [_isVerticalEnabled, isVerticalEnabled] = createSignalsTuple(true); const wrapSupported = createWrapSupported(COMPONENT_NAME, SETUP_METHOD_NAME); -const wrapComplete = createWrapComplete(COMPONENT_NAME, isMounted, SETUP_METHOD_NAME); +const wrapComplete = createWrapComplete(COMPONENT_NAME, _isMounted, SETUP_METHOD_NAME); /** * Disables vertical swipes. @@ -76,7 +76,7 @@ export const enableVertical = wrapComplete('enableVertical', (): void => { * } */ export const mount = wrapSupported('mount', (): void => { - if (!isMounted()) { + if (!_isMounted()) { setVerticalEnabled( isPageReload() && getStorageValue(COMPONENT_NAME) || false, true, @@ -86,7 +86,7 @@ export const mount = wrapSupported('mount', (): void => { }); function setVerticalEnabled(value: boolean, force?: boolean): void { - if (value !== isVerticalEnabled() || force) { + if (value !== _isVerticalEnabled() || force) { postEvent(SETUP_METHOD_NAME, { allow_vertical_swipe: value }); setStorageValue(COMPONENT_NAME, value); _isVerticalEnabled.set(value); diff --git a/packages/sdk/src/scopes/components/theme-params/signals.ts b/packages/sdk/src/scopes/components/theme-params/signals.ts index bfef3a3bc..0b0d23ed5 100644 --- a/packages/sdk/src/scopes/components/theme-params/signals.ts +++ b/packages/sdk/src/scopes/components/theme-params/signals.ts @@ -15,7 +15,7 @@ export const [_isCssVarsBound, isCssVarsBound] = createSignalsTuple(false); export const [_state, state] = createSignalsTuple({}); function fromState(key: K): Computed { - return createComputed(() => state()[key]); + return createComputed(() => _state()[key]); } /** @@ -48,7 +48,7 @@ export const hintColor = fromState('hintColor'); * This value is calculated based on the current theme's background color. */ export const isDark = createComputed(() => { - const { bgColor } = state(); + const { bgColor } = _state(); return !bgColor || isColorDark(bgColor); }); diff --git a/packages/sdk/src/scopes/components/viewport/css-vars.ts b/packages/sdk/src/scopes/components/viewport/css-vars.ts index 876a32404..7026356af 100644 --- a/packages/sdk/src/scopes/components/viewport/css-vars.ts +++ b/packages/sdk/src/scopes/components/viewport/css-vars.ts @@ -5,7 +5,7 @@ import { CSSVarsBoundError } from '@/errors.js'; import { createSignalsTuple } from '@/signals-registry.js'; import { createWrapMounted } from '@/scopes/wrappers/createWrapMounted.js'; import { COMPONENT_NAME } from '@/scopes/components/viewport/const.js'; -import { isMounted } from '@/scopes/components/viewport/mounting.js'; +import { _isMounted } from '@/scopes/components/viewport/mounting.js'; import { safeAreaInsetBottom, @@ -22,7 +22,7 @@ import { } from './signals.js'; import type { GetCSSVarNameFn } from './types.js'; -const wrapMounted = createWrapMounted(COMPONENT_NAME, isMounted); +const wrapMounted = createWrapMounted(COMPONENT_NAME, _isMounted); /** * True if CSS variables are currently bound. @@ -69,7 +69,7 @@ export const [_isCssVarsBound, isCssVarsBound] = createSignalsTuple(false); export const bindCssVars = wrapMounted( 'bindCssVars', (getCSSVarName?: GetCSSVarNameFn): VoidFunction => { - if (isCssVarsBound()) { + if (_isCssVarsBound()) { throw new CSSVarsBoundError(); } diff --git a/packages/sdk/src/scopes/components/viewport/fullscreen.ts b/packages/sdk/src/scopes/components/viewport/fullscreen.ts index 21e4d25a5..5095180c3 100644 --- a/packages/sdk/src/scopes/components/viewport/fullscreen.ts +++ b/packages/sdk/src/scopes/components/viewport/fullscreen.ts @@ -7,11 +7,11 @@ import { defineNonConcurrentFn } from '@/scopes/defineNonConcurrentFn.js'; import { COMPONENT_NAME, FS_CHANGED_EVENT } from './const.js'; import { setState, signalFromState } from './signals.js'; -import { isMounted } from './mounting.js'; +import { _isMounted } from './mounting.js'; import { createSignalsTuple } from '@/signals-registry.js'; const REQUEST_METHOD_NAME = 'web_app_request_fullscreen'; -const wrapComplete = createWrapComplete(COMPONENT_NAME, isMounted, REQUEST_METHOD_NAME); +const wrapComplete = createWrapComplete(COMPONENT_NAME, _isMounted, REQUEST_METHOD_NAME); /** * Signal indicating if the viewport is currently in fullscreen mode. diff --git a/packages/sdk/src/scopes/components/viewport/signals.ts b/packages/sdk/src/scopes/components/viewport/signals.ts index abf1308c5..6c14c5f13 100644 --- a/packages/sdk/src/scopes/components/viewport/signals.ts +++ b/packages/sdk/src/scopes/components/viewport/signals.ts @@ -94,7 +94,7 @@ export function setState(s: Partial): void { const { height, stableHeight, width } = s; _state.set({ - ...state(), + ..._state(), ...removeUndefined({ ...s, height: height ? nonNegative(height) : undefined, @@ -102,7 +102,7 @@ export function setState(s: Partial): void { stableHeight: stableHeight ? nonNegative(stableHeight) : undefined, }), }); - setStorageValue(COMPONENT_NAME, state()); + setStorageValue(COMPONENT_NAME, _state()); } /** diff --git a/packages/sdk/src/scopes/defineMountFn.ts b/packages/sdk/src/scopes/defineMountFn.ts index 54caf9345..5c368a75c 100644 --- a/packages/sdk/src/scopes/defineMountFn.ts +++ b/packages/sdk/src/scopes/defineMountFn.ts @@ -29,7 +29,7 @@ export function defineMountFn AbortablePromise const [_isMounted, isMounted] = createSignalsTuple(false); return [ - (...args) => isMounted() + (...args) => _isMounted() ? AbortablePromise.resolve() : fn(...args).then(data => { batch(() => { diff --git a/packages/sdk/src/scopes/defineNonConcurrentFn.ts b/packages/sdk/src/scopes/defineNonConcurrentFn.ts index e2cc2f48d..50a2a625e 100644 --- a/packages/sdk/src/scopes/defineNonConcurrentFn.ts +++ b/packages/sdk/src/scopes/defineNonConcurrentFn.ts @@ -68,7 +68,7 @@ export function defineNonConcurrentFn AbortableProm }); }); }, fn), - [_promise, promise, createComputed(() => !!promise())], + [_promise, promise, createComputed(() => !!_promise())], [_error, error], ]; } \ No newline at end of file From 7825425df8545970bf576e7b41eaa8505234d93f Mon Sep 17 00:00:00 2001 From: Vladislav Kibenko Date: Thu, 30 Jan 2025 19:25:22 +0500 Subject: [PATCH 2/2] docs(changeset): Fix problems related to signals' tuples and batched updates --- .changeset/moody-jokes-rule.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/moody-jokes-rule.md diff --git a/.changeset/moody-jokes-rule.md b/.changeset/moody-jokes-rule.md new file mode 100644 index 000000000..8bc7c2b55 --- /dev/null +++ b/.changeset/moody-jokes-rule.md @@ -0,0 +1,5 @@ +--- +"@telegram-apps/sdk": patch +--- + +Fix problems related to signals' tuples and batched updates