nuxt-telegram-mini-app 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -107,50 +107,202 @@
107
107
  <div class="p-4 space-y-4">
108
108
  <div class="space-y-2">
109
109
  <h3 class="text-sm font-medium text-subtitle">Form Elements</h3>
110
- <div class="space-y-3">
111
- <input
112
- v-model="textInput"
113
- type="text"
114
- placeholder="Enter text here..."
115
- class="w-full px-3 py-2 bg-secondaryBg border border-sectionSeparator rounded-lg text-text placeholder-hint"
116
- />
117
- <textarea
118
- v-model="textareaInput"
119
- rows="3"
120
- placeholder="Multi-line input..."
121
- class="w-full px-3 py-2 bg-secondaryBg border border-sectionSeparator rounded-lg text-text placeholder-hint resize-none"
122
- />
110
+ <div class="space-y-5">
111
+ <label class="block rounded-2xl bg-secondaryBg px-4 py-3">
112
+ <span class="mb-2 block text-xs text-hint">Display name</span>
113
+ <input
114
+ v-model="formName"
115
+ type="text"
116
+ placeholder="Your name"
117
+ class="min-h-10 w-full bg-transparent text-base text-text placeholder-hint outline-none focus-visible:ring-2 focus-visible:ring-link/40"
118
+ />
119
+ </label>
120
+
121
+ <label class="block rounded-2xl bg-secondaryBg px-4 py-3">
122
+ <span class="mb-2 block text-xs text-hint">Notes</span>
123
+ <textarea
124
+ v-model="formNotes"
125
+ rows="3"
126
+ placeholder="Add a short note"
127
+ class="min-h-20 w-full resize-none bg-transparent text-base text-text placeholder-hint outline-none focus-visible:ring-2 focus-visible:ring-link/40"
128
+ />
129
+ </label>
130
+
131
+ <div class="space-y-2">
132
+ <p class="px-4 text-xs uppercase tracking-normal text-hint">Notifications</p>
133
+ <div class="overflow-hidden rounded-2xl bg-secondaryBg">
134
+ <label class="flex min-h-16 items-center justify-between gap-3 px-4 py-3 active:bg-bg/60">
135
+ <span class="min-w-0 text-base font-medium text-text">Notifications</span>
136
+ <input
137
+ v-model="notificationsEnabled"
138
+ type="checkbox"
139
+ class="peer sr-only"
140
+ @change="onFormSelection"
141
+ />
142
+ <span
143
+ class="relative h-8 w-12 shrink-0 rounded-full transition-colors peer-focus-visible:ring-2 peer-focus-visible:ring-link/40"
144
+ :class="notificationsEnabled ? 'bg-primary' : 'bg-bg'"
145
+ >
146
+ <span
147
+ class="absolute left-1 top-1 h-6 w-6 rounded-full bg-primaryFg shadow-sm transition-transform"
148
+ :class="notificationsEnabled ? 'translate-x-4' : 'translate-x-0'"
149
+ />
150
+ </span>
151
+ </label>
152
+
153
+ <label class="flex min-h-16 items-center justify-between gap-3 border-t border-sectionSeparator px-4 py-3 active:bg-bg/60">
154
+ <span class="min-w-0">
155
+ <span class="block text-base font-medium text-text">Updates</span>
156
+ <span class="block text-sm text-hint">Include product news in this demo profile</span>
157
+ </span>
158
+ <input
159
+ v-model="termsAccepted"
160
+ type="checkbox"
161
+ class="peer sr-only"
162
+ @change="onFormSelection"
163
+ />
164
+ <span
165
+ class="flex h-7 w-7 shrink-0 items-center justify-center rounded-full border transition-colors peer-focus-visible:ring-2 peer-focus-visible:ring-link/40"
166
+ :class="termsAccepted ? 'border-primary bg-primary text-primaryFg' : 'border-hint/40 text-transparent'"
167
+ >
168
+ <Icon name="i-heroicons-check-20-solid" class="h-5 w-5" />
169
+ </span>
170
+ </label>
171
+ </div>
172
+ </div>
173
+
174
+ <div class="space-y-2">
175
+ <p class="px-4 text-xs uppercase tracking-normal text-hint">Visibility</p>
176
+ <div class="overflow-hidden rounded-2xl bg-secondaryBg">
177
+ <label
178
+ v-for="option in visibilityOptions"
179
+ :key="option.value"
180
+ class="flex min-h-14 items-center gap-4 border-b border-sectionSeparator px-4 py-3 last:border-b-0 active:bg-bg/60"
181
+ >
182
+ <input
183
+ v-model="selectedVisibility"
184
+ type="radio"
185
+ name="visibility"
186
+ :value="option.value"
187
+ class="peer sr-only"
188
+ @change="onFormSelection"
189
+ />
190
+ <span
191
+ class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full border-2 transition-colors peer-focus-visible:ring-2 peer-focus-visible:ring-link/40"
192
+ :class="selectedVisibility === option.value ? 'border-primary' : 'border-hint/40'"
193
+ >
194
+ <span
195
+ class="h-3.5 w-3.5 rounded-full transition-colors"
196
+ :class="selectedVisibility === option.value ? 'bg-primary' : 'bg-transparent'"
197
+ />
198
+ </span>
199
+ <span class="text-base font-medium text-text">{{ option.label }}</span>
200
+ </label>
201
+ </div>
202
+ </div>
203
+
204
+ <div class="space-y-2">
205
+ <p class="px-4 text-xs uppercase tracking-normal text-hint">Mode</p>
206
+ <div class="grid grid-cols-3 rounded-2xl bg-secondaryBg p-1">
207
+ <button
208
+ v-for="option in modeOptions"
209
+ :key="option.value"
210
+ type="button"
211
+ class="min-h-11 rounded-xl px-2 text-base font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-link/40"
212
+ :class="selectedMode === option.value ? 'bg-primary text-primaryFg' : 'text-text active:bg-bg/70'"
213
+ @click="selectMode(option.value)"
214
+ >
215
+ {{ option.label }}
216
+ </button>
217
+ </div>
218
+ </div>
219
+
220
+ <div class="overflow-hidden rounded-2xl bg-secondaryBg">
221
+ <label class="relative flex min-h-16 items-center justify-between gap-3 border-b border-sectionSeparator px-4 py-3 active:bg-bg/60">
222
+ <span class="text-base font-medium text-text">Category</span>
223
+ <span class="flex min-w-0 items-center gap-1 text-base text-hint">
224
+ <span class="truncate">{{ selectedCategoryLabel }}</span>
225
+ <Icon name="i-heroicons-chevron-right-20-solid" class="h-5 w-5 shrink-0" />
226
+ </span>
227
+ <select
228
+ v-model="selectedCategory"
229
+ aria-label="Category"
230
+ class="absolute inset-0 h-full w-full cursor-pointer opacity-0"
231
+ @change="onFormSelection"
232
+ >
233
+ <option
234
+ v-for="option in categoryOptions"
235
+ :key="option.value"
236
+ :value="option.value"
237
+ >
238
+ {{ option.label }}
239
+ </option>
240
+ </select>
241
+ </label>
242
+
243
+ <label class="block px-4 py-3">
244
+ <span class="mb-3 flex items-center justify-between gap-3 text-base">
245
+ <span class="font-medium text-text">Intensity</span>
246
+ <span class="text-hint">{{ sliderValue }}%</span>
247
+ </span>
248
+ <input
249
+ v-model.number="sliderValue"
250
+ type="range"
251
+ min="0"
252
+ max="100"
253
+ step="5"
254
+ class="tg-native-range h-8 w-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-link/40"
255
+ :style="{ '--range-value': `${sliderValue}%` }"
256
+ @change="onFormSelection"
257
+ />
258
+ </label>
259
+ </div>
123
260
  </div>
124
261
  </div>
125
262
 
126
263
  <div class="space-y-2">
127
264
  <h3 class="text-sm font-medium text-subtitle">Counters & Progress</h3>
128
- <div class="space-y-3">
129
- <div class="flex items-center justify-between">
130
- <span class="text-sm">Counter: {{ counter }}</span>
131
- <div class="flex gap-2">
265
+ <div class="overflow-hidden rounded-2xl bg-secondaryBg">
266
+ <div class="flex min-h-24 items-center justify-between gap-4 px-4 py-4">
267
+ <span class="text-lg font-medium text-text">Counter</span>
268
+ <div class="grid shrink-0 grid-cols-[3.25rem_3rem_3.25rem] items-center gap-2">
132
269
  <button
133
- @click="counter--"
134
- class="w-8 h-8 bg-button-color text-button-text rounded-full flex items-center justify-center text-sm font-medium"
270
+ type="button"
271
+ aria-label="Decrease counter"
272
+ class="flex h-[3.25rem] w-[3.25rem] items-center justify-center rounded-2xl bg-bg text-3xl font-light leading-none text-primary transition-colors active:bg-sectionSeparator focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-link/40"
273
+ @click="decrementCounter"
135
274
  >
136
- -
275
+ <span class="-mt-1">-</span>
137
276
  </button>
277
+ <output class="text-center text-2xl font-semibold tabular-nums text-text">
278
+ {{ counter }}
279
+ </output>
138
280
  <button
139
- @click="counter++"
140
- class="w-8 h-8 bg-button-color text-button-text rounded-full flex items-center justify-center text-sm font-medium"
281
+ type="button"
282
+ aria-label="Increase counter"
283
+ class="flex h-[3.25rem] w-[3.25rem] items-center justify-center rounded-2xl bg-bg text-3xl font-light leading-none text-primary transition-colors active:bg-sectionSeparator focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-link/40"
284
+ @click="incrementCounter"
141
285
  >
142
- +
286
+ <span class="-mt-1">+</span>
143
287
  </button>
144
288
  </div>
145
289
  </div>
146
- <div class="space-y-1">
147
- <div class="flex justify-between text-sm">
148
- <span>Progress</span>
149
- <span>{{ progress }}%</span>
290
+
291
+ <div class="border-t border-sectionSeparator px-4 py-5">
292
+ <div class="mb-4 flex items-center justify-between gap-3">
293
+ <span class="text-lg font-medium text-text">Progress</span>
294
+ <output class="text-xl tabular-nums text-hint">{{ progress }}%</output>
150
295
  </div>
151
- <div class="w-full h-2 bg-secondaryBg rounded-full overflow-hidden">
296
+ <div
297
+ class="h-3 w-full overflow-hidden rounded-full bg-bg"
298
+ role="progressbar"
299
+ :aria-valuenow="progress"
300
+ aria-valuemin="0"
301
+ aria-valuemax="100"
302
+ aria-label="Counter progress"
303
+ >
152
304
  <div
153
- class="h-full bg-link transition-all duration-300"
305
+ class="h-full rounded-full bg-primary transition-all duration-300"
154
306
  :style="{ width: `${progress}%` }"
155
307
  />
156
308
  </div>
@@ -218,16 +370,44 @@ const main = useMainButton()
218
370
  // Interactive state
219
371
  const loadingButton = ref(false)
220
372
  const counter = ref(0)
221
- const textInput = ref('')
222
- const textareaInput = ref('')
373
+ const formName = ref('Your name')
374
+ const formNotes = ref('')
375
+ const notificationsEnabled = ref(true)
376
+ const termsAccepted = ref(true)
377
+ const selectedVisibility = ref('friends')
378
+ const selectedMode = ref('auto')
379
+ const selectedCategory = ref('personal')
380
+ const sliderValue = ref(60)
223
381
 
224
382
  const progress = computed(() => Math.min(100, Math.max(0, counter.value * 10)))
225
383
 
226
384
  const cellDescription = computed(() => {
227
- if (textInput.value) return `You typed: "${textInput.value}"`
385
+ if (formName.value) return `You typed: "${formName.value}"`
228
386
  return 'Type something in the input above'
229
387
  })
230
388
 
389
+ const visibilityOptions = [
390
+ { label: 'Everyone', value: 'everyone' },
391
+ { label: 'Friends', value: 'friends' },
392
+ { label: 'Only me', value: 'private' },
393
+ ]
394
+
395
+ const modeOptions = [
396
+ { label: 'Auto', value: 'auto' },
397
+ { label: 'Light', value: 'light' },
398
+ { label: 'Dark', value: 'dark' },
399
+ ]
400
+
401
+ const categoryOptions = [
402
+ { label: 'Personal', value: 'personal' },
403
+ { label: 'Work', value: 'work' },
404
+ { label: 'Travel', value: 'travel' },
405
+ ]
406
+
407
+ const selectedCategoryLabel = computed(() => {
408
+ return categoryOptions.find(option => option.value === selectedCategory.value)?.label || selectedCategory.value
409
+ })
410
+
231
411
  function toggleLoading() {
232
412
  loadingButton.value = true
233
413
  setTimeout(() => {
@@ -241,6 +421,26 @@ function onCellClick() {
241
421
  counter.value += 5
242
422
  }
243
423
 
424
+ function onFormSelection() {
425
+ haptic.selectionChanged()
426
+ }
427
+
428
+ function selectMode(value: string) {
429
+ if (selectedMode.value === value) return
430
+ selectedMode.value = value
431
+ onFormSelection()
432
+ }
433
+
434
+ function decrementCounter() {
435
+ counter.value -= 1
436
+ haptic.selectionChanged()
437
+ }
438
+
439
+ function incrementCounter() {
440
+ counter.value += 1
441
+ haptic.selectionChanged()
442
+ }
443
+
244
444
  // Theme colors demo
245
445
  const themeColors = computed(() => [
246
446
  { name: 'Background', value: theme.backgroundColor.value || 'var(--tg-theme-bg-color)' },
@@ -277,3 +477,54 @@ onMounted(() => {
277
477
  }, 500)
278
478
  })
279
479
  </script>
480
+
481
+ <style scoped>
482
+ .tg-native-range {
483
+ appearance: none;
484
+ background: transparent;
485
+ }
486
+
487
+ .tg-native-range::-webkit-slider-runnable-track {
488
+ height: 4px;
489
+ border-radius: 9999px;
490
+ background: linear-gradient(
491
+ to right,
492
+ var(--tg-theme-button-color) 0%,
493
+ var(--tg-theme-button-color) var(--range-value, 60%),
494
+ var(--tg-theme-bg-color) var(--range-value, 60%),
495
+ var(--tg-theme-bg-color) 100%
496
+ );
497
+ }
498
+
499
+ .tg-native-range::-webkit-slider-thumb {
500
+ appearance: none;
501
+ width: 28px;
502
+ height: 28px;
503
+ margin-top: -12px;
504
+ border: 0;
505
+ border-radius: 9999px;
506
+ background: var(--tg-theme-button-color);
507
+ box-shadow: 0 2px 8px rgb(0 0 0 / 24%);
508
+ }
509
+
510
+ .tg-native-range::-moz-range-track {
511
+ height: 4px;
512
+ border-radius: 9999px;
513
+ background: var(--tg-theme-bg-color);
514
+ }
515
+
516
+ .tg-native-range::-moz-range-progress {
517
+ height: 4px;
518
+ border-radius: 9999px;
519
+ background: var(--tg-theme-button-color);
520
+ }
521
+
522
+ .tg-native-range::-moz-range-thumb {
523
+ width: 28px;
524
+ height: 28px;
525
+ border: 0;
526
+ border-radius: 9999px;
527
+ background: var(--tg-theme-button-color);
528
+ box-shadow: 0 2px 8px rgb(0 0 0 / 24%);
529
+ }
530
+ </style>
@@ -33,6 +33,14 @@ export interface TelegramWebAppInitData {
33
33
  can_send_after?: number
34
34
  auth_date: number
35
35
  hash: string
36
+ signature?: string
37
+ }
38
+
39
+ export interface TelegramWebAppInsets {
40
+ top: number
41
+ bottom: number
42
+ left: number
43
+ right: number
36
44
  }
37
45
 
38
46
  export interface TelegramWebAppThemeParams {
@@ -62,11 +70,15 @@ export interface TelegramWebAppBackButton {
62
70
  }
63
71
 
64
72
  export interface TelegramWebAppMainButton {
73
+ type?: 'main' | 'secondary'
74
+ iconCustomEmojiId?: string
65
75
  text: string
66
76
  color: string
67
77
  textColor: string
68
78
  isVisible: boolean
69
79
  isActive: boolean
80
+ hasShineEffect?: boolean
81
+ position?: 'left' | 'right' | 'top' | 'bottom'
70
82
  readonly isProgressVisible: boolean
71
83
  setText(text: string): void
72
84
  onClick(callback: () => void): void
@@ -78,20 +90,134 @@ export interface TelegramWebAppMainButton {
78
90
  showProgress(leaveActive?: boolean): void
79
91
  hideProgress(): void
80
92
  setParams(params: {
93
+ icon_custom_emoji_id?: string
81
94
  text?: string
82
95
  color?: string
83
96
  text_color?: string
97
+ has_shine_effect?: boolean
98
+ position?: 'left' | 'right' | 'top' | 'bottom'
84
99
  is_active?: boolean
85
100
  is_visible?: boolean
86
101
  }): void
87
102
  }
88
103
 
89
104
  export interface TelegramWebAppHapticFeedback {
90
- impactOccurred(style: 'light' | 'medium' | 'heavy'): void
105
+ impactOccurred(style: 'light' | 'medium' | 'heavy' | 'rigid' | 'soft'): void
91
106
  notificationOccurred(type: 'error' | 'success' | 'warning'): void
92
107
  selectionChanged(): void
93
108
  }
94
109
 
110
+ export interface TelegramWebAppSettingsButton {
111
+ isVisible: boolean
112
+ onClick(callback: () => void): void
113
+ offClick(callback: () => void): void
114
+ show(): void
115
+ hide(): void
116
+ }
117
+
118
+ export interface TelegramStorageError {
119
+ error?: string
120
+ message?: string
121
+ }
122
+
123
+ export type TelegramStorageCallback<T = boolean> = (error: TelegramStorageError | string | null, result?: T) => void
124
+
125
+ export interface TelegramWebAppCloudStorage {
126
+ setItem(key: string, value: string, callback?: TelegramStorageCallback<boolean>): void
127
+ getItem(key: string, callback: TelegramStorageCallback<string>): void
128
+ getItems(keys: string[], callback: TelegramStorageCallback<Record<string, string>>): void
129
+ removeItem(key: string, callback?: TelegramStorageCallback<boolean>): void
130
+ removeItems(keys: string[], callback?: TelegramStorageCallback<boolean>): void
131
+ getKeys(callback: TelegramStorageCallback<string[]>): void
132
+ }
133
+
134
+ export interface TelegramWebAppDeviceStorage {
135
+ setItem(key: string, value: string, callback?: TelegramStorageCallback<boolean>): void
136
+ getItem(key: string, callback: TelegramStorageCallback<string | null>): void
137
+ removeItem(key: string, callback?: TelegramStorageCallback<boolean>): void
138
+ clear(callback?: TelegramStorageCallback<boolean>): void
139
+ }
140
+
141
+ export interface TelegramWebAppSecureStorage extends TelegramWebAppDeviceStorage {
142
+ getItem(key: string, callback: (error: TelegramStorageError | string | null, value?: string | null, canRestore?: boolean) => void): void
143
+ restoreItem(key: string, callback?: TelegramStorageCallback<string | null>): void
144
+ }
145
+
146
+ export interface TelegramWebAppBiometricManager {
147
+ isInited: boolean
148
+ isBiometricAvailable: boolean
149
+ biometricType: 'finger' | 'face' | 'unknown'
150
+ isAccessRequested: boolean
151
+ isAccessGranted: boolean
152
+ isBiometricTokenSaved: boolean
153
+ deviceId: string
154
+ init(callback?: () => void): void
155
+ requestAccess(params: { reason?: string }, callback?: (granted: boolean) => void): void
156
+ authenticate(params: { reason?: string }, callback?: (authenticated: boolean, token?: string) => void): void
157
+ updateBiometricToken(token: string, callback?: (updated: boolean) => void): void
158
+ openSettings(): void
159
+ }
160
+
161
+ export interface TelegramWebAppMotionSensor {
162
+ isStarted: boolean
163
+ x: number
164
+ y: number
165
+ z: number
166
+ start(params?: { refresh_rate?: number }, callback?: (started: boolean) => void): void
167
+ stop(callback?: (stopped: boolean) => void): void
168
+ }
169
+
170
+ export interface TelegramWebAppDeviceOrientation {
171
+ isStarted: boolean
172
+ absolute: boolean
173
+ alpha: number
174
+ beta: number
175
+ gamma: number
176
+ start(params?: { refresh_rate?: number, need_absolute?: boolean }, callback?: (started: boolean) => void): void
177
+ stop(callback?: (stopped: boolean) => void): void
178
+ }
179
+
180
+ export interface TelegramLocationData {
181
+ latitude: number
182
+ longitude: number
183
+ altitude: number | null
184
+ course: number | null
185
+ speed: number | null
186
+ horizontal_accuracy: number | null
187
+ vertical_accuracy: number | null
188
+ course_accuracy: number | null
189
+ speed_accuracy: number | null
190
+ }
191
+
192
+ export interface TelegramWebAppLocationManager {
193
+ isInited: boolean
194
+ isLocationAvailable: boolean
195
+ isAccessRequested: boolean
196
+ isAccessGranted: boolean
197
+ init(callback?: () => void): void
198
+ getLocation(callback: (location: TelegramLocationData | null) => void): void
199
+ openSettings(): void
200
+ }
201
+
202
+ export type TelegramHomeScreenStatus = 'unsupported' | 'unknown' | 'added' | 'missed'
203
+
204
+ export interface TelegramStoryShareParams {
205
+ text?: string
206
+ widget_link?: {
207
+ url: string
208
+ name?: string
209
+ }
210
+ }
211
+
212
+ export interface TelegramEmojiStatusParams {
213
+ duration?: number
214
+ }
215
+
216
+ export interface TelegramDownloadFileParams {
217
+ url: string
218
+ file_name: string
219
+ }
220
+
95
221
  export interface TelegramWebApp {
96
222
  initData: string
97
223
  initDataUnsafe: TelegramWebAppInitData
@@ -99,9 +225,14 @@ export interface TelegramWebApp {
99
225
  platform: string
100
226
  colorScheme: 'light' | 'dark'
101
227
  themeParams: TelegramWebAppThemeParams
228
+ isActive?: boolean
102
229
  isExpanded: boolean
230
+ isFullscreen?: boolean
231
+ isOrientationLocked?: boolean
103
232
  viewportHeight: number
104
233
  viewportStableHeight: number
234
+ safeAreaInset?: TelegramWebAppInsets
235
+ contentSafeAreaInset?: TelegramWebAppInsets
105
236
  headerColor: string
106
237
  backgroundColor: string
107
238
  bottomBarColor: string
@@ -109,7 +240,17 @@ export interface TelegramWebApp {
109
240
  isVerticalSwipesEnabled: boolean
110
241
  BackButton: TelegramWebAppBackButton
111
242
  MainButton: TelegramWebAppMainButton
243
+ SecondaryButton?: TelegramWebAppMainButton
244
+ SettingsButton?: TelegramWebAppSettingsButton
112
245
  HapticFeedback: TelegramWebAppHapticFeedback
246
+ CloudStorage?: TelegramWebAppCloudStorage
247
+ BiometricManager?: TelegramWebAppBiometricManager
248
+ Accelerometer?: TelegramWebAppMotionSensor
249
+ DeviceOrientation?: TelegramWebAppDeviceOrientation
250
+ Gyroscope?: TelegramWebAppMotionSensor
251
+ LocationManager?: TelegramWebAppLocationManager
252
+ DeviceStorage?: TelegramWebAppDeviceStorage
253
+ SecureStorage?: TelegramWebAppSecureStorage
113
254
 
114
255
  isVersionAtLeast(version: string): boolean
115
256
  setHeaderColor(color: string): void
@@ -119,13 +260,25 @@ export interface TelegramWebApp {
119
260
  disableClosingConfirmation(): void
120
261
  enableVerticalSwipes(): void
121
262
  disableVerticalSwipes(): void
122
- onEvent(eventType: string, eventHandler: () => void): void
123
- offEvent(eventType: string, eventHandler: () => void): void
263
+ requestFullscreen?(): void
264
+ exitFullscreen?(): void
265
+ lockOrientation?(): void
266
+ unlockOrientation?(): void
267
+ addToHomeScreen?(): void
268
+ checkHomeScreenStatus?(callback?: (status: TelegramHomeScreenStatus) => void): void
269
+ onEvent(eventType: string, eventHandler: (...args: any[]) => void): void
270
+ offEvent(eventType: string, eventHandler: (...args: any[]) => void): void
124
271
  sendData(data: string): void
125
272
  switchInlineQuery(query: string, choose_chat_types?: string[]): void
126
273
  openLink(url: string, options?: { try_instant_view?: boolean }): void
127
274
  openTelegramLink(url: string): void
128
275
  openInvoice(url: string, callback?: (status: string) => void): void
276
+ shareToStory?(mediaUrl: string, params?: TelegramStoryShareParams): void
277
+ shareMessage?(msgId: string, callback?: (sent: boolean) => void): void
278
+ setEmojiStatus?(customEmojiId: string, params?: TelegramEmojiStatusParams, callback?: (set: boolean) => void): void
279
+ requestEmojiStatusAccess?(callback?: (granted: boolean) => void): void
280
+ downloadFile?(params: TelegramDownloadFileParams, callback?: (accepted: boolean) => void): void
281
+ hideKeyboard?(): void
129
282
  showPopup(params: {
130
283
  title?: string
131
284
  message: string
@@ -144,6 +297,7 @@ export interface TelegramWebApp {
144
297
  readTextFromClipboard(callback?: (text: string) => void): void
145
298
  requestWriteAccess(callback?: (granted: boolean) => void): void
146
299
  requestContact(callback?: (granted: boolean) => void): void
300
+ requestChat?(reqId: string, callback?: (sent: boolean) => void): void
147
301
  ready(): void
148
302
  expand(): void
149
303
  close(): void
@@ -1,17 +1,42 @@
1
1
  export type RGB = { r?: number; g?: number; b?: number }
2
2
 
3
+ const namedColors: Record<string, string> = {
4
+ red: '#ff0000',
5
+ green: '#008000',
6
+ blue: '#0000ff'
7
+ }
8
+
9
+ function componentToHex(value: number): string | null {
10
+ if (!Number.isInteger(value) || value < 0 || value > 255) return null
11
+ return value.toString(16).padStart(2, '0')
12
+ }
13
+
3
14
  export function toHex(value?: RGB | `#${string}` | string | null): string {
4
15
  if (!value) return '—'
5
16
 
6
17
  if (typeof value === 'string') {
7
- const v = value.trim()
8
- return v.startsWith('#') ? v : '—'
18
+ const v = value.trim().toLowerCase()
19
+ if (/^#[0-9a-f]{6}$/.test(v)) return v
20
+ if (/^#[0-9a-f]{3}$/.test(v)) {
21
+ return `#${v.slice(1).split('').map(c => c + c).join('')}`
22
+ }
23
+
24
+ const rgb = v.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/)
25
+ if (rgb) {
26
+ const parts = rgb.slice(1).map(part => componentToHex(Number(part)))
27
+ if (parts.every(Boolean)) return `#${parts.join('')}`
28
+ }
29
+
30
+ return namedColors[v] || '—'
9
31
  }
10
32
 
11
33
  const { r, g, b } = value
12
34
  if (typeof r !== 'number' || typeof g !== 'number' || typeof b !== 'number') return '—'
13
- const to = (n: number) => n.toString(16).padStart(2, '0')
14
- return `#${to(r)}${to(g)}${to(b)}`
35
+ const red = componentToHex(r)
36
+ const green = componentToHex(g)
37
+ const blue = componentToHex(b)
38
+ if (!red || !green || !blue) return '—'
39
+ return `#${red}${green}${blue}`
15
40
  }
16
41
 
17
42
  export function toRgb(value?: RGB | `#${string}` | string | null): { r: number; g: number; b: number } | null {
@@ -34,4 +59,3 @@ export function toRgb(value?: RGB | `#${string}` | string | null): { r: number;
34
59
  if (typeof r !== 'number' || typeof g !== 'number' || typeof b !== 'number') return null
35
60
  return { r, g, b }
36
61
  }
37
-
package/nuxt.config.ts CHANGED
@@ -27,7 +27,7 @@ export default defineNuxtConfig({
27
27
  ],
28
28
  script: [
29
29
  {
30
- src: 'https://telegram.org/js/telegram-web-app.js?58'
30
+ src: 'https://telegram.org/js/telegram-web-app.js?62'
31
31
  }
32
32
  ]
33
33
  },