Haunted — это библиотека для создания веб-компонентов на основе
функционального подхода в стиле React, но с полной совместимостью с Web
Components. Она позволяет создавать лёгкие, реактивные компоненты с
минимальным синтаксисом и высокой производительностью. Одной из ключевых
особенностей современной веб-разработки с Haunted является работа с
частями компонента через псевдоэлемент ::part, что
позволяет управлять стилями внутренней структуры компонента из внешнего
контекста.
В Haunted компонент определяется как обычная функция, которая
возвращает шаблон HTML через Tagged Template Literal html.
Простейший компонент выглядит следующим образом:
import { html, component } from 'haunted';
function Button({ label }) {
return html`<button part="custom-button">${label}</button>`;
}
customElements.define('my-button', component(Button));
Здесь важным моментом является атрибут part. Он
обозначает имя части, через которую можно будет
применять стили извне компонента, используя CSS-псевдоэлемент
::part.
part и его
назначениеАтрибут part позволяет делать внутренние
элементы компонента доступными для внешнего CSS. Без него стили
внутри Shadow DOM компонента остаются инкапсулированными и недоступными
для изменения снаружи. Пример использования:
my-button::part(custom-button) {
background-color: #007acc;
color: white;
border-radius: 5px;
padding: 10px 20px;
font-size: 16px;
}
my-button — тег веб-компонента.::part(custom-button) — псевдоэлемент, указывающий на
внутренний элемент с атрибутом part="custom-button".Один компонент может содержать несколько частей для более гибкого управления стилями:
function Card({ title, content }) {
return html`
<div part="card-container">
<h2 part="card-title">${title}</h2>
<p part="card-content">${content}</p>
</div>
`;
}
customElements.define('my-card', component(Card));
CSS для разных частей:
my-card::part(card-container) {
border: 1px solid #ccc;
padding: 20px;
border-radius: 8px;
}
my-card::part(card-title) {
font-size: 24px;
margin-bottom: 10px;
}
my-card::part(card-content) {
font-size: 16px;
color: #555;
}
Такой подход делает компонент модульным и настраиваемым без необходимости раскрывать внутреннюю структуру через слоты или глобальные селекторы.
Можно использовать комбинированные селекторы для управления стилями частей внутри разных состояний компонента:
my-card::part(card-container):hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
my-card::part(card-title) {
font-family: 'Arial', sans-serif;
}
::part работает вместе с любыми CSS-свойствами,
поддерживаемыми стандартными элементами. Важно помнить, что нельзя
применять селекторы потомков (::part(child) > span) —
::part всегда применяется к самому элементу с
атрибутом part.
Haunted поддерживает реактивность, поэтому части могут изменять свои атрибуты и содержимое при обновлении состояния компонента. Пример:
import { useState, html, component } from 'haunted';
function ToggleButton() {
const [active, setActive] = useState(false);
return html`
<button
part="toggle"
@click=${() => setActive(!active)}
class="${active ? 'active' : ''}"
>
${active ? 'Включено' : 'Выключено'}
</button>
`;
}
customElements.define('toggle-button', component(ToggleButton));
CSS с внешним управлением:
toggle-button::part(toggle).active {
background-color: #28a745;
color: white;
}
toggle-button::part(toggle) {
background-color: #ccc;
color: black;
}
Состояние компонента влияет на внешний стиль через классы, но
::part обеспечивает безопасное разделение
внутренней структуры и внешней стилизации.
::partheader, content,
footer).part +
классов, если нужны состояния или анимации.::part, это нарушает совместимость с Shadow
DOM.::part не поддерживает псевдоэлементы
потомков (::part(child)::after нельзя
применять).useState и реактивный рендеринг.Haunted и ::part создают мощный инструмент для
настраиваемых и реактивных веб-компонентов, где
внутренние элементы остаются инкапсулированными, но при этом доступными
для безопасной внешней стилизации. Такой подход обеспечивает чистую
архитектуру компонентов и масштабируемость интерфейсов.