Обновить README.md
This commit is contained in:
parent
6b5296f533
commit
b744e2b193
281
README.md
281
README.md
@ -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);
|
||||
```
|
||||
Loading…
x
Reference in New Issue
Block a user