@helfy/helfy 0.0.7 → 0.0.8

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.
Files changed (2) hide show
  1. package/README.md +265 -266
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,50 +1,50 @@
1
1
  # Helfy
2
2
 
3
- Helfy -- TypeScript UI-фреймворк с декоратор-ориентированным API, кастомным JSX и fine-grained reactivity.
3
+ Helfy a TypeScript UI framework with a decorator-oriented API, custom JSX, and fine-grained reactivity.
4
4
 
5
- Компоненты описываются классами с декоратором `@View`, состояние управляется через `@state` и `@observable`, а шаблоны используют расширенный JSX с директивами `@if`, `@for`, `@else`. Реактивность построена на signals (аналог SolidJS): `render()` вызывается один раз, а последующие обновления DOM происходят точечно только для тех узлов, которые читают изменившийся signal.
5
+ Components are defined as classes with the `@View` decorator, state is managed via `@state` and `@observable`, and templates use extended JSX with `@if`, `@for`, and `@else` directives. Reactivity is built on signals (similar to SolidJS): `render()` runs once, and subsequent DOM updates are granularonly nodes that read the changed signal are updated.
6
6
 
7
- ## Содержание
7
+ ## Table of contents
8
8
 
9
- - [Установка](#установка)
10
- - [Компоненты](#компоненты)
11
- - [Базовый компонент](#базовый-компонент)
9
+ - [Installation](#installation)
10
+ - [Components](#components)
11
+ - [Basic component](#basic-component)
12
12
  - [Props](#props)
13
- - [Локальное состояние (@state)](#локальное-состояние-state)
14
- - [Вложенные компоненты](#вложенные-компоненты)
15
- - [Монтирование в DOM](#монтирование-в-dom)
16
- - [Привязка к DOM и компонентам (@ref)](#привязка-к-dom-и-компонентам-ref)
17
- - [Двустороннее связывание (@bind)](#двустороннее-связывание-bind)
18
- - [Формы (@Form, @field, @field в JSX)](#формы-form-field-field-в-jsx)
19
- - [Шаблонные директивы](#шаблонные-директивы)
13
+ - [Local state (@state)](#local-state-state)
14
+ - [Nested components](#nested-components)
15
+ - [DOM mounting](#dom-mounting)
16
+ - [DOM and component refs (@ref)](#dom-and-component-refs-ref)
17
+ - [Two-way binding (@bind)](#two-way-binding-bind)
18
+ - [Forms (@Form, @field, @field in JSX)](#forms-form-field-field-in-jsx)
19
+ - [Template directives](#template-directives)
20
20
  - [@if / @elseif / @else](#if--elseif--else)
21
21
  - [@for](#for)
22
22
  - [@empty](#empty)
23
23
  - [State Management](#state-management)
24
- - [Создание Store](#создание-store)
25
- - [Подписка через @observe](#подписка-через-observe)
26
- - [Контекст хранилищ](#контекст-хранилищ)
27
- - [Логирование (@logger)](#логирование-logger)
24
+ - [Creating a Store](#creating-a-store)
25
+ - [Subscribing with @observe](#subscribing-with-observe)
26
+ - [Store context](#store-context)
27
+ - [Logging (@logger)](#logging-logger)
28
28
  - [Context & DI](#context--di)
29
- - [Провайдер (@Context, @provide)](#провайдер-context-provide)
30
- - [Потребитель (@ctx)](#потребитель-ctx)
31
- - [Реактивные поля](#реактивные-поля)
32
- - [Опциональная инжекция](#опциональная-инжекция)
33
- - [DI Container](#di-container-конструкторный-инжект)
29
+ - [Provider (@Context, @provide)](#provider-context-provide)
30
+ - [Consumer (@ctx)](#consumer-ctx)
31
+ - [Reactive fields](#reactive-fields)
32
+ - [Optional injection](#optional-injection)
33
+ - [DI Container](#di-container-constructor-injection)
34
34
  - [JSX](#jsx)
35
- - [Атрибуты](#атрибуты)
36
- - [События](#события)
37
- - [Стили](#стили)
38
- - [CSS-классы](#css-классы)
35
+ - [Attributes](#attributes)
36
+ - [Events](#events)
37
+ - [Styles](#styles)
38
+ - [CSS classes](#css-classes)
39
39
  - [API](#api)
40
40
 
41
41
  ---
42
42
 
43
- ## Установка
43
+ ## Installation
44
44
 
45
- ### Создание нового проекта (helfy-create)
45
+ ### Creating a new project (helfy-create)
46
46
 
47
- Быстрый стартсоздать проект одной командой:
47
+ Quick startcreate a project with a single command:
48
48
 
49
49
  ```bash
50
50
  npx helfy-create my-app
@@ -52,21 +52,21 @@ cd my-app
52
52
  npm run dev
53
53
  ```
54
54
 
55
- Без имени (создаст папку `helfy-app`):
55
+ Without a project name (creates `helfy-app`):
56
56
 
57
57
  ```bash
58
58
  npx helfy-create
59
59
  ```
60
60
 
61
- Опция `--skip-install` — создать без `npm install` (для оффлайна или своего registry).
61
+ The `--skip-install` option create without running `npm install` (e.g. for offline use or custom registry).
62
62
 
63
- ### Добавление в существующий проект
63
+ ### Adding to an existing project
64
64
 
65
65
  ```bash
66
66
  npm install @helfy/helfy
67
67
  ```
68
68
 
69
- или в `package.json`:
69
+ Or in `package.json`:
70
70
 
71
71
  ```json
72
72
  {
@@ -78,11 +78,11 @@ npm install @helfy/helfy
78
78
 
79
79
  ### Build setup
80
80
 
81
- Babel-плагин автоматически запускает DI-сканер перед трансформацией (pre-скрипты в `package.json` не нужны). Сканер генерирует `.helfy/di-tokens.ts`, `.helfy/di-registry.ts`. Добавьте `.helfy` в `.gitignore`.
81
+ The Babel plugin runs the DI scanner before transformation (no pre-scripts in `package.json` needed). The scanner generates `.helfy/di-tokens.ts` and `.helfy/di-registry.ts`. Add `.helfy` to `.gitignore`.
82
82
 
83
83
  ### Babel
84
84
 
85
- В `.babelrc` достаточно одного пресета:
85
+ A single preset in `.babelrc` is enough:
86
86
 
87
87
  ```json
88
88
  {
@@ -90,11 +90,11 @@ Babel-плагин автоматически запускает DI-сканер
90
90
  }
91
91
  ```
92
92
 
93
- Пресет включает: JSX runtime, TypeScript, legacy-декораторы, class properties, babel-plugin-transform-typescript-metadata, **helfy-di** (compile-time DI для `@Injectable<IX>()`, автоинъекция регистрации при `createApp`, трансформация `@logger()` → `@logger("<ClassName>")`).
93
+ The preset includes: JSX runtime, TypeScript, legacy decorators, class properties, babel-plugin-transform-typescript-metadata, **helfy-di** (compile-time DI for `@Injectable<IX>()`, auto-registration at `createApp`, `@logger()` → `@logger("<ClassName>")` transformation).
94
94
 
95
95
  ### Webpack
96
96
 
97
- Для обработки директив (`@if`, `@for`, `@ref`, `@bind`, `@field`) в `.tsx` файлах подключить loader:
97
+ To process directives (`@if`, `@for`, `@ref`, `@bind`, `@field`) in `.tsx` files, add the loader:
98
98
 
99
99
  ```javascript
100
100
  {
@@ -108,7 +108,7 @@ Babel-плагин автоматически запускает DI-сканер
108
108
 
109
109
  ### TypeScript
110
110
 
111
- В `tsconfig.json` указать:
111
+ In `tsconfig.json` set:
112
112
 
113
113
  ```json
114
114
  {
@@ -121,15 +121,15 @@ Babel-плагин автоматически запускает DI-сканер
121
121
  }
122
122
  ```
123
123
 
124
- Плагин `@helfy/helfy-ts-plugin` даёт поддержку директив `@if`, `@for`, `@ref`, `@bind`, `@field` в IDE (автодополнение, типизация, переход к определению). Устанавливается отдельно: `npm install @helfy/helfy-ts-plugin`.
124
+ The `@helfy/helfy-ts-plugin` plugin provides IDE support for `@if`, `@for`, `@ref`, `@bind`, `@field` directives (autocomplete, typing, go-to-definition). Install separately: `npm install @helfy/helfy-ts-plugin`.
125
125
 
126
126
  ---
127
127
 
128
- ## Компоненты
128
+ ## Components
129
129
 
130
- ### Базовый компонент
130
+ ### Basic component
131
131
 
132
- Компонент -- это класс с декоратором `@View` и методом `render()`, возвращающим JSX:
132
+ A component is a class with the `@View` decorator and a `render()` method that returns JSX:
133
133
 
134
134
  ```tsx
135
135
  import { View } from "@helfy/helfy";
@@ -146,19 +146,19 @@ class Hello {
146
146
  }
147
147
  ```
148
148
 
149
- Декоратор `@View` автоматически:
150
- - вызывает `render()` **один раз** и монтирует результат в DOM-фрагмент
151
- - добавляет свойство `view` (ссылка на корневой DOM-элемент)
152
- - оборачивает `this.props` в реактивный Proxy (каждый prop signal)
153
- - настраивает fine-grained эффекты: при изменении signal обновляется только конкретный DOM-узел, а не весь компонент
149
+ The `@View` decorator automatically:
150
+ - calls `render()` **once** and mounts the result into a DOM fragment
151
+ - adds a `view` property (reference to the root DOM element)
152
+ - wraps `this.props` in a reactive Proxy (each prop is a signal)
153
+ - configures fine-grained effects: when a signal changes, only the specific DOM node that reads it is updated, not the entire component
154
154
 
155
- > **Важно:** При наследовании `@View` нужно указывать и на дочернем классе. Без этого не будут работать `@ctx`, `scheduleUpdate` и обновление DOM.
155
+ > **Important:** When inheriting from `@View`, apply the decorator on the child class as well. Otherwise `@ctx`, `scheduleUpdate`, and DOM updates will not work.
156
156
 
157
157
  ### Props
158
158
 
159
- Props передаются через конструктор. Декоратор `@View` оборачивает `this.props` в реактивный Proxy — каждый prop становится signal. Это означает, что при изменении props родителем обновляются только те DOM-узлы, которые их читают, без полного перерендера компонента.
159
+ Props are passed via the constructor. The `@View` decorator wraps `this.props` in a reactive Proxy — each prop becomes a signal. This means that when the parent updates props, only the DOM nodes that read them are updated, without a full re-render.
160
160
 
161
- > **Важно:** Не деструктуризируйте `this.props` в `render()`. Обращайтесь к полям напрямую через `this.props.field` — это обеспечивает корректную подписку на signals.
161
+ > **Important:** Do not destructure `this.props` in `render()`. Access fields directly via `this.props.field` — this ensures correct signal subscription.
162
162
 
163
163
  ```tsx
164
164
  import { View } from "@helfy/helfy";
@@ -186,18 +186,17 @@ class Button {
186
186
  }
187
187
  ```
188
188
 
189
- Использование:
189
+ Usage:
190
190
 
191
191
  ```tsx
192
- <Button label="Нажми" onClick={() => console.log('clicked')} />
192
+ <Button label="Click" onClick={() => console.log('clicked')} />
193
193
  ```
194
194
 
195
- При изменении props родителем обновляются только те DOM-узлы, которые читают изменившиеся props.
195
+ When the parent updates props, only the DOM nodes that read the changed props are updated.
196
196
 
197
- ### Локальное состояние (@state)
197
+ ### Local state (@state)
198
198
 
199
- Декоратор `@state` делает поле реактивным.
200
- При изменении значения обновляются только те DOM-узлы и эффекты, которые читают это поле (fine-grained). Полный перерендер компонента не происходит:
199
+ The `@state` decorator makes a field reactive. When its value changes, only the DOM nodes and effects that read it are updated (fine-grained). The component is not fully re-rendered:
201
200
 
202
201
  ```tsx
203
202
  import { View, state } from "@helfy/helfy";
@@ -207,7 +206,7 @@ class Counter {
207
206
  @state private count = 0;
208
207
 
209
208
  increment() {
210
- this.count++; // обновит только узлы, читающие count
209
+ this.count++; // updates only nodes that read count
211
210
  }
212
211
 
213
212
  decrement() {
@@ -226,13 +225,13 @@ class Counter {
226
225
  }
227
226
  ```
228
227
 
229
- Можно объявить несколько `@state`-полей. Каждое из них независимо триггерит обновление только тех узлов DOM, которые его читают.
228
+ You can declare multiple `@state` fields. Each independently triggers updates only in the DOM nodes that read it.
230
229
 
231
- Signals‑примитивы (`createSignal`, `createEffect`, `createComputed`, `batch`, `onCleanup`) также экспортируются из `helfy` и могут использоваться напрямую, но в большинстве случаев достаточно `@state`, `@computed` и `@effect`.
230
+ Signal primitives (`createSignal`, `createEffect`, `createComputed`, `batch`, `onCleanup`) are also exported from `helfy` and can be used directly, but in most cases `@state`, `@computed`, and `@effect` are sufficient.
232
231
 
233
- ### Вложенные компоненты
232
+ ### Nested components
234
233
 
235
- Компоненты используются в JSX как теги. Props передаются как атрибуты:
234
+ Components are used in JSX as tags. Props are passed as attributes:
236
235
 
237
236
  ```tsx
238
237
  import { View } from "@helfy/helfy";
@@ -250,9 +249,9 @@ class App {
250
249
  }
251
250
  ```
252
251
 
253
- ### Монтирование в DOM
252
+ ### DOM mounting
254
253
 
255
- Типичный bootstrap через `createApp()`: создаётся экземпляр, вызывается `attach(root)` внутри, хук `onAttached()` срабатывает после вставки в document.
254
+ Typical bootstrap is via `createApp()`: an instance is created, `attach(root)` is called internally, and the `onAttached()` hook runs after insertion into the document.
256
255
 
257
256
  ```typescript
258
257
  import { createApp } from "@helfy/helfy";
@@ -262,19 +261,19 @@ createApp({ root: document.getElementById("root")! })
262
261
  .mount(App);
263
262
  ```
264
263
 
265
- Для сценариев без роутера:
264
+ For scenarios without routing:
266
265
 
267
266
  ```typescript
268
267
  createApp({ root: document.getElementById("root")! }).mount(App);
269
268
  ```
270
269
 
271
- Ручное монтирование (без `createApp`): `const app = new App(); app.attach(root)`.
270
+ Manual mounting (without `createApp`): `const app = new App(); app.attach(root)`.
272
271
 
273
- ### Привязка к DOM и компонентам (@ref)
272
+ ### DOM and component refs (@ref)
274
273
 
275
- Декоратор `@ref` помечает поле для получения ссылки на DOM-элемент или экземпляр компонента. В JSX используется директива `@ref(this.имяПоля)`. Доступ к ссылке возможен после `onMount()`. Для операций вроде `focus()`, требующих элемент в document, используйте `onAttached()`.
274
+ The `@ref` decorator marks a field to receive a reference to a DOM element or component instance. In JSX use the `@ref(this.fieldName)` directive. The reference is available after `onMount()`. For operations like `focus()` that require the element in the document, use `onAttached()`.
276
275
 
277
- **Для компонентов**родитель получает прокси с доступом только к методам, помеченным `@expose`. Это позволяет явно определить публичный API компонента.
276
+ **For components**the parent receives a proxy with access only to methods marked with `@expose`. This lets you explicitly define the component's public API.
278
277
 
279
278
  ```tsx
280
279
  import { View, ref, expose } from "@helfy/helfy";
@@ -297,7 +296,7 @@ class Form {
297
296
  @ref private input!: Input;
298
297
 
299
298
  onAttached() {
300
- this.input.focus(); // доступен только помеченный метод
299
+ this.input.focus(); // only the exposed method is available
301
300
  }
302
301
 
303
302
  render() {
@@ -306,15 +305,15 @@ class Form {
306
305
  }
307
306
  ```
308
307
 
309
- - DOM-элементы по `@ref` передаются как есть
310
- - Компоненты с `@expose` — родитель видит только помеченные методы
311
- - Компоненты без `@expose` — передаётся экземпляр целиком (обратная совместимость)
308
+ - DOM elements via `@ref` are passed as-is
309
+ - Components with `@expose` — parent sees only the exposed methods
310
+ - Components without `@expose` — the full instance is passed (backward compatibility)
312
311
 
313
- При условном рендере (`@if`) ссылка обнуляется при удалении элемента. Запрещено использовать `@ref` и `@state` на одном поле.
312
+ With conditional rendering (`@if`), the ref is cleared when the element is removed. Do not use `@ref` and `@state` on the same field.
314
313
 
315
- ### Двустороннее связывание (@bind)
314
+ ### Two-way binding (@bind)
316
315
 
317
- Директива `@bind` синтаксический сахар для двусторонней привязки значения поля `@state` к элементу формы. Синтаксис: `@bind(expr)` — круглые скобки, как у `@ref`. Компилятор автоматически генерирует пару `value`/`checked` и обработчик события.
316
+ The `@bind` directive is syntactic sugar for two-way binding of a `@state` field to a form element. Syntax: `@bind(expr)` — parentheses, like `@ref`. The compiler generates the `value`/`checked` pair and event handler.
318
317
 
319
318
  ```tsx
320
319
  import { View, state } from "@helfy/helfy";
@@ -332,8 +331,8 @@ class LoginForm {
332
331
  <input @bind(this.email) type="email" />
333
332
  <input @bind(this.isActive) type="checkbox" />
334
333
  <select @bind(this.priority)>
335
- <option value="low">Низкий</option>
336
- <option value="high">Высокий</option>
334
+ <option value="low">Low</option>
335
+ <option value="high">High</option>
337
336
  </select>
338
337
  </form>
339
338
  );
@@ -341,24 +340,24 @@ class LoginForm {
341
340
  }
342
341
  ```
343
342
 
344
- Трансформация по типу элемента:
343
+ Transformation by element type:
345
344
 
346
- | Элемент | Атрибут | Событие |
347
- |---------|---------|---------|
345
+ | Element | Attribute | Event |
346
+ |---------|-----------|-------|
348
347
  | `input[text|email|password|...]` | `value` | `oninput` |
349
348
  | `input[checkbox|radio]` | `checked` | `onchange` |
350
349
  | `select` | `value` | `onchange` |
351
350
  | `textarea` | `value` | `oninput` |
352
351
 
353
- Поле должно быть помечено `@state` для реактивного обновления UI. Для union-типов (например, `TodoPriority`) используется приведение `as typeof expr` — типобезопасность сохраняется.
352
+ The field must be marked with `@state` for reactive UI updates. For union types (e.g. `TodoPriority`), use `as typeof expr` — type safety is preserved.
354
353
 
355
- **Кастомные компоненты:** для привязки к компоненту с пропом `value` используется именованный биндинг `@bind:value(expr)`:
354
+ **Custom components:** for binding to a component with a `value` prop, use named binding `@bind:value(expr)`:
356
355
 
357
356
  ```tsx
358
- // Родитель
359
- <Input @bind:value(this.title) placeholder="Заголовок" />
357
+ // Parent
358
+ <Input @bind:value(this.title) placeholder="Title" />
360
359
 
361
- // Компонент Input с @binded("value")
360
+ // Input component with @binded("value")
362
361
  @View
363
362
  class Input {
364
363
  @binded("value") private bindedVal!: string;
@@ -369,11 +368,11 @@ class Input {
369
368
  }
370
369
  ```
371
370
 
372
- ### Формы (@Form, @field, @field в JSX)
371
+ ### Forms (@Form, @field, @field in JSX)
373
372
 
374
- Централизованное управление формами с валидацией через контекст: `@Form` — класс контекста формы, `@field` — декоратор полей (создаёт `FieldState`), `@useForm` — инжект формы в компонент. JSX-директива `@field(expr)` подключает инпут к `FieldState` одной строкой.
373
+ Centralized form handling with validation via context: `@Form` — form context class, `@field` — field decorator (creates `FieldState`), `@useForm` — injects the form into a component. The JSX directive `@field(expr)` connects an input to `FieldState` in one line.
375
374
 
376
- **FormContext** — класс с `@Form` и полями `@field`:
375
+ **FormContext** — a class with `@Form` and `@field` fields:
377
376
 
378
377
  ```tsx
379
378
  import { Form, field, logger, type FieldState, type ILogger } from "@helfy/helfy";
@@ -392,7 +391,7 @@ export class LoginFormContext {
392
391
  rememberMe!: FieldState<boolean>;
393
392
 
394
393
  validateAll(): boolean {
395
- // проверка полей, установка field.error
394
+ // validate fields, set field.error
396
395
  return true;
397
396
  }
398
397
 
@@ -406,9 +405,9 @@ export class LoginFormContext {
406
405
  }
407
406
  ```
408
407
 
409
- **FieldState** содержит `value`, `isDirty`, `isTouched`, `error`, `isValid`. При изменении `value`/`error`/`isTouched` компоненты с `@useForm` перерендериваются.
408
+ **FieldState** has `value`, `isDirty`, `isTouched`, `error`, `isValid`. When `value`/`error`/`isTouched` changes, components with `@useForm` re-render.
410
409
 
411
- **Провайдер формы**родительский компонент оборачивает форму в контекст:
410
+ **Form provider**the parent component wraps the form in context:
412
411
 
413
412
  ```tsx
414
413
  // LoginPage.tsx
@@ -424,7 +423,7 @@ export class LoginPage {
424
423
  }
425
424
  ```
426
425
 
427
- **Компонент формы**инжект через `@useForm`, доступ к полям через `this.form`:
426
+ **Form component**inject via `@useForm`, access fields via `this.form`:
428
427
 
429
428
  ```tsx
430
429
  import { View, useForm } from "@helfy/helfy";
@@ -443,72 +442,72 @@ export class LoginForm {
443
442
  )}
444
443
  <input @field(this.form.password) type="password" class="input" />
445
444
  <input @field(this.form.rememberMe) type="checkbox" id="remember" />
446
- <label for="remember">Запомнить меня</label>
447
- <button type="submit">Войти</button>
445
+ <label for="remember">Remember me</label>
446
+ <button type="submit">Sign in</button>
448
447
  </form>
449
448
  );
450
449
  }
451
450
  }
452
451
  ```
453
452
 
454
- Компоненты-обёртки (TextField, CheckboxField и т.п.) принимают `field` как prop: `<TextField field={this.form.email} label="Email" />` и используют внутри `<input @field(this.props.field) />`.
453
+ Wrapper components (TextField, CheckboxField, etc.) accept `field` as a prop: `<TextField field={this.form.email} label="Email" />` and use `<input @field(this.props.field) />` internally.
455
454
 
456
- **JSX-директива `@field(expr)`** — одна директива заменяет `@bind` + `onblur` + `class` для ошибок + `aria-invalid`. Компилятор генерирует:
455
+ **JSX directive `@field(expr)`** — one directive replaces `@bind` + `onblur` + error `class` + `aria-invalid`. The compiler generates:
457
456
 
458
- - `value`/`checked` и `oninput`/`onchange`
457
+ - `value`/`checked` and `oninput`/`onchange`
459
458
  - `onblur` → `expr.isTouched = true`
460
- - `class` — объединение с существующим; при `expr.isTouched && expr.error` добавляется `input-error`
459
+ - `class` — merged with existing; when `expr.isTouched && expr.error` adds `input-error`
461
460
  - `aria-invalid={expr.isTouched && expr.error ? "true" : "false"}`
462
461
 
463
- Поддерживаются `input` (text, email, password, checkbox, radio), `select`, `textarea`. Класс `.input-error` можно задать в глобальных стилях (например, `@apply border-red-500` в Tailwind).
462
+ Supports `input` (text, email, password, checkbox, radio), `select`, `textarea`. The `.input-error` class can be defined in global styles (e.g. `@apply border-red-500` in Tailwind).
464
463
 
465
464
  ---
466
465
 
467
- ## Шаблонные директивы
466
+ ## Template directives
468
467
 
469
- Helfy расширяет JSX директивами `@if`, `@elseif`, `@else`, `@for`, `@empty`.
468
+ Helfy extends JSX with `@if`, `@elseif`, `@else`, `@for`, and `@empty` directives.
470
469
 
471
470
  ### @if / @elseif / @else
472
471
 
473
- Условный рендеринг:
472
+ Conditional rendering:
474
473
 
475
474
  ```tsx
476
475
  render() {
477
476
  return (
478
477
  <div>
479
478
  @if (this.count > 0) {
480
- <span>Положительное: {this.count}</span>
479
+ <span>Positive: {this.count}</span>
481
480
  }
482
481
  </div>
483
482
  );
484
483
  }
485
484
  ```
486
485
 
487
- Цепочка условий:
486
+ Condition chains:
488
487
 
489
488
  ```tsx
490
489
  render() {
491
490
  return (
492
491
  <div>
493
492
  @if (this.status === 'loading') {
494
- <span>Загрузка...</span>
493
+ <span>Loading...</span>
495
494
  } @elseif (this.status === 'error') {
496
- <span>Ошибка!</span>
495
+ <span>Error!</span>
497
496
  } @else {
498
- <span>Данные загружены</span>
497
+ <span>Data loaded</span>
499
498
  }
500
499
  </div>
501
500
  );
502
501
  }
503
502
  ```
504
503
 
505
- Вложенные условия:
504
+ Nested conditions:
506
505
 
507
506
  ```tsx
508
507
  @if (this.isVisible) {
509
508
  <div>
510
509
  @if (this.count > 10) {
511
- <span>Больше десяти</span>
510
+ <span>More than ten</span>
512
511
  }
513
512
  </div>
514
513
  }
@@ -516,7 +515,7 @@ render() {
516
515
 
517
516
  ### @for
518
517
 
519
- Итерация по массиву. Синтаксис: `@for (item of array)` или `@for (item, index of array)`.
518
+ Array iteration. Syntax: `@for (item of array)` or `@for (item, index of array)`.
520
519
 
521
520
  ```tsx
522
521
  @state private items = ['apple', 'banana', 'cherry'];
@@ -532,7 +531,7 @@ render() {
532
531
  }
533
532
  ```
534
533
 
535
- `track` задает ключ для оптимизации DOM-диффинга (аналог `key` в React):
534
+ `track` sets the key for DOM diffing optimization (like `key` in React):
536
535
 
537
536
  ```tsx
538
537
  @for (user of this.users; track user.id) {
@@ -542,13 +541,13 @@ render() {
542
541
 
543
542
  ### @empty
544
543
 
545
- Блок `@empty` после `@for` рендерится, когда массив пуст:
544
+ The `@empty` block after `@for` renders when the array is empty:
546
545
 
547
546
  ```tsx
548
547
  @for (item of this.items; track item) {
549
548
  <div>{item}</div>
550
549
  } @empty {
551
- <span>Список пуст</span>
550
+ <span>List is empty</span>
552
551
  }
553
552
  ```
554
553
 
@@ -556,9 +555,9 @@ render() {
556
555
 
557
556
  ## State Management
558
557
 
559
- ### Создание Store
558
+ ### Creating a Store
560
559
 
561
- Store -- глобальное реактивное хранилище. Создается классом с декоратором `@Store`, реактивные поля помечаются `@observable`:
560
+ A Store is a global reactive store. Create a class with the `@Store` decorator; reactive fields use `@observable`:
562
561
 
563
562
  ```typescript
564
563
  import { Store, observable } from "@helfy/helfy";
@@ -570,7 +569,7 @@ class UserStore {
570
569
 
571
570
  login(name: string) {
572
571
  this.name = name;
573
- this.isLoggedIn = true; // подписчики name и isLoggedIn уведомлены
572
+ this.isLoggedIn = true; // subscribers of name and isLoggedIn are notified
574
573
  }
575
574
 
576
575
  logout() {
@@ -582,15 +581,15 @@ class UserStore {
582
581
  export default UserStore;
583
582
  ```
584
583
 
585
- Декоратор `@Store` добавляет классу:
586
- - `subscribe(field, callback)` -- подписка на изменение поля, возвращает функцию отписки
587
- - `unsubscribe(field, callback)` -- отписка
584
+ The `@Store` decorator adds:
585
+ - `subscribe(field, callback)` subscribe to field changes, returns unsubscribe function
586
+ - `unsubscribe(field, callback)` unsubscribe
588
587
 
589
- Декоратор `@observable` превращает поле в реактивное свойство с getter/setter. При записи нового значения все подписчики уведомляются.
588
+ The `@observable` decorator turns a field into a reactive property with getter/setter. On write, all subscribers are notified.
590
589
 
591
- ### Подписка через @observe
590
+ ### Subscribing with @observe
592
591
 
593
- Декоратор `@observe` связывает поле компонента с полем store. При изменении значения в store компонент автоматически перерендерится:
592
+ The `@observe` decorator binds a component field to a store field. When the store value changes, the component re-renders:
594
593
 
595
594
  ```tsx
596
595
  import { View, observe } from "@helfy/helfy";
@@ -609,9 +608,9 @@ class Header {
609
608
  return (
610
609
  <header>
611
610
  @if (this.isLoggedIn) {
612
- <span>Привет, {this.userName}!</span>
611
+ <span>Hello, {this.userName}!</span>
613
612
  } @else {
614
- <span>Войдите в систему</span>
613
+ <span>Sign in</span>
615
614
  }
616
615
  </header>
617
616
  );
@@ -619,11 +618,11 @@ class Header {
619
618
  }
620
619
  ```
621
620
 
622
- Тип поля берется из store через lookup-тип (`UserStore['name']`), что дает полную типобезопасность.
621
+ The field type is inferred from the store via lookup type (`UserStore['name']`), giving full type safety.
623
622
 
624
- ### Контекст хранилищ
623
+ ### Store context
625
624
 
626
- Рекомендуется создать единый контекст для всех store:
625
+ Create a single context for all stores:
627
626
 
628
627
  ```typescript
629
628
  import UserStore from "./UserStore";
@@ -637,26 +636,26 @@ class Stores {
637
636
  export default Stores;
638
637
  ```
639
638
 
640
- Использование в компонентах:
639
+ Usage in components:
641
640
 
642
641
  ```tsx
643
642
  import Stores from "./StoreContext";
644
643
 
645
- // подписка через декоратор
644
+ // subscribe via decorator
646
645
  @observe(Stores.userStore, 'name')
647
646
  private userName: string;
648
647
 
649
- // прямой вызов метода store
648
+ // direct store method call
650
649
  Stores.userStore.login("Alice");
651
650
  ```
652
651
 
653
652
  ---
654
653
 
655
- ## Логирование (@logger)
654
+ ## Logging (@logger)
656
655
 
657
- Декоратор `@logger` инжектирует логгер в классы View, Context, Store и Injectable. Имя класса подставляется на этапе компиляции (Babel-плагин), что сохраняет читаемые имена даже при минификации.
656
+ The `@logger` decorator injects a logger into View, Context, Store, and Injectable classes. The class name is set at compile time (Babel plugin), keeping readable names even after minification.
658
657
 
659
- ### Использование
658
+ ### Usage
660
659
 
661
660
  ```tsx
662
661
  import { View, logger, type ILogger } from "@helfy/helfy";
@@ -673,22 +672,22 @@ class TodoInput {
673
672
  }
674
673
  ```
675
674
 
676
- **Варианты:**
677
- - `@logger()` — имя класса подставляется на compile-time как `<ClassName>`, формат и цвет зависят от типа класса
678
- - `@logger("my-tag")` — кастомный тег, серый цвет
675
+ **Variants:**
676
+ - `@logger()` — class name is set at compile-time as `<ClassName>`, format and color depend on class type
677
+ - `@logger("my-tag")` — custom tag, gray color
679
678
 
680
- ### Формат и цвет тегов по типу класса
679
+ ### Tag format and color by class type
681
680
 
682
- | Тип | Формат | Цвет |
683
- |-----|--------|------|
684
- | View (компоненты) | `<TodoInput>` | skyblue |
685
- | Injectable (сервисы) | `TodoValidateService()` | pink |
681
+ | Type | Format | Color |
682
+ |------|--------|-------|
683
+ | View (components) | `<TodoInput>` | skyblue |
684
+ | Injectable (services) | `TodoValidateService()` | pink |
686
685
  | Context | `{TodoContext}` | khaki |
687
686
  | Form | `[LoginFormContext]` | bright blue |
688
687
  | Store | `TodoStore[]` | lime green |
689
- | Кастомный `@logger("...")` | как указано | gray |
688
+ | Custom `@logger("...")` | as specified | gray |
690
689
 
691
- ### API логгера
690
+ ### Logger API
692
691
 
693
692
  ```typescript
694
693
  interface ILogger {
@@ -696,13 +695,13 @@ interface ILogger {
696
695
  info(message: string, meta?: Record<string, unknown>): void;
697
696
  warn(message: string, meta?: Record<string, unknown>): void;
698
697
  error(messageOrError: string | Error, meta?: Record<string, unknown>): void;
699
- withContext(ctx: string): ILogger; // дочерний логгер с доп. префиксом
698
+ withContext(ctx: string): ILogger; // child logger with extra prefix
700
699
  }
701
700
  ```
702
701
 
703
- ### DI и транспорты
702
+ ### DI and transports
704
703
 
705
- `LoggerService` регистрируется под `ILoggerToken` при `registerAllServices`. Можно подключить кастомные транспорты (Sentry, файл и т.д.), реализующие `ILogTransport`:
704
+ `LoggerService` is registered under `ILoggerToken` via `registerAllServices`. Custom transports (Sentry, file, etc.) implementing `ILogTransport` can be wired up:
706
705
 
707
706
  ```typescript
708
707
  import { LoggerService, ConsoleTransport, ILoggerToken } from "@helfy/helfy";
@@ -715,17 +714,17 @@ createApp({ root })
715
714
  .mount(App);
716
715
  ```
717
716
 
718
- Без зарегистрированного логгера в контейнере используется fallback с выводом в `console`.
717
+ Without a registered logger in the container, a fallback that logs to `console` is used.
719
718
 
720
719
  ---
721
720
 
722
721
  ## Context & DI
723
722
 
724
- Helfy поддерживает иерархический Context и Dependency Injection: провайдер оборачивает поддерево в JSX, а дочерние компоненты получают значения через `@ctx`. Это удобно для темы, форм, роутинга и других общих зависимостей.
723
+ Helfy supports hierarchical Context and Dependency Injection: a provider wraps a subtree in JSX, and child components receive values via `@ctx`. Useful for theme, forms, routing, and other shared dependencies.
725
724
 
726
- ### Провайдер (@Context, @provide)
725
+ ### Provider (@Context, @provide)
727
726
 
728
- Класс с декоратором `@Context` не-рендерящий провайдер: он не имеет `render()` и рендерит только своих детей. Поля с `@provide` становятся доступны потребителям ниже по дереву.
727
+ A class with the `@Context` decorator is a non-rendering provider: it has no `render()` and only renders its children. Fields with `@provide` are available to consumers down the tree.
729
728
 
730
729
  ```tsx
731
730
  import { Context, provide } from "@helfy/helfy";
@@ -744,7 +743,7 @@ export class ThemeContext {
744
743
  }
745
744
  ```
746
745
 
747
- Использование в JSX — обёртка вокруг поддерева:
746
+ Usage in JSX — wrap a subtree:
748
747
 
749
748
  ```tsx
750
749
  <ThemeContext>
@@ -753,11 +752,11 @@ export class ThemeContext {
753
752
  </ThemeContext>
754
753
  ```
755
754
 
756
- Фреймворк рендерит только детей `ThemeContext`; сам провайдер не создаёт DOM-узлов.
755
+ The framework only renders the children of `ThemeContext`; the provider itself does not create DOM nodes.
757
756
 
758
- ### Потребитель (@ctx)
757
+ ### Consumer (@ctx)
759
758
 
760
- Компонент с `@View` может получать значение контекста через `@ctx`:
759
+ A component with `@View` can receive context values via `@ctx`:
761
760
 
762
761
  ```tsx
763
762
  import { View, ctx } from "@helfy/helfy";
@@ -771,14 +770,14 @@ class ThemeToggle {
771
770
  render() {
772
771
  return (
773
772
  <button onclick={this.theme.toggle}>
774
- {this.theme.mode === "dark" ? "Светлая тема" : "Тёмная тема"}
773
+ {this.theme.mode === "dark" ? "Light theme" : "Dark theme"}
775
774
  </button>
776
775
  );
777
776
  }
778
777
  }
779
778
  ```
780
779
 
781
- Можно инжектировать только поле контекста:
780
+ You can inject only a context field:
782
781
 
783
782
  ```tsx
784
783
  @View
@@ -787,18 +786,18 @@ class ModeDisplay {
787
786
  private mode!: TThemeMode;
788
787
 
789
788
  render() {
790
- return <span>Текущая тема: {this.mode}</span>;
789
+ return <span>Current theme: {this.mode}</span>;
791
790
  }
792
791
  }
793
792
  ```
794
793
 
795
- Поиск провайдера идёт вверх по дереву (`_parentView`); используется ближайший провайдер с нужным ключом.
794
+ Provider lookup goes up the tree (`_parentView`); the nearest provider with the matching key is used.
796
795
 
797
- ### Реактивные поля
796
+ ### Reactive fields
798
797
 
799
- `@provide({ reactive: true })` делает поле реактивным: при изменении все потребители перерендериваются. Для методов достаточно `@provide()` без `reactive`.
798
+ `@provide({ reactive: true })` makes a field reactive: when it changes, all consumers re-render. For methods, `@provide()` without `reactive` is enough.
800
799
 
801
- **Вычисляемые поля**геттеры с `@provide({ computed: true, deps: ["field1", "field2"] })` пересчитываются при изменении зависимостей и триггерят перерендер потребителей:
800
+ **Computed fields**getters with `@provide({ computed: true, deps: ["field1", "field2"] })` recompute when dependencies change and trigger consumer re-renders:
802
801
 
803
802
  ```tsx
804
803
  @provide({ computed: true, deps: ["todos", "filter"] })
@@ -809,9 +808,9 @@ get filteredTodos(): Todo[] {
809
808
  }
810
809
  ```
811
810
 
812
- ### Опциональная инжекция
811
+ ### Optional injection
813
812
 
814
- Третий аргумент `@ctx` — опции `{ optional?, defaultValue? }`:
813
+ The third argument of `@ctx` — options `{ optional?, defaultValue? }`:
815
814
 
816
815
  ```tsx
817
816
  @ctx(ThemeContext, { optional: true })
@@ -821,15 +820,15 @@ private theme?: ThemeContext;
821
820
  private mode = "light";
822
821
  ```
823
822
 
824
- Без провайдера в дереве `optional: true` даёт `undefined`; `defaultValue` используется, когда провайдера нет.
823
+ With no provider in the tree, `optional: true` yields `undefined`; `defaultValue` is used when the provider is absent.
825
824
 
826
- ### DI Container (конструкторный инжект)
825
+ ### DI Container (constructor injection)
827
826
 
828
- Helfy поддерживает DI-контейнер для глобальных сервисов с **compile-time** магией: сервисы помечаются `@Injectable<IX>()`, потребители указывают только тип в конструкторебез `@inject` и явных токенов.
827
+ Helfy supports a DI container for global services with **compile-time** magic: services are marked with `@Injectable<IX>()`, consumers only specify the type in the constructor no `@inject` or explicit tokens.
829
828
 
830
- **Конвенция:** последний параметр конструктора props от родителя; предшествующие параметры резолвятся из контейнера или из @Context по дереву.
829
+ **Convention:** the last constructor parameter is props from the parent; preceding parameters are resolved from the container or from @Context up the tree.
831
830
 
832
- **Сервис**обязательно `@Injectable<IX>()` (интерфейс в generic):
831
+ **Service**must use `@Injectable<IX>()` (interface in generic):
833
832
 
834
833
  ```typescript
835
834
  export interface ITodoValidateService {
@@ -842,7 +841,7 @@ export class TodoValidateService implements ITodoValidateService {
842
841
  }
843
842
  ```
844
843
 
845
- **Потребитель**только тип в конструкторе (плагин подставляет токен):
844
+ **Consumer**only the type in the constructor (plugin injects the token):
846
845
 
847
846
  ```tsx
848
847
  @View
@@ -854,7 +853,7 @@ class TodoInput {
854
853
  }
855
854
  ```
856
855
 
857
- **Bootstrap** — регистрация DI подключается автоматически при `createApp` (плагин подставляет `.useDI(registerAllServices)` в цепочку):
856
+ **Bootstrap** — DI registration is attached automatically at `createApp` (plugin injects `.useDI(registerAllServices)` in the chain):
858
857
 
859
858
  ```typescript
860
859
  createApp({ root: document.getElementById("root")! })
@@ -862,9 +861,9 @@ createApp({ root: document.getElementById("root")! })
862
861
  .mount(App);
863
862
  ```
864
863
 
865
- Сканер вызывается Babel-плагином перед трансформацией, находит все `@Injectable<IX>()`, генерирует `.helfy/di-tokens.ts` и `.helfy/di-registry.ts`.
864
+ The scanner is run by the Babel plugin before transformation, finds all `@Injectable<IX>()`, and generates `.helfy/di-tokens.ts` and `.helfy/di-registry.ts`.
866
865
 
867
- **Fallback** — для ручной настройки доступны `.configureContainer()`, `@inject(token)`, `@Injectable(token)`:
866
+ **Fallback** — for manual setup: `.configureContainer()`, `@inject(token)`, `@Injectable(token)`:
868
867
 
869
868
  ```typescript
870
869
  createApp({ root })
@@ -876,38 +875,38 @@ createApp({ root })
876
875
  .mount(App);
877
876
  ```
878
877
 
879
- Опционально: передать свой контейнер через `.container(myContainer)` или `createApp({ root, container })`.
878
+ Optionally: pass a custom container via `.container(myContainer)` or `createApp({ root, container })`.
880
879
 
881
- `@Context` и контейнер сосуществуют: Context для tree-scoped значений (тема, роутер), Container для глобальных сервисов.
880
+ `@Context` and the container coexist: Context is for tree-scoped values (theme, router), Container for global services.
882
881
 
883
882
  ---
884
883
 
885
884
  ## JSX
886
885
 
887
- Helfy использует кастомный JSX runtime (`@helfy/helfy/jsx-runtime`). JSX транслируется в вызовы `jsx()` / `jsxs()`, которые строят виртуальное представление DOM.
886
+ Helfy uses a custom JSX runtime (`@helfy/helfy/jsx-runtime`). JSX is translated to `jsx()` / `jsxs()` calls that build a virtual DOM representation.
888
887
 
889
- ### Атрибуты
888
+ ### Attributes
890
889
 
891
- Стандартные HTML-атрибуты передаются напрямую:
890
+ Standard HTML attributes are passed through:
892
891
 
893
892
  ```tsx
894
- <input type="text" placeholder="Введите имя" />
893
+ <input type="text" placeholder="Enter name" />
895
894
  <img src="/logo.png" alt="Logo" />
896
895
  <div id="container" class="wrapper"></div>
897
896
  ```
898
897
 
899
- ### События
898
+ ### Events
900
899
 
901
- Обработчики событий передаются через атрибуты в **нижнем регистре** (`onclick`, `oninput`, а не `onClick`):
900
+ Event handlers use **lowercase** attributes (`onclick`, `oninput`, not `onClick`):
902
901
 
903
902
  ```tsx
904
903
  <button onclick={() => this.increment()}>+</button>
905
904
  <input oninput={(e) => this.handleInput(e)} />
906
905
  ```
907
906
 
908
- ### Стили
907
+ ### Styles
909
908
 
910
- Инлайн-стили передаются объектом с camelCase-ключами:
909
+ Inline styles use an object with camelCase keys:
911
910
 
912
911
  ```tsx
913
912
  <div style={{
@@ -916,26 +915,26 @@ Helfy использует кастомный JSX runtime (`@helfy/helfy/jsx-run
916
915
  padding: '8px 16px',
917
916
  borderRadius: '4px'
918
917
  }}>
919
- Стилизованный блок
918
+ Styled block
920
919
  </div>
921
920
  ```
922
921
 
923
- ### CSS-классы
922
+ ### CSS classes
924
923
 
925
- Атрибут `class` принимает строку или массив с условными классами:
924
+ The `class` attribute accepts a string or an array with conditional classes:
926
925
 
927
926
  ```tsx
928
- // строка
927
+ // string
929
928
  <div class="container">...</div>
930
929
 
931
930
  // CSS Modules
932
931
  import styles from './App.module.css';
933
932
  <div class={styles.wrapper}>...</div>
934
933
 
935
- // условные классы через массив
934
+ // conditional classes via array
936
935
  <div class={[
937
936
  styles.cell,
938
- [styles.active, this.isActive], // применяется если this.isActive === true
937
+ [styles.active, this.isActive], // applied when this.isActive === true
939
938
  [styles.disabled, this.isDisabled],
940
939
  ]}>...</div>
941
940
  ```
@@ -944,33 +943,33 @@ import styles from './App.module.css';
944
943
 
945
944
  ## API
946
945
 
947
- ### Декораторы
948
-
949
- | Декоратор | Область | Описание |
950
- |-----------|---------|----------|
951
- | `@View` | Класс | Превращает класс в компонент с `render()`, `view`, `update()` |
952
- | `@state` | Поле | Локальное состояние компонента на базе signals (запись обновляет только этот компонент) |
953
- | `@Store` | Класс | Добавляет `subscribe`/`unsubscribe` и реактивность `@observable`-полей |
954
- | `@observable` | Поле | Делает поле store реактивным -- запись уведомляет подписчиков |
955
- | `@observe(store, field)` | Поле | Связывает поле компонента с полем store |
956
- | `@Context` | Класс | Не-рендерящий провайдер контекста; рендерит только детей |
957
- | `@provide()` / `@provide({ reactive: true })` / `@provide({ computed: true, deps })` | Поле | Помечает поле как доступное для `@ctx`. `reactive` — потребители перерендериваются при изменении; `computed` + `deps` — для геттеров |
958
- | `@ctx(ContextClass)` / `@ctx(ContextClass, field)` | Поле | Инжектирует контекст или поле контекста из ближайшего провайдера вверх по дереву |
959
- | `@Injectable<IX>()` / `@Injectable<IX>('singleton' \| 'transient' \| 'scoped')` / `@Injectable(token)` | Класс | Помечает класс как injectable. Scope в (): `'singleton'` (по умолчанию), `'transient'`, `'scoped'`. Fallback: `@Injectable(token)` с Symbol/строкой |
960
- | `@inject(token)` | Параметр конструктора | Задаёт токен для параметра (fallback; при `@Injectable<IX>()` плагин подставляет токен автоматически) |
961
- | `@logger()` / `@logger("tag")` | Поле | Инжектирует ILogger. Без аргумента — compile-time имя класса и цвет по типу (View/Context/Store/Injectable). С аргументомкастомный тег (серый) |
962
- | `@ref` | Поле | Помечает поле для получения ссылки на DOM или компонент (использовать с `@ref(this.имяПоля)` в JSX) |
963
- | `@expose` | Метод | Делает метод доступным родителю при `@ref` на компонент (без `@expose` родитель получает экземпляр целиком) |
964
- | `@binded(name)` | Поле | Связывает поле с биндингом `@bind:name` от родителя (для кастомных компонентов) |
965
- | `@bind(expr)` / `@bind:name(expr)` | JSX | Двустороннее связывание с `@state` полем (value/checked + oninput/onchange). Для компонентов: `@bind:value(expr)` |
966
- | `@Form` | Класс | Контекст формы с полями `@field` |
967
- | `@field(options)` | Поле FormContext | Создаёт FieldState для поля формы (value, isTouched, error, isDirty) |
968
- | `@useForm(FormContext)` | Поле | Инжектирует форму в компонент с подпиской на изменения полей |
969
- | `@field(expr)` | JSX | Подключает инпут к FieldState: value/checked + onblur + error class + aria-invalid |
970
-
971
- ### Роутинг (SPA)
972
-
973
- Helfy включает лёгкий SPA‑роутер. При вызове `createApp().router({ routes }).mount(App)` фреймворк автоматически оборачивает приложение в `RouterContext`, поэтому свой App не нужно оборачивать вручную:
946
+ ### Decorators
947
+
948
+ | Decorator | Scope | Description |
949
+ |-----------|-------|-------------|
950
+ | `@View` | Class | Turns a class into a component with `render()`, `view`, `update()` |
951
+ | `@state` | Field | Component local state on signals (write updates only this component) |
952
+ | `@Store` | Class | Adds `subscribe`/`unsubscribe` and `@observable` field reactivity |
953
+ | `@observable` | Field | Makes a store field reactive write notifies subscribers |
954
+ | `@observe(store, field)` | Field | Binds a component field to a store field |
955
+ | `@Context` | Class | Non-rendering context provider; renders only children |
956
+ | `@provide()` / `@provide({ reactive: true })` / `@provide({ computed: true, deps })` | Field | Marks a field as available to `@ctx`. `reactive` — consumers re-render on change; `computed` + `deps` — for getters |
957
+ | `@ctx(ContextClass)` / `@ctx(ContextClass, field)` | Field | Injects context or a context field from the nearest provider up the tree |
958
+ | `@Injectable<IX>()` / `@Injectable<IX>('singleton' \| 'transient' \| 'scoped')` / `@Injectable(token)` | Class | Marks a class as injectable. Scope in (): `'singleton'` (default), `'transient'`, `'scoped'`. Fallback: `@Injectable(token)` with Symbol/string |
959
+ | `@inject(token)` | Constructor param | Sets the token for the parameter (fallback; with `@Injectable<IX>()` the plugin injects it automatically) |
960
+ | `@logger()` / `@logger("tag")` | Field | Injects ILogger. No arg — compile-time class name and color by type (View/Context/Store/Injectable). With argcustom tag (gray) |
961
+ | `@ref` | Field | Marks a field to receive a DOM or component reference (use with `@ref(this.fieldName)` in JSX) |
962
+ | `@expose` | Method | Makes a method available to the parent when using `@ref` on a component (without `@expose` the parent gets the full instance) |
963
+ | `@binded(name)` | Field | Binds a field to `@bind:name` from the parent (for custom components) |
964
+ | `@bind(expr)` / `@bind:name(expr)` | JSX | Two-way binding with `@state` field (value/checked + oninput/onchange). For components: `@bind:value(expr)` |
965
+ | `@Form` | Class | Form context with `@field` fields |
966
+ | `@field(options)` | FormContext field | Creates FieldState for a form field (value, isTouched, error, isDirty) |
967
+ | `@useForm(FormContext)` | Field | Injects the form into a component with subscription to field changes |
968
+ | `@field(expr)` | JSX | Connects an input to FieldState: value/checked + onblur + error class + aria-invalid |
969
+
970
+ ### Routing (SPA)
971
+
972
+ Helfy includes a lightweight SPA router. When you call `createApp().router({ routes }).mount(App)`, the framework automatically wraps the app in `RouterContext`, so you don't need to wrap your App manually:
974
973
 
975
974
  ```tsx
976
975
  // index.ts
@@ -1012,15 +1011,15 @@ class Sidebar {
1012
1011
 
1013
1012
  return (
1014
1013
  <nav>
1015
- <Link to="/" label="Главная" class={isHome ? "font-bold" : ""} />
1016
- <Link to="/analytics" label="Аналитика" class={isAnalytics ? "font-bold" : ""} />
1014
+ <Link to="/" label="Home" class={isHome ? "font-bold" : ""} />
1015
+ <Link to="/analytics" label="Analytics" class={isAnalytics ? "font-bold" : ""} />
1017
1016
  </nav>
1018
1017
  );
1019
1018
  }
1020
1019
  }
1021
1020
  ```
1022
1021
 
1023
- Типичный компонент‑страница может использовать утилитарные декораторы роутера:
1022
+ A typical page component can use router decorators:
1024
1023
 
1025
1024
  ```tsx
1026
1025
  import { View, path, search, params, router, type RouterAPI } from "@helfy/helfy";
@@ -1047,7 +1046,7 @@ class DebugPage {
1047
1046
  <pre>params: {JSON.stringify(this.routeParams)}</pre>
1048
1047
  <pre>query: {JSON.stringify(this.query)}</pre>
1049
1048
  <button onclick={() => this.rtr.push("/analytics")}>
1050
- Перейти в аналитику
1049
+ Go to analytics
1051
1050
  </button>
1052
1051
  </section>
1053
1052
  );
@@ -1055,7 +1054,7 @@ class DebugPage {
1055
1054
  }
1056
1055
  ```
1057
1056
 
1058
- **Кастомная страница 404.** Оберните `RouterView` и переопределите слот `notFound`:
1057
+ **Custom 404 page.** Wrap `RouterView` and override the `notFound` slot:
1059
1058
 
1060
1059
  ```tsx
1061
1060
  @View
@@ -1070,35 +1069,35 @@ class AppRouter {
1070
1069
  );
1071
1070
  }
1072
1071
  }
1073
- ```
1072
+ ```
1074
1073
 
1075
- ### Жизненный цикл компонента
1074
+ ### Component lifecycle
1076
1075
 
1077
- 1. `constructor(props)` -- создание экземпляра, `this.props` оборачивается в реактивный Proxy
1078
- 2. `render()` -- возврат JSX (**вызывается один раз** при монтировании; Babel-плагин оборачивает выражения в `createReactiveChild` для fine-grained обновлений)
1079
- 3. `mount()` -- первичный рендер JSX в DOM-фрагмент
1080
- 4. `onMount()` -- хук после первого монтирования (опциональный)
1081
- 5. `onAttached()` -- хук после вставки в document (опциональный). Вызывается при `attach(parent)`.
1082
- 6. `update()` -- структурное обновление (при смене корневого компонента, например при роутинге). Для одного и того же дочернего компонента обновляются только signals через `updateProps`
1083
- 7. `updateProps(newProps)` -- обновление prop-signals через `batch()` (fine-grained, без полного перерендера)
1076
+ 1. `constructor(props)` instance creation, `this.props` wrapped in reactive Proxy
1077
+ 2. `render()` returns JSX (**called once** on mount)
1078
+ 3. `mount()` initial JSX render to DOM fragment
1079
+ 4. `onMount()` hook after first mount (optional)
1080
+ 5. `onAttached()` hook after insertion into document (optional). Called on `attach(parent)`.
1081
+ 6. `update()` structural update (e.g. on route change). For the same child component, only signals are updated via `updateProps`
1082
+ 7. `updateProps(newProps)` updates prop signals (fine-grained, no full re-render)
1084
1083
 
1085
1084
  #### onMount vs onAttached
1086
1085
 
1087
1086
  | | `onMount()` | `onAttached()` |
1088
1087
  |---|---|---|
1089
- | **Когда** | Сразу после `mount()`, дерево построено, рефы назначены | После `attach(parent)`, элемент в document |
1090
- | **Элемент в document** | Может быть ещё нет (корень в fragment) | Да |
1091
- | **Рефы** | Доступны | Доступны |
1088
+ | **When** | Right after `mount()`, tree built, refs assigned | After `attach(parent)`, element in document |
1089
+ | **Element in document** | May not be yet (root in fragment) | Yes |
1090
+ | **Refs** | Available | Available |
1092
1091
 
1093
- **`onMount()`** — для инициализации, не требующей document:
1094
- - Подписки на store/observable/сервисы
1095
- - Настройка внутреннего состояния
1096
- - Добавление обработчиков (работают и на detached-узлах)
1092
+ **`onMount()`** — for initialization that doesn't need the document:
1093
+ - Subscriptions to store/observable/services
1094
+ - Internal state setup
1095
+ - Adding handlers (works on detached nodes too)
1097
1096
 
1098
- **`onAttached()`** — для операций, требующих элемент в document:
1097
+ **`onAttached()`** — for operations that require the element in the document:
1099
1098
  - `focus()`, `scrollIntoView()`
1100
- - `getBoundingClientRect()`, замер layout
1101
- - Любые DOM API, работающие только с подключённым узлом
1099
+ - `getBoundingClientRect()`, layout measurement
1100
+ - Any DOM API that only works on attached nodes
1102
1101
 
1103
1102
  ```tsx
1104
1103
  @View
@@ -1106,22 +1105,22 @@ class SearchInput {
1106
1105
  @ref private input!: HTMLInputElement;
1107
1106
 
1108
1107
  onMount() {
1109
- this.store.subscribe(this.handleChange); // подписка — document не нужен
1108
+ this.store.subscribe(this.handleChange); // subscription — document not needed
1110
1109
  }
1111
1110
 
1112
1111
  onAttached() {
1113
- this.input.focus(); // фокуснужен document
1112
+ this.input.focus(); // focusneeds document
1114
1113
  }
1115
1114
  }
1116
1115
  ```
1117
1116
 
1118
- ### Слоты (content projection)
1117
+ ### Slots (content projection)
1119
1118
 
1120
- Helfy поддерживает именованные слоты с fallback‑содержимым и переопределением в дочерних компонентах.
1119
+ Helfy supports named slots with fallback content and override in child components.
1121
1120
 
1122
- #### Провайдер слота (`@View`‑компонент)
1121
+ #### Slot provider (`@View` component)
1123
1122
 
1124
- Слоты объявляются прямо в JSX через директиву `@slot:имя(...)` внутри `render()`:
1123
+ Slots are declared in JSX via the `@slot:name(...)` directive inside `render()`:
1125
1124
 
1126
1125
  ```tsx
1127
1126
  import { View } from "@helfy/helfy";
@@ -1131,24 +1130,24 @@ class AppLayout {
1131
1130
  render() {
1132
1131
  return (
1133
1132
  <section class="layout">
1134
- {/* Именованный слот header с fallback-разметкой */}
1135
- @slot:header({ title: "Список задач" }) fallback {
1133
+ {/* Named slot header with fallback markup */}
1134
+ @slot:header({ title: "Task list" }) fallback {
1136
1135
  <header class="mb-4">
1137
1136
  <h1 class="text-2xl font-bold text-gray-900">
1138
- Список задач
1137
+ Task list
1139
1138
  </h1>
1140
1139
  </header>
1141
1140
  }
1142
1141
 
1143
- {/* Именованный слот content с fallback и директивой @if внутри */}
1142
+ {/* Named slot content with fallback and @if inside */}
1144
1143
  @slot:content({ store: this.props.store, filtered: this.props.filtered, hasTodos: this.props.hasTodos }) fallback {
1145
1144
  @if (this.props.hasTodos) {
1146
1145
  <section class="pt-3 border-t border-gray-200 text-sm text-gray-600">
1147
1146
  <p class="mb-1">
1148
- Всего задач: {this.props.store.todos.length}, активных: {this.props.store.activeCount},
1149
- завершённых: {this.props.store.completedCount}
1147
+ Total: {this.props.store.todos.length}, active: {this.props.store.activeCount},
1148
+ completed: {this.props.store.completedCount}
1150
1149
  </p>
1151
- <p>Фильтрованных к показу: {this.props.filtered.length}</p>
1150
+ <p>Filtered: {this.props.filtered.length}</p>
1152
1151
  </section>
1153
1152
  }
1154
1153
  }
@@ -1158,15 +1157,15 @@ class AppLayout {
1158
1157
  }
1159
1158
  ```
1160
1159
 
1161
- Правила для провайдера:
1160
+ Provider rules:
1162
1161
 
1163
- - `@slot:header({ ... })` — объявляет именованный слот `header` и вызывает его.
1164
- - Блок `fallback { ... }` (опциональный) задаёт разметку по умолчанию, если слот не переопределён.
1165
- - Внутри `fallback` можно использовать директивы `@if`, `@for` и обычный JSX.
1162
+ - `@slot:header({ ... })` — declares the named slot `header` and invokes it.
1163
+ - The `fallback { ... }` block (optional) defines default markup when the slot is not overridden.
1164
+ - Inside `fallback` you can use `@if`, `@for`, and regular JSX.
1166
1165
 
1167
- #### Потребитель слота (override в JSX)
1166
+ #### Slot consumer (override in JSX)
1168
1167
 
1169
- Переопределение слота в дочернем компоненте делается через `@slot.имя(...) { ... }` внутри JSX‑детей:
1168
+ Override a slot in a child component via `@slot.name(...) { ... }` inside JSX children:
1170
1169
 
1171
1170
  ```tsx
1172
1171
  @View
@@ -1182,20 +1181,20 @@ class TodoApp {
1182
1181
  filtered={filtered}
1183
1182
  hasTodos={this.hasTodos}
1184
1183
  >
1185
- {/* Переопределяем слот header */}
1184
+ {/* Override header slot */}
1186
1185
  @slot.header({ title }) {
1187
1186
  <header class="mb-5">
1188
1187
  <h1 class="mb-4 text-2xl font-bold text-gray-900">
1189
- Задачи ({title})
1188
+ Tasks ({title})
1190
1189
  </h1>
1191
1190
  <TodoInput
1192
- placeholder="Добавить задачу…"
1191
+ placeholder="Add task…"
1193
1192
  onSubmit={(text) => store.add(text)}
1194
1193
  />
1195
1194
  </header>
1196
1195
  }
1197
1196
 
1198
- {/* Переопределяем слот content, внутри можно использовать @if/@for */}
1197
+ {/* Override content slot, @if/@for allowed inside */}
1199
1198
  @slot.content({ store, filtered, hasTodos }) {
1200
1199
  @if (hasTodos) {
1201
1200
  <section class="pt-3 border-t border-gray-200">
@@ -1221,8 +1220,8 @@ class TodoApp {
1221
1220
  }
1222
1221
  ```
1223
1222
 
1224
- Кратко по синтаксису:
1223
+ Syntax summary:
1225
1224
 
1226
- - `@slot:имя({ props }) fallback { FallbackJSX }` — объявление и вызов слота в провайдере.
1227
- - `@slot.имя({ ctx }) { OverrideJSX }` — переопределение слота в потребителе.
1228
- - Внутри `FallbackJSX` и `OverrideJSX` поддерживаются все директивы (`@if`, `@for`, `@empty`) и обычный JSX.
1225
+ - `@slot:name({ props }) fallback { FallbackJSX }` — declare and invoke the slot in the provider.
1226
+ - `@slot.name({ ctx }) { OverrideJSX }` — override the slot in the consumer.
1227
+ - All directives (`@if`, `@for`, `@empty`) and regular JSX work inside `FallbackJSX` and `OverrideJSX`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helfy/helfy",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "TypeScript UI framework with decorator API, custom JSX and reactive state management",
5
5
  "author": "ikrymsaev",
6
6
  "license": "MIT",