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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/happy-boxes-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tma.js/bridge": minor
"@tma.js/sdk": minor
---

added support for icon_custom_emoji_id in bottom buttons
19 changes: 19 additions & 0 deletions apps/docs/platform/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,7 @@ Updates the [Main Button](main-button.md) settings.
| color | `string` | _Optional_. The button background color in `#RRGGBB` format. |
| text_color | `string` | _Optional_. The button text color in `#RRGGBB` format. |
| has_shine_effect | `boolean` | _Optional_. Should the button have a shining effect. | `v7.8` |
| icon_custom_emoji_id | `string` | _Optional_. The ID of custom emoji icon displayed alongside button text. | `v9.5` |

### `web_app_setup_settings_button`

Expand Down Expand Up @@ -750,6 +751,7 @@ The method that updates the Secondary Button settings.
<th>Field</th>
<th>Type</th>
<th>Description</th>
<th>Available since</th>
</tr>

</thead>
Expand All @@ -761,6 +763,7 @@ The method that updates the Secondary Button settings.
<code>boolean</code>
</td>
<td><i>Optional</i>. Should the button be displayed.</td>
<td><code>v7.10</code></td>
</tr>

<tr>
Expand All @@ -769,6 +772,7 @@ The method that updates the Secondary Button settings.
<code>boolean</code>
</td>
<td><i>Optional</i>. Should the button be enabled.</td>
<td><code>v7.10</code></td>
</tr>

<tr>
Expand All @@ -780,6 +784,7 @@ The method that updates the Secondary Button settings.
<i>Optional</i>. Should loader inside the button be displayed. Use this property in case,
some operation takes time. This loader will make user notified about it.
</td>
<td><code>v7.10</code></td>
</tr>

<tr>
Expand All @@ -788,6 +793,7 @@ The method that updates the Secondary Button settings.
<code>string</code>
</td>
<td><i>Optional</i>. The button background color in <code>#RRGGBB</code> format.</td>
<td><code>v7.10</code></td>
</tr>

<tr>
Expand All @@ -796,6 +802,7 @@ The method that updates the Secondary Button settings.
<code>string</code>
</td>
<td><i>Optional</i>. The button text color in <code>#RRGGBB</code> format.</td>
<td><code>v7.10</code></td>
</tr>

<tr>
Expand All @@ -804,6 +811,7 @@ The method that updates the Secondary Button settings.
<code>boolean</code>
</td>
<td><i>Optional</i>. Should the button have a shining effect.</td>
<td><code>v7.10</code></td>
</tr>

<tr>
Expand All @@ -829,7 +837,18 @@ The method that updates the Secondary Button settings.
</li>
</ul>
</td>
<td><code>v7.10</code></td>
</tr>

<tr>
<td>icon_custom_emoji_id</td>
<td>
<code>string</code>
</td>
<td><i>Optional</i>. The ID of custom emoji icon displayed alongside button text.</td>
<td><code>v9.5</code></td>
</tr>

</tbody>
</table>

Expand Down
7 changes: 7 additions & 0 deletions packages/bridge/src/methods/createPostEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ export function createPostEvent(
) {
return onUnsupported({ version, method, param: 'color' });
}
if (
(method === 'web_app_setup_main_button' || method === 'web_app_setup_secondary_button')
&& is(looseObject({ icon_custom_emoji_id: any() }), params)
&& !supports(method, 'icon_custom_emoji_id', version)
) {
return onUnsupported({ version, method, param: 'icon_custom_emoji_id' });
}

return postEvent(method, params);
}) as PostEventFn;
Expand Down
12 changes: 12 additions & 0 deletions packages/bridge/src/methods/getReleaseVersion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@ describe.each<[
'web_app_secure_storage_save_key',
]],
['9.1', ['web_app_hide_keyboard']],
['9.5', [
{
title: 'web_app_setup_main_button.icon_custom_emoji_id',
method: 'web_app_setup_main_button',
param: 'icon_custom_emoji_id',
},
{
title: 'web_app_setup_secondary_button.icon_custom_emoji_id',
method: 'web_app_setup_secondary_button',
param: 'icon_custom_emoji_id',
},
]],
])('%s', (version, methods) => {
const methodsOnly = methods.filter((m): m is MethodName => {
return typeof m === 'string';
Expand Down
4 changes: 4 additions & 0 deletions packages/bridge/src/methods/getReleaseVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ const releases = {
'web_app_secure_storage_save_key',
],
9.1: ['web_app_hide_keyboard'],
9.5: [
{ method: 'web_app_setup_main_button', param: 'icon_custom_emoji_id' },
{ method: 'web_app_setup_secondary_button', param: 'icon_custom_emoji_id' },
],
};

/**
Expand Down
9 changes: 7 additions & 2 deletions packages/bridge/src/methods/types/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ interface ButtonParams {
* @since 7.10
*/
has_shine_effect?: boolean;
/**
* The ID of custom emoji icon displayed alongside button text.
* @since 9.5
*/
icon_custom_emoji_id?: string;
/**
* Should the button be displayed.
*/
Expand Down Expand Up @@ -562,7 +567,7 @@ export interface Methods {
* Updates the Main Button settings.
* @see https://docs.telegram-mini-apps.com/platform/methods#web-app-setup-main-button
*/
web_app_setup_main_button: CreateMethodParams<ButtonParams, 'has_shine_effect'>;
web_app_setup_main_button: CreateMethodParams<ButtonParams, 'has_shine_effect' | 'icon_custom_emoji_id'>;

/**
* Updates the secondary button settings.
Expand All @@ -581,7 +586,7 @@ export interface Methods {
* - `bottom`, displayed below the main button.
*/
position?: SecondaryButtonPosition;
}>;
}, 'icon_custom_emoji_id'>;

/**
* Updates the current state of the Settings Button.
Expand Down
10 changes: 10 additions & 0 deletions packages/sdk/src/features/MainButton/MainButton.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ describe.each([
['hideLoader', (component: MainButton) => component.hideLoader(), true],
['setText', (component: MainButton) => component.setText('a'), true],
['setTextColor', (component: MainButton) => component.setTextColor('#aaa'), true],
['setIconCustomEmojiId', (component: MainButton) => component.setIconCustomEmojiId('123'), true],
['setBgColor', (component: MainButton) => component.setBgColor('#ddd'), true],
] as const)('%s', (method, tryCall, requireMount) => {
describe('safety', () => {
Expand Down Expand Up @@ -182,6 +183,13 @@ describe.each([
usedValue: '#cba',
use: (component: MainButton) => component.setTextColor('#cba'),
},
{
method: 'setIconCustomEmojiId',
property: 'iconCustomEmojiId',
payloadProperty: 'icon_custom_emoji_id',
usedValue: '123',
use: (component: MainButton) => component.setIconCustomEmojiId('123'),
},
// {
// method: 'setText',
// property: 'text',
Expand Down Expand Up @@ -265,6 +273,7 @@ describe('mount', () => {
isLoaderVisible: false,
text: 'Text',
textColor: '#112',
iconCustomEmojiId: '123',
} as const));
const component = instantiate({ storage: { get, set: vi.fn() } });
component.mount();
Expand All @@ -291,6 +300,7 @@ describe('mount', () => {
isLoaderVisible: false,
text: 'Text',
textColor: '#112',
iconCustomEmojiId: '123',
} as const));
const component2 = instantiate({
storage: { get: get2, set: vi.fn() },
Expand Down
37 changes: 34 additions & 3 deletions packages/sdk/src/features/MainButton/MainButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface MainButtonState {
isLoaderVisible: boolean;
text: string;
textColor?: RGB;
iconCustomEmojiId?: string;
}

export interface MainButtonOptions extends Omit<
Expand All @@ -30,6 +31,7 @@ export interface MainButtonOptions extends Omit<
defaults: {
bgColor: MaybeAccessor<RGB>;
textColor: MaybeAccessor<RGB>;
iconCustomEmojiId: MaybeAccessor<string>;
};
}

Expand All @@ -44,6 +46,7 @@ export class MainButton {
isLoaderVisible: false,
isVisible: false,
text: 'Continue',
iconCustomEmojiId: '',
},
method: 'web_app_setup_main_button',
payload: state => ({
Expand All @@ -54,23 +57,33 @@ export class MainButton {
text: state.text,
color: state.bgColor,
text_color: state.textColor,
icon_custom_emoji_id: state.iconCustomEmojiId,
}),
});

const withDefault = (
const withDefaultColor = (
field: 'bgColor' | 'textColor',
getDefault: MaybeAccessor<RGB>,
) => {
const fromState = button.stateGetter(field);
return computed(() => fromState() || access(getDefault));
};

this.bgColor = withDefault('bgColor', defaults.bgColor);
this.textColor = withDefault('textColor', defaults.textColor);
const withDefault = (
field: 'iconCustomEmojiId',
getDefault: MaybeAccessor<string>,
) => {
const fromState = button.stateGetter(field);
return computed(() => fromState() || access(getDefault));
};

this.bgColor = withDefaultColor('bgColor', defaults.bgColor);
this.textColor = withDefaultColor('textColor', defaults.textColor);
this.hasShineEffect = button.stateGetter('hasShineEffect');
this.isEnabled = button.stateGetter('isEnabled');
this.isLoaderVisible = button.stateGetter('isLoaderVisible');
this.text = button.stateGetter('text');
this.iconCustomEmojiId = withDefault('iconCustomEmojiId', '');
this.isVisible = button.stateGetter('isVisible');
this.isMounted = button.isMounted;
this.state = button.state;
Expand All @@ -91,6 +104,7 @@ export class MainButton {
] = button.stateBoolSetters('isLoaderVisible');

[this.setText, this.setTextFp] = button.stateSetters('text');
[this.setIconCustomEmojiId, this.setIconCustomEmojiIdFp] = button.stateSetters('iconCustomEmojiId');
[[this.hide, this.hideFp], [this.show, this.showFp]] = button.stateBoolSetters('isVisible');
this.setParams = button.setState;
this.setParamsFp = button.setStateFp;
Expand Down Expand Up @@ -152,6 +166,12 @@ export class MainButton {
* params colors.
*/
readonly textColor: Computed<RGB>;

/**
* The ID of custom emoji icon displayed alongside button text.
* @since Mini Apps v9.5
*/
readonly iconCustomEmojiId: Computed<string>;
//#endregion

//#region Methods.
Expand Down Expand Up @@ -245,6 +265,17 @@ export class MainButton {
*/
readonly setText: WithChecks<(value: string) => void, false>;

/**
* Updates the button custom emoji ID.
* @since Mini Apps v9.5
*/
readonly setIconCustomEmojiIdFp: WithChecksFp<(value: string) => MainButtonEither, false>;

/**
* @see setIconCustomEmojiIdFp
*/
readonly setIconCustomEmojiId: WithChecks<(value: string) => void, false>;

/**
* Shows the button loader.
*/
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/features/MainButton/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export const mainButton = /* @__PURE__*/ new MainButton(
bottomButtonOptions('mainButton', 'main_button_pressed', {
bgColor: computed(() => themeParams.buttonColor() || '#2481cc'),
textColor: computed(() => themeParams.buttonTextColor() || '#ffffff'),
iconCustomEmojiId: computed(() => ''),
}),
);
10 changes: 10 additions & 0 deletions packages/sdk/src/features/SecondaryButton/SecondaryButton.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe.each([
['hideLoader', (component: SecondaryButton) => component.hideLoader(), true],
['setText', (component: SecondaryButton) => component.setText('a'), true],
['setTextColor', (component: SecondaryButton) => component.setTextColor('#aaa'), true],
['setIconCustomEmojiId', (component: SecondaryButton) => component.setIconCustomEmojiId('123'), true],
['setBgColor', (component: SecondaryButton) => component.setBgColor('#ddd'), true],
['setPosition', (component: SecondaryButton) => component.setPosition('right'), true],
] as const)('%s', (method, tryCall, requireMount) => {
Expand Down Expand Up @@ -196,6 +197,13 @@ describe.each([
usedValue: 'Some text',
use: (component: SecondaryButton) => component.setText('Some text'),
},
{
method: 'setIconCustomEmojiId',
property: 'iconCustomEmojiId',
payloadProperty: 'icon_custom_emoji_id',
usedValue: '123',
use: (component: SecondaryButton) => component.setIconCustomEmojiId('123'),
},
{
method: 'setPosition',
property: 'position',
Expand Down Expand Up @@ -279,6 +287,7 @@ describe('mount', () => {
isLoaderVisible: false,
text: 'Text',
textColor: '#112',
iconCustomEmojiId: '123',
} as const));
const component = instantiate({ storage: { get, set: vi.fn() } });
component.mount();
Expand All @@ -305,6 +314,7 @@ describe('mount', () => {
isLoaderVisible: false,
text: 'Text',
textColor: '#112',
iconCustomEmojiId: '123',
} as const));
const component2 = instantiate({
storage: { get: get2, set: vi.fn() },
Expand Down
Loading
Loading