@letar/forms 1.0.3 → 1.2.0
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/CHANGELOG.md +331 -0
- package/README.md +9 -9
- package/README.ru.md +389 -0
- package/analytics.js +3 -0
- package/analytics.js.map +1 -0
- package/chunk-2PSXYC3I.js +1782 -0
- package/chunk-2PSXYC3I.js.map +1 -0
- package/chunk-5D6S6EGF.js +206 -0
- package/chunk-5D6S6EGF.js.map +1 -0
- package/chunk-6E7VJAJT.js +78 -0
- package/chunk-6E7VJAJT.js.map +1 -0
- package/chunk-CGXKRCSM.js +117 -0
- package/chunk-CGXKRCSM.js.map +1 -0
- package/chunk-DQUVUMCX.js +982 -0
- package/chunk-DQUVUMCX.js.map +1 -0
- package/chunk-K3J4L26K.js +345 -0
- package/chunk-K3J4L26K.js.map +1 -0
- package/chunk-MAYUFA5K.js +822 -0
- package/chunk-MAYUFA5K.js.map +1 -0
- package/{chunk-4V6WBJ76.js → chunk-MVGXZNHP.js} +2 -2
- package/{chunk-4V6WBJ76.js.map → chunk-MVGXZNHP.js.map} +1 -1
- package/chunk-MZDTJSF7.js +299 -0
- package/chunk-MZDTJSF7.js.map +1 -0
- package/chunk-Q5EOF36Y.js +709 -0
- package/chunk-Q5EOF36Y.js.map +1 -0
- package/{chunk-7FEQFDJ7.js → chunk-R2RTCKXY.js} +2 -2
- package/{chunk-7FEQFDJ7.js.map → chunk-R2RTCKXY.js.map} +1 -1
- package/chunk-XFWLD5EO.js +1045 -0
- package/chunk-XFWLD5EO.js.map +1 -0
- package/fields/boolean.js +5 -0
- package/fields/boolean.js.map +1 -0
- package/fields/datetime.js +5 -0
- package/fields/datetime.js.map +1 -0
- package/fields/number.js +5 -0
- package/fields/number.js.map +1 -0
- package/fields/selection.js +5 -0
- package/fields/selection.js.map +1 -0
- package/fields/specialized.js +5 -0
- package/fields/specialized.js.map +1 -0
- package/fields/text.js +5 -0
- package/fields/text.js.map +1 -0
- package/hcaptcha-U4XIT3HS.js +64 -0
- package/hcaptcha-U4XIT3HS.js.map +1 -0
- package/i18n.js +1 -1
- package/index.js +3736 -4962
- package/index.js.map +1 -1
- package/offline.js +1 -1
- package/package.json +59 -4
- package/recaptcha-PKAUAY2S.js +56 -0
- package/recaptcha-PKAUAY2S.js.map +1 -0
- package/server-errors.js +3 -0
- package/server-errors.js.map +1 -0
- package/turnstile-7FXTBSLW.js +36 -0
- package/turnstile-7FXTBSLW.js.map +1 -0
- package/validators/ru.js +73 -0
- package/validators/ru.js.map +1 -0
package/README.ru.md
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# @lena/form-components
|
|
2
|
+
|
|
3
|
+
Переиспользуемая UI-библиотека компонентов форм на базе TanStack Form для монорепозитория Lena.
|
|
4
|
+
|
|
5
|
+
[English documentation](./README.en.md)
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Form } from '@lena/form-components'
|
|
11
|
+
import { z } from 'zod/v4'
|
|
12
|
+
|
|
13
|
+
const Schema = z.object({
|
|
14
|
+
title: z.string().min(2).meta({ ui: { title: 'Название', placeholder: 'Введите...' } }),
|
|
15
|
+
rating: z.number().min(0).max(10).meta({ ui: { title: 'Рейтинг' } }),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
<Form schema={Schema} initialValue={{ title: '', rating: 5 }} onSubmit={save}>
|
|
19
|
+
<Form.Field.String name="title" />
|
|
20
|
+
<Form.Field.Number name="rating" />
|
|
21
|
+
<Form.Button.Submit>Сохранить</Form.Button.Submit>
|
|
22
|
+
</Form>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Или полная автогенерация:**
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
<Form.FromSchema schema={Schema} initialValue={data} onSubmit={handleSubmit} submitLabel="Создать" />
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Философия: Отделение вёрстки от логики
|
|
34
|
+
|
|
35
|
+
| Аспект | Где определяется | Как используется в JSX |
|
|
36
|
+
| ----------------- | -------------------------- | ------------------------------- |
|
|
37
|
+
| **Валидация** | Zod схема | `schema={Schema}` |
|
|
38
|
+
| **UI метаданные** | Zod `.meta({ ui: {...} })` | Автоматически из схемы |
|
|
39
|
+
| **Структура** | TypeScript типы | `initialValue={data}` |
|
|
40
|
+
| **Вёрстка** | JSX | `<HStack>`, `<VStack>`, `<Box>` |
|
|
41
|
+
|
|
42
|
+
**Результат:** JSX содержит только вёрстку и имена полей. Вся логика живёт в схеме.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Документация
|
|
47
|
+
|
|
48
|
+
| Категория | Документация | Описание |
|
|
49
|
+
| ---------------- | -------------------------------------------------------- | --------------------------------------------- |
|
|
50
|
+
| Field компоненты | [docs/fields.md](./docs/fields.md) | 50+ типов полей (String, Number, Select, ...) |
|
|
51
|
+
| Form-level | [docs/form-level.md](./docs/form-level.md) | Steps, When, Watch, Errors, Persistence |
|
|
52
|
+
| Schema генерация | [docs/schema-generation.md](./docs/schema-generation.md) | FromSchema, AutoFields, Builder, Templates |
|
|
53
|
+
| Server Errors | [docs/server-errors.md](./docs/server-errors.md) | Маппинг Prisma/ZenStack/Zod ошибок на поля |
|
|
54
|
+
| Offline | [docs/offline.md](./docs/offline.md) | Оффлайн режим, очередь синхронизации |
|
|
55
|
+
| ZenStack | [docs/zenstack.md](./docs/zenstack.md) | Плагин, @form.\* директивы, withUIMeta |
|
|
56
|
+
| i18n | [docs/i18n.md](./docs/i18n.md) | Мультиязычность, перевод ошибок валидации |
|
|
57
|
+
| API Reference | [docs/api-reference.md](./docs/api-reference.md) | Хуки, контексты, типы |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Основные возможности
|
|
62
|
+
|
|
63
|
+
### 40+ Field компонентов
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// Текстовые
|
|
67
|
+
<Form.Field.String name="title" />
|
|
68
|
+
<Form.Field.Textarea name="description" />
|
|
69
|
+
<Form.Field.RichText name="content" />
|
|
70
|
+
|
|
71
|
+
// Числовые
|
|
72
|
+
<Form.Field.Number name="price" />
|
|
73
|
+
<Form.Field.Slider name="rating" />
|
|
74
|
+
<Form.Field.Currency name="amount" />
|
|
75
|
+
|
|
76
|
+
// Выбор
|
|
77
|
+
<Form.Field.Select name="category" />
|
|
78
|
+
<Form.Field.RadioGroup name="type" />
|
|
79
|
+
<Form.Field.Checkbox name="agree" />
|
|
80
|
+
|
|
81
|
+
// Специальные
|
|
82
|
+
<Form.Field.Date name="birthday" />
|
|
83
|
+
<Form.Field.Phone name="phone" />
|
|
84
|
+
<Form.Field.FileUpload name="avatar" />
|
|
85
|
+
<Form.Field.Signature name="signature" />
|
|
86
|
+
<Form.Field.CreditCard name="card" />
|
|
87
|
+
|
|
88
|
+
// Защита
|
|
89
|
+
<Form.Captcha />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
[Полный список → docs/fields.md](./docs/fields.md)
|
|
93
|
+
|
|
94
|
+
### Form-level компоненты
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
<Form schema={Schema} initialValue={data} onSubmit={save}>
|
|
98
|
+
{/* Реактивные побочные эффекты */}
|
|
99
|
+
<Form.Watch
|
|
100
|
+
field="name"
|
|
101
|
+
onChange={(v, { setFieldValue }) => {
|
|
102
|
+
setFieldValue('slug', transliterate(String(v)))
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
{/* Условный рендеринг */}
|
|
107
|
+
<Form.When field="type" is="company">
|
|
108
|
+
<Form.Field.String name="companyName" />
|
|
109
|
+
</Form.When>
|
|
110
|
+
|
|
111
|
+
{/* Мультистеп формы */}
|
|
112
|
+
<Form.Steps animated validateOnNext>
|
|
113
|
+
<Form.Steps.Step title="Шаг 1">...</Form.Steps.Step>
|
|
114
|
+
<Form.Steps.Step title="Шаг 2">...</Form.Steps.Step>
|
|
115
|
+
<Form.Steps.Navigation />
|
|
116
|
+
</Form.Steps>
|
|
117
|
+
|
|
118
|
+
{/* Информационный блок */}
|
|
119
|
+
<Form.InfoBlock variant="info" title="Подсказка">
|
|
120
|
+
Заполните все поля для скидки.
|
|
121
|
+
</Form.InfoBlock>
|
|
122
|
+
|
|
123
|
+
{/* Разделитель секций */}
|
|
124
|
+
<Form.Divider label="Контакты" />
|
|
125
|
+
|
|
126
|
+
{/* Вычисляемые поля */}
|
|
127
|
+
<Form.Field.Calculated
|
|
128
|
+
name="total"
|
|
129
|
+
compute={(v) => v.price * v.qty}
|
|
130
|
+
format={(v) => `${v.toLocaleString()} ₽`}
|
|
131
|
+
deps={['price', 'qty']}
|
|
132
|
+
/>
|
|
133
|
+
|
|
134
|
+
{/* Табличный редактор (массив объектов) */}
|
|
135
|
+
<Form.Field.TableEditor
|
|
136
|
+
name="items"
|
|
137
|
+
columns={[
|
|
138
|
+
{ name: 'product', width: '40%' },
|
|
139
|
+
{ name: 'qty', width: '15%', align: 'right' },
|
|
140
|
+
{ name: 'price', width: '15%', align: 'right' },
|
|
141
|
+
{ name: 'total', computed: (row) => row.qty * row.price, label: 'Итого' },
|
|
142
|
+
]}
|
|
143
|
+
addLabel="Добавить товар"
|
|
144
|
+
footer={[{ column: 'total', aggregate: 'sum', label: 'Итого:' }]}
|
|
145
|
+
/>
|
|
146
|
+
|
|
147
|
+
{/* Скрытые поля (UTM, referral) */}
|
|
148
|
+
<Form.Field.Hidden name="utm_source" value="landing" />
|
|
149
|
+
|
|
150
|
+
{/* Сводка ошибок */}
|
|
151
|
+
<Form.Errors title="Исправьте ошибки:" />
|
|
152
|
+
|
|
153
|
+
{/* JSON-инспектор значений (скрыт в production) */}
|
|
154
|
+
<Form.DebugValues />
|
|
155
|
+
|
|
156
|
+
<Form.Button.Submit />
|
|
157
|
+
</Form>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
[Подробнее → docs/form-level.md](./docs/form-level.md)
|
|
161
|
+
|
|
162
|
+
### Группы и массивы
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
// Вложенный объект
|
|
166
|
+
<Form.Group name="address">
|
|
167
|
+
<Form.Field.String name="city" /> {/* → address.city */}
|
|
168
|
+
<Form.Field.String name="street" /> {/* → address.street */}
|
|
169
|
+
</Form.Group>
|
|
170
|
+
|
|
171
|
+
// Массив
|
|
172
|
+
<Form.Group.List name="phones">
|
|
173
|
+
<Form.Field.Phone />
|
|
174
|
+
<Form.Group.List.Button.Add>Добавить телефон</Form.Group.List.Button.Add>
|
|
175
|
+
</Form.Group.List>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Smart Autofill
|
|
179
|
+
|
|
180
|
+
Поля автоматически получают правильные `autocomplete` атрибуты (+30% конверсии, WCAG 1.3.5):
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
<Form.Field.String name="email" /> // → autocomplete="email"
|
|
184
|
+
<Form.Field.String name="firstName" /> // → autocomplete="given-name"
|
|
185
|
+
<Form.Field.Password name="password" /> // → autocomplete="current-password"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Override: `autoComplete` prop или `.meta({ ui: { autocomplete: 'off' } })`.
|
|
189
|
+
|
|
190
|
+
### Автоматические constraints из Zod
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
const Schema = z.object({
|
|
194
|
+
title: z.string().min(2).max(100), // → minLength={2} maxLength={100}
|
|
195
|
+
email: z.string().email(), // → type="email"
|
|
196
|
+
rating: z.number().min(1).max(10), // → min={1} max={10}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// DRY: валидация и UI constraints в одном месте
|
|
200
|
+
<Form.Field.String name="title" /> {/* maxLength={100} из схемы */}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### ZenStack интеграция
|
|
204
|
+
|
|
205
|
+
```zmodel
|
|
206
|
+
model Product {
|
|
207
|
+
/// @form.title("Название продукта")
|
|
208
|
+
/// @form.placeholder("Введите название")
|
|
209
|
+
title String
|
|
210
|
+
|
|
211
|
+
/// @form.title("Цена")
|
|
212
|
+
/// @form.fieldType("currency")
|
|
213
|
+
/// @form.props({ min: 0, currency: "RUB" })
|
|
214
|
+
price Int
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
import { ProductCreateFormSchema } from '@/generated/form-schemas'
|
|
220
|
+
<Form.FromSchema schema={ProductCreateFormSchema} initialValue={data} onSubmit={save} />
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
[Подробнее → docs/zenstack.md](./docs/zenstack.md)
|
|
224
|
+
|
|
225
|
+
### Offline Support
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
<Form
|
|
229
|
+
initialValue={data}
|
|
230
|
+
offline={{
|
|
231
|
+
actionType: 'UPDATE_PROFILE',
|
|
232
|
+
onQueued: () => toast.info('Сохранено локально'),
|
|
233
|
+
onSynced: () => toast.success('Синхронизировано'),
|
|
234
|
+
}}
|
|
235
|
+
onSubmit={handleSubmit}
|
|
236
|
+
>
|
|
237
|
+
<Form.OfflineIndicator />
|
|
238
|
+
<Form.Field.String name="name" />
|
|
239
|
+
<Form.Button.Submit />
|
|
240
|
+
</Form>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
[Подробнее → docs/offline.md](./docs/offline.md)
|
|
244
|
+
|
|
245
|
+
### Security
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
// Honeypot — ловушка для ботов
|
|
249
|
+
<Form honeypot={true} initialValue={data} onSubmit={handleSubmit}>
|
|
250
|
+
<Form.Field.String name="email" />
|
|
251
|
+
<Form.Button.Submit />
|
|
252
|
+
</Form>
|
|
253
|
+
|
|
254
|
+
// Rate Limiting — ограничение попыток submit
|
|
255
|
+
<Form rateLimit={{ maxSubmits: 3, windowMs: 60000 }} initialValue={data} onSubmit={handleSubmit}>
|
|
256
|
+
...
|
|
257
|
+
</Form>
|
|
258
|
+
|
|
259
|
+
// Secure File Upload — проверка MIME, удаление EXIF, переименование
|
|
260
|
+
<Form.Field.FileUpload
|
|
261
|
+
name="document"
|
|
262
|
+
security={{
|
|
263
|
+
maxSize: '10MB',
|
|
264
|
+
allowedTypes: ['image/jpeg', 'image/png', 'application/pdf'],
|
|
265
|
+
stripMetadata: true,
|
|
266
|
+
renameFile: true,
|
|
267
|
+
}}
|
|
268
|
+
/>
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Установка
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# Уже установлен в монорепозитории
|
|
277
|
+
import { Form } from '@lena/form-components'
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Опциональные зависимости (npm)
|
|
281
|
+
|
|
282
|
+
| Пакет | Для чего |
|
|
283
|
+
| --------------------------- | --------------------------------- |
|
|
284
|
+
| `@dnd-kit/*` | Drag & drop сортировка в массивах |
|
|
285
|
+
| `use-mask-input` | Phone, MaskedInput |
|
|
286
|
+
| `@tiptap/*` | RichText редактор |
|
|
287
|
+
| `@uiw/react-json-view` | Form.DebugValues (JSON инспектор) |
|
|
288
|
+
| `next-intl` | i18n интеграция |
|
|
289
|
+
| `@marsidev/react-turnstile` | CAPTCHA (Cloudflare Turnstile) |
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Команды
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
nx build @lena/form-components # Сборка
|
|
297
|
+
nx lint @lena/form-components # Линтинг
|
|
298
|
+
nx test @lena/form-components # Тесты
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Провайдер адресов
|
|
304
|
+
|
|
305
|
+
Поля Address и City поддерживают подключаемые провайдеры геокодинга. DaData (Россия) встроен:
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import { createForm, createDaDataProvider } from '@lena/form-components'
|
|
309
|
+
|
|
310
|
+
// Вариант 1: через createForm (рекомендуемый)
|
|
311
|
+
const AppForm = createForm({
|
|
312
|
+
addressProvider: createDaDataProvider({ token: process.env.DADATA_TOKEN }),
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
<AppForm.Field.Address name="address" />
|
|
316
|
+
<AppForm.Field.City name="city" />
|
|
317
|
+
|
|
318
|
+
// Вариант 2: на конкретном поле
|
|
319
|
+
<Form.Field.Address name="address" provider={myProvider} />
|
|
320
|
+
|
|
321
|
+
// Вариант 3: обратная совместимость через token
|
|
322
|
+
<Form.Field.Address name="address" token="dadata-token" />
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Для других сервисов — реализуйте интерфейс `AddressProvider`:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
const myProvider: AddressProvider = {
|
|
329
|
+
async getSuggestions(query, options) {
|
|
330
|
+
const res = await fetch(`/api/geocode?q=${query}`)
|
|
331
|
+
return res.json() // [{ label, value, data }]
|
|
332
|
+
},
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## AI Tooling (MCP)
|
|
339
|
+
|
|
340
|
+
MCP сервер [`@letar/form-mcp`](../form-mcp/README.md) предоставляет AI-ассистентам (Claude Code, Cursor, VS Code Copilot) полный контекст о библиотеке: 40+ полей, паттерны форм, @form.\* директивы.
|
|
341
|
+
|
|
342
|
+
```json
|
|
343
|
+
{ "form-mcp": { "command": "npx", "args": ["-y", "@letar/form-mcp"] } }
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Bundle Size
|
|
347
|
+
|
|
348
|
+
Библиотека поставляется как ESM с external dependencies. Все тяжёлые зависимости (Chakra, React, Tiptap, dnd-kit) — external и не включаются в bundle.
|
|
349
|
+
|
|
350
|
+
| Модуль | Размер (brotli) | Размер (raw) |
|
|
351
|
+
| --------------------------------- | --------------- | ------------ |
|
|
352
|
+
| `@letar/forms` (все 40 полей) | **20 KB** | 109 KB |
|
|
353
|
+
| `@letar/forms/fields/text` | < 1 KB | re-export |
|
|
354
|
+
| `@letar/forms/fields/number` | < 1 KB | re-export |
|
|
355
|
+
| `@letar/forms/fields/datetime` | < 1 KB | re-export |
|
|
356
|
+
| `@letar/forms/fields/selection` | < 1 KB | re-export |
|
|
357
|
+
| `@letar/forms/fields/boolean` | < 1 KB | re-export |
|
|
358
|
+
| `@letar/forms/fields/specialized` | < 1 KB | re-export |
|
|
359
|
+
| `@letar/forms/offline` | < 1 KB | 5 KB |
|
|
360
|
+
| `@letar/forms/i18n` | < 1 KB | 13 KB |
|
|
361
|
+
|
|
362
|
+
Категорийные entry points (`fields/*`) позволяют импортировать только нужные поля:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// Полный импорт — все 40 полей
|
|
366
|
+
import { Form } from '@letar/forms'
|
|
367
|
+
|
|
368
|
+
// Категорийный импорт — только текстовые поля
|
|
369
|
+
import { FieldString, FieldTextarea } from '@letar/forms/fields/text'
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Метрики проверяются в CI через [size-limit](https://github.com/ai/size-limit).
|
|
373
|
+
|
|
374
|
+
### Ре-рендеры
|
|
375
|
+
|
|
376
|
+
При вводе текста в одно поле формы из 10 полей — **остальные 9 полей НЕ ре-рендерятся** (0 лишних рендеров). TanStack Form обеспечивает field-level подписки — каждое поле изолировано.
|
|
377
|
+
|
|
378
|
+
## Связанные документы
|
|
379
|
+
|
|
380
|
+
- [/.claude/docs/forms.md](../../.claude/docs/forms.md) — документация по формам
|
|
381
|
+
- [/.claude/docs/pwa-offline.md](../../.claude/docs/pwa-offline.md) — оффлайн-формы
|
|
382
|
+
- [/libs/form-mcp](../form-mcp/) — MCP сервер для AI-ассистентов
|
|
383
|
+
- [PLAN.md](./PLAN.md) — план развития библиотеки
|
|
384
|
+
- [TESTING_PLAN.md](./TESTING_PLAN.md) — план тестирования
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
**Версия:** 0.63.0
|
|
389
|
+
**Последнее обновление:** 2026-04-03
|
package/analytics.js
ADDED
package/analytics.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"analytics.js"}
|