diff --git a/packages/mini-app-react/src/hooks/useBackButton.ts b/packages/mini-app-react/src/hooks/useBackButton.ts index fd2fed7..6ce38bb 100644 --- a/packages/mini-app-react/src/hooks/useBackButton.ts +++ b/packages/mini-app-react/src/hooks/useBackButton.ts @@ -13,7 +13,7 @@ export function useBackButton({ const { backButton } = useMiniApp() useEffect(() => { backButton.stateStore.setState({ visible }) - }, [backButton, visible]) + }, [backButton.stateStore, visible]) useEffect(() => { if (onClick) { return backButton.onClick(onClick) diff --git a/packages/mini-app-react/src/hooks/useMainButton.ts b/packages/mini-app-react/src/hooks/useMainButton.ts index 6a4d639..027cad3 100644 --- a/packages/mini-app-react/src/hooks/useMainButton.ts +++ b/packages/mini-app-react/src/hooks/useMainButton.ts @@ -3,40 +3,34 @@ import { useMiniApp } from './useMiniApp' export interface UseMainButtonOptions { text?: string - visible?: boolean - active?: boolean loading?: boolean shining?: boolean - bgColor?: string | null - textColor?: string | null + bgColor?: string + textColor?: string onClick?: () => void } export function useMainButton({ - text, - visible, - loading, - active, - shining, + text = '', + loading = false, + shining = false, bgColor, textColor, onClick, }: UseMainButtonOptions): void { const { mainButton } = useMiniApp() useEffect(() => { - mainButton.stateStore.setState(prev => ({ - text: text ?? prev.text, - visible: visible ?? prev.visible, - loading: loading ?? prev.loading, - active: active ?? prev.active, - shining: shining ?? prev.shining, - bgColor: bgColor === undefined ? prev.bgColor : bgColor, - textColor: textColor === undefined ? prev.textColor : textColor, - })) - }, [mainButton, text, visible, loading, active, shining, bgColor, textColor]) + mainButton.setup({ + text, + loading, + shining, + bgColor, + textColor, + }) + }, [mainButton.setup, text, loading, shining, bgColor, textColor]) useEffect(() => { if (onClick) { return mainButton.onClick(onClick) } - }, [mainButton, onClick]) + }, [mainButton.onClick, onClick]) } diff --git a/packages/mini-app-react/src/hooks/useSettingsButton.ts b/packages/mini-app-react/src/hooks/useSettingsButton.ts index 1c66c3c..ca39d06 100644 --- a/packages/mini-app-react/src/hooks/useSettingsButton.ts +++ b/packages/mini-app-react/src/hooks/useSettingsButton.ts @@ -13,7 +13,7 @@ export function useSettingsButton({ const { settingsButton } = useMiniApp() useEffect(() => { settingsButton.stateStore.setState({ visible }) - }, [settingsButton, visible]) + }, [settingsButton.stateStore, visible]) useEffect(() => { if (onClick) { return settingsButton.onClick(onClick) diff --git a/packages/mini-app/src/BackButton.ts b/packages/mini-app/src/BackButton.ts index 4a9b99b..0226f61 100644 --- a/packages/mini-app/src/BackButton.ts +++ b/packages/mini-app/src/BackButton.ts @@ -1,38 +1,26 @@ import type { Bridge } from './Bridge.ts' import type { UnsubscribeFn } from './internal/EventBus.ts' -import type { SessionStorage } from './SessionStorage.ts' -import { Store } from '@tanstack/store' /** * Module for controlling the back button. */ export interface BackButton { - stateStore: Store + setVisible: (visible: boolean) => void onClick: (listener: () => void) => UnsubscribeFn offClick: (listener: any) => void } -export interface State { - visible: boolean -} - export interface InitOptions { - storage: SessionStorage bridge: Bridge } export const init = ({ - storage, bridge, }: InitOptions): BackButton => { - const storedState = storage.storedState('BackButton') - const stateStore = new Store(storedState.load() ?? { visible: false }) - stateStore.subscribe(({ currentVal }) => { - bridge.emit('setup_back_button', { is_visible: currentVal.visible }) - storedState.save(currentVal) - }) return { - stateStore, + setVisible: (visible) => { + bridge.emit('setup_back_button', { is_visible: visible }) + }, onClick: (listener) => { return bridge.on('back_button_pressed', listener) }, diff --git a/packages/mini-app/src/Events.ts b/packages/mini-app/src/Events.ts index 1dd82cf..b5f2927 100644 --- a/packages/mini-app/src/Events.ts +++ b/packages/mini-app/src/Events.ts @@ -265,9 +265,9 @@ export interface OutgoingEventMap { request_theme: void ready: void setup_main_button: { - is_visible?: boolean + is_visible: boolean is_active?: boolean - text: string + text?: string color?: string text_color?: string is_progress_visible?: boolean @@ -286,9 +286,9 @@ export interface OutgoingEventMap { color: string } setup_secondary_button: { - is_visible?: boolean + is_visible: boolean is_active?: boolean - text: string + text?: string color?: string text_color?: string is_progress_visible?: boolean diff --git a/packages/mini-app/src/MainButton.ts b/packages/mini-app/src/MainButton.ts index 2d95baf..11256f0 100644 --- a/packages/mini-app/src/MainButton.ts +++ b/packages/mini-app/src/MainButton.ts @@ -1,79 +1,56 @@ import type { Bridge } from './Bridge.ts' import type { UnsubscribeFn } from './internal/EventBus.ts' -import type { SessionStorage } from './SessionStorage.ts' -import type { Theme } from './Theme.ts' -import { Effect, Store } from '@tanstack/store' import * as Color from './internal/Color.ts' /** * Module for controlling main button. */ export interface MainButton { - stateStore: Store + setup: (state: null | State) => void onClick: (listener: () => void) => UnsubscribeFn offClick: (listener: any) => void } export interface State { text: string - visible: boolean - active: boolean - loading: boolean - shining: boolean - bgColor: string | null - textColor: string | null -} - -const INITIAL_STATE: State = { - text: 'Continue', - visible: false, - active: true, - shining: false, - loading: false, - bgColor: null, - textColor: null, + loading?: boolean + shining?: boolean + bgColor?: string + textColor?: string } export interface InitOptions { - storage: SessionStorage bridge: Bridge - theme: Theme } +const SETUP_EVENT = 'setup_main_button' +const PRESS_EVENT = 'main_button_pressed' + export const init = ({ - storage, bridge, - theme, }: InitOptions): MainButton => { - const storedState = storage.storedState('MainButton') - const stateStore = new Store(storedState.load() ?? INITIAL_STATE) - stateStore.subscribe(({ currentVal }) => { - storedState.save(currentVal) - }) - const effect = new Effect({ - fn: () => { - const state = stateStore.state - const palette = theme.paletteStore.state - bridge.emit('setup_main_button', { - text: state.text, - is_visible: state.visible, - is_active: state.active, - is_progress_visible: state.loading, - has_shine_effect: state.shining, - color: (state.bgColor ? Color.toHex(state.bgColor) : null) ?? palette.button_color ?? '#2481cc', - text_color: (state.textColor ? Color.toHex(state.textColor) : null) ?? palette.button_text_color ?? '#ffffff', - }) - }, - deps: [stateStore, theme.paletteStore], - }) - effect.mount() return { - stateStore, + setup: (state) => { + if (!state?.text) { + bridge.emit(SETUP_EVENT, { is_visible: false }) + } + else { + bridge.emit(SETUP_EVENT, { + is_visible: true, + is_active: true, + text: state.text, + is_progress_visible: state.loading, + has_shine_effect: state.shining, + color: state.bgColor ? Color.toHexUnsafe(state.bgColor) : undefined, + text_color: state.textColor ? Color.toHexUnsafe(state.textColor) : undefined, + }) + } + }, onClick: (listener) => { - return bridge.on('main_button_pressed', listener) + return bridge.on(PRESS_EVENT, listener) }, offClick: (listener) => { - bridge.off('main_button_pressed', listener) + bridge.off(PRESS_EVENT, listener) }, } } diff --git a/packages/mini-app/src/SecondaryButton.ts b/packages/mini-app/src/SecondaryButton.ts index b9a073b..6eddf4e 100644 --- a/packages/mini-app/src/SecondaryButton.ts +++ b/packages/mini-app/src/SecondaryButton.ts @@ -1,82 +1,58 @@ import type { Bridge } from './Bridge.ts' import type { UnsubscribeFn } from './internal/EventBus.ts' -import type { SessionStorage } from './SessionStorage.ts' -import type { Theme } from './Theme.ts' -import { Effect, Store } from '@tanstack/store' import * as Color from './internal/Color.ts' /** * Module for controlling secondary button. */ export interface SecondaryButton { - stateStore: Store + setup: (state: null | State) => void onClick: (listener: () => void) => UnsubscribeFn offClick: (listener: any) => void } export interface State { text: string - visible: boolean - active: boolean - loading: boolean - shining: boolean - position: 'left' | 'right' | 'top' | 'bottom' - bgColor: string | null - textColor: string | null -} - -const INITIAL_STATE: State = { - text: 'Cancel', - visible: false, - active: true, - shining: false, - loading: false, - position: 'left', - bgColor: null, - textColor: null, + loading?: boolean + shining?: boolean + position?: 'left' | 'right' | 'top' | 'bottom' + bgColor?: string + textColor?: string } export interface InitOptions { - storage: SessionStorage bridge: Bridge - theme: Theme } +const PRESS_EVENT = 'secondary_button_pressed' +const SETUP_EVENT = 'setup_secondary_button' + export const init = ({ - storage, bridge, - theme, }: InitOptions): SecondaryButton => { - const storedState = storage.storedState('SecondaryButton') - const stateStore = new Store(storedState.load() ?? INITIAL_STATE) - stateStore.subscribe(({ currentVal }) => { - storedState.save(currentVal) - }) - const effect = new Effect({ - fn: () => { - const state = stateStore.state - const palette = theme.paletteStore.state - bridge.emit('setup_secondary_button', { - text: state.text, - is_visible: state.visible, - is_active: state.active, - is_progress_visible: state.loading, - has_shine_effect: state.shining, - position: state.position, - color: (state.bgColor ? Color.toHex(state.bgColor) : null) ?? palette.bottom_bar_bg_color ?? palette.secondary_bg_color ?? '#ffffff', - text_color: (state.textColor ? Color.toHex(state.textColor) : null) ?? palette.button_color ?? '#2481cc', - }) - }, - deps: [stateStore, theme.paletteStore], - }) - effect.mount() return { - stateStore, + setup: (state) => { + if (!state?.text) { + bridge.emit(SETUP_EVENT, { is_visible: false }) + } + else { + bridge.emit(SETUP_EVENT, { + is_visible: true, + is_active: true, + text: state.text, + is_progress_visible: state.loading, + has_shine_effect: state.shining, + position: state.position, + color: state.bgColor ? Color.toHexUnsafe(state.bgColor) : undefined, + text_color: state.textColor ? Color.toHexUnsafe(state.textColor) : undefined, + }) + } + }, onClick: (listener) => { - return bridge.on('secondary_button_pressed', listener) + return bridge.on(PRESS_EVENT, listener) }, offClick: (listener) => { - bridge.off('secondary_button_pressed', listener) + bridge.off(PRESS_EVENT, listener) }, } } diff --git a/packages/mini-app/src/SettingsButton.ts b/packages/mini-app/src/SettingsButton.ts index cc1ccbd..4eddc63 100644 --- a/packages/mini-app/src/SettingsButton.ts +++ b/packages/mini-app/src/SettingsButton.ts @@ -1,38 +1,26 @@ import type { Bridge } from './Bridge.ts' import type { UnsubscribeFn } from './internal/EventBus.ts' -import type { SessionStorage } from './SessionStorage.ts' -import { Store } from '@tanstack/store' /** - * Module for controlling the back button. + * Module for controlling the settings button. */ export interface SettingsButton { - stateStore: Store + setVisible: (visible: boolean) => void onClick: (listener: () => void) => UnsubscribeFn offClick: (listener: any) => void } -export interface State { - visible: boolean -} - export interface InitOptions { - storage: SessionStorage bridge: Bridge } export const init = ({ - storage, bridge, }: InitOptions): SettingsButton => { - const storedState = storage.storedState('SettingsButton') - const stateStore = new Store(storedState.load() ?? { visible: false }) - stateStore.subscribe(({ currentVal }) => { - bridge.emit('setup_settings_button', { is_visible: currentVal.visible }) - storedState.save(currentVal) - }) return { - stateStore, + setVisible: (visible) => { + bridge.emit('setup_settings_button', { is_visible: visible }) + }, onClick: (listener) => { return bridge.on('settings_button_pressed', listener) }, diff --git a/packages/mini-app/src/internal/Color.ts b/packages/mini-app/src/internal/Color.ts index 8562ef7..f3189bd 100644 --- a/packages/mini-app/src/internal/Color.ts +++ b/packages/mini-app/src/internal/Color.ts @@ -18,6 +18,14 @@ export function toHex(color: string): string | null { return null } +export function toHexUnsafe(color: string): string { + const hex = toHex(color) + if (hex === null) { + throw new Error(`Invalid color format: ${color}`) + } + return color +} + export function isDark(hex: string): boolean { hex = hex.replace(/[\s#]/g, '') if (hex.length === 3) {