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.
- package/.github/dependabot.yml +6 -0
- package/.github/workflows/deploy-pages.yml +63 -0
- package/README.md +47 -5
- package/app/composables/telegram.ts +370 -36
- package/app/pages/components.vue +283 -32
- package/app/types/telegram-webapp.ts +157 -3
- package/app/utils/color.ts +29 -5
- package/nuxt.config.ts +1 -1
- package/package.json +16 -16
- package/tests/pages.spec.ts +9 -12
- package/tests/telegram.spec.ts +1 -1
package/app/pages/components.vue
CHANGED
|
@@ -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-
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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="
|
|
129
|
-
<div class="flex items-center justify-between">
|
|
130
|
-
<span class="text-
|
|
131
|
-
<div class="
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
<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
|
|
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-
|
|
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
|
|
222
|
-
const
|
|
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 (
|
|
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
|
-
|
|
123
|
-
|
|
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
|
package/app/utils/color.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
14
|
-
|
|
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
|
-
|