Обновить 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