Обновить README.md

This commit is contained in:
svsptech 2026-05-18 16:10:42 +05:00
parent 6b5296f533
commit b744e2b193

281
README.md
View File

@ -1,3 +1,280 @@
# dom.js # dom.js — декларативное создание DOM с реактивностью на сигналах
Библиотека для быстрого и удобного построения динамических DOM элементов Библиотека предоставляет текучий (fluent) API для создания DOM-элементов и реактивного управления ими через собственную систему сигналов.
## Установка
```html
<script src="path/to/dom.js"></script>
```
## Быстрый старт
```javascript
// Создать кнопку с реактивным текстом
const count = dom.signal(0);
const button = dom.button('counter')
.cls('btn btn-primary')
.text(count)
.on('click', () => count(count() + 1))
.done();
document.body.appendChild(button);
```
## API
### Сигналы
Сигнал — реактивный контейнер для значения. При изменении значения все подписчики автоматически уведомляются.
```javascript
// Создание сигнала
const name = dom.signal('John');
// Чтение значения
console.log(name()); // 'John'
console.log(name.value); // 'John'
// Изменение значения
name('Jane');
name.value = 'Jane';
// Подписка на изменения
const unsubscribe = name.on((newVal, oldVal) => {
console.log(`${oldVal} → ${newVal}`);
});
// Отписка
unsubscribe();
```
#### Производные сигналы `.map()`
Создаёт новый сигнал, значение которого автоматически обновляется при изменении родительского:
```javascript
const count = dom.signal(1);
const doubled = count.map(x => x * 2);
console.log(doubled()); // 2
count(5);
console.log(doubled()); // 10
```
#### Управление памятью `.destroy()`
Уничтожает сигнал, отписывая его от родителя и очищая всех слушателей:
```javascript
doubled.destroy();
```
#### Отладка `.chain()` и `.listenerCount()`
```javascript
console.log(doubled.chain()); // 'signal_a1b2c → signal_a1b2c_map_1'
console.log(count.listenerCount()); // количество подписчиков
```
### Создание элементов
Любой HTML-тег доступен как метод `dom`:
```javascript
dom.div() // <div></div>
dom.div('app') // <div id="app"></div>
dom.section() // <section></section>
dom.span() // <span></span>
```
CamelCase автоматически преобразуется в kebab-case:
```javascript
dom.viewBox() // <view-box></view-box>
```
### Инструкции
После создания элемента конфигурация задаётся цепочкой инструкций. Все инструкции возвращают прокси элемента для продолжения цепочки.
| Инструкция | Описание | Пример |
|---|---|---|
| `.id(id)` | Установить id | `.id('main')` |
| `.cls(classes)` | Добавить CSS-классы | `.cls('btn primary')` |
| `.atr(name, value)` | Установить атрибут | `.atr('href', '/home')` |
| `.atrs({...})` | Установить несколько атрибутов | `.atrs({ href: '/', title: 'Home' })` |
| `.css(styles)` | Добавить инлайн-стили | `.css('color: red;')` |
| `.pcss(selector, styles)` | Scoped-стили | `.pcss(':hover', 'color: blue;')` |
| `.mcss(query, styles)` | Media-запросы | `.mcss('(max-width: 768px)', 'font-size: 14px;')` |
| `.text(content)` | Установить текстовое содержимое | `.text('Hello')` |
| `.child(element)` | Добавить дочерний элемент | `.child(dom.span().done())` |
| `.children([...])` | Добавить массив дочерних | `.children([el1, el2])` |
**Важно:** внутри `.atrs()`, `.children()` и других методов, где значения могут быть сигналами, нужно передавать их явно:
```javascript
// Передача сигнала в атрибуты
const href = dom.signal('/home');
dom.a().atr('href', href).done();
```
#### Scoped-стили: `pcss`
Создаёт стили, привязанные только к этому элементу, с уникальным классом:
```javascript
dom.button()
.pcss(':hover', 'background: blue; color: white;')
.pcss('::after', 'content: "→";')
.done();
```
Сгенерирует `<style>` с уникальным селектором, действующим только на этот элемент.
#### Media-запросы: `mcss`
Создаёт медиа-запросы для конкретного элемента:
```javascript
dom.div()
.mcss('(max-width: 600px)', 'width: 100%;')
.mcss('(min-width: 1200px)', 'max-width: 800px; margin: 0 auto;')
.done();
```
### Условный рендеринг
```javascript
const isLoggedIn = dom.signal(false);
dom.div()
.if(isLoggedIn)
.text('Welcome back!')
.elif(dom.signal(false))
.text('Session expired')
.else()
.text('Please log in')
.endif()
.done();
```
При изменении сигнала DOM перерендеривается автоматически.
### Циклы
#### Неупорядоченный список: `ueach`
```javascript
const items = dom.signal(['Apple', 'Banana', 'Cherry']);
dom.div()
.ueach(items)
.done();
```
#### Упорядоченный список: `oeach`
```javascript
const steps = dom.signal(['Install', 'Configure', 'Run']);
dom.div()
.oeach(steps)
.done();
```
При изменении массива в сигнале элементы списка обновляются реактивно — добавляются/удаляются по мере необходимости, существующие элементы переиспользуются.
### События
Все нативные события доступны через camelCase-алиасы:
```javascript
dom.button()
.onClick(() => console.log('clicked'))
.onMouseEnter(() => console.log('hover'))
.onKeyDown(e => console.log(e.key))
.done();
```
Поддерживаемые алиасы событий:
| Категория | Алиасы |
|---|---|
| Keyboard | `keyDown`, `keyUp`, `keyPress` |
| Mouse | `mouseEnter`, `mouseLeave`, `mouseDown`, `mouseUp`, `mouseMove`, `mouseOver`, `mouseOut` |
| Touch | `touchStart`, `touchEnd`, `touchMove`, `touchCancel` |
| Focus | `focusIn`, `focusOut` |
| Animation | `animationStart`, `animationEnd`, `animationIteration` |
| Transition | `transitionEnd` |
| Drag | `dragStart`, `dragEnd`, `dragEnter`, `dragLeave`, `dragOver` |
| Pointer | `pointerDown`, `pointerUp`, `pointerMove`, `pointerEnter`, `pointerLeave`, `pointerOver`, `pointerOut`, `pointerCancel` |
| Wheel | `wheelStart`, `wheelEnd` |
| Composition | `compositionStart`, `compositionEnd`, `compositionUpdate` |
| Other | `contextMenu`, `formData`, `formChange`, `formInput` |
Для событий без алиаса используйте kebab-case в camelCase-нотации:
```javascript
dom.div().onCustomEvent(handler) // → 'custom-event'
```
### Завершение сборки: `.done()`
Подписывается на все сигналы, выполняет рендеринг и возвращает готовый DOM-элемент:
```javascript
const element = dom.div('app')
.cls('container')
.child(dom.span().text('Hello').done())
.done();
document.body.appendChild(element);
```
**Важно:** дочерние элементы, добавляемые через `.child()`, должны быть либо готовым `HTMLElement`, либо результатом `.done()`.
## Полный пример
```javascript
// Сигналы состояния
const count = dom.signal(0);
const theme = dom.signal('light');
// Приложение
const app = dom.div('app')
.cls('app-container')
.pcss(':hover', 'box-shadow: 0 0 10px rgba(0,0,0,0.1);')
.child(
dom.h1()
.text('Counter')
.done()
)
.child(
dom.p('counter-value')
.text(count.map(x => `Count: ${x}`))
.css('font-size: 24px;')
.done()
)
.child(
dom.div()
.cls('buttons')
.child(
dom.button('increment')
.text('+')
.onClick(() => count(count() + 1))
.done()
)
.child(
dom.button('decrement')
.text('-')
.onClick(() => count(count() - 1))
.done()
)
.done()
)
.done();
document.body.appendChild(app);
```