Части и ::part

Haunted — это библиотека для создания веб-компонентов на основе функционального подхода в стиле React, но с полной совместимостью с Web Components. Она позволяет создавать лёгкие, реактивные компоненты с минимальным синтаксисом и высокой производительностью. Одной из ключевых особенностей современной веб-разработки с Haunted является работа с частями компонента через псевдоэлемент ::part, что позволяет управлять стилями внутренней структуры компонента из внешнего контекста.


Функциональные компоненты в Haunted

В 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

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 обеспечивает безопасное разделение внутренней структуры и внешней стилизации.


Лучшие практики работы с ::part

  1. Давать осмысленные имена частям, чтобы сторонние стили были понятными (header, content, footer).
  2. Минимизировать количество частей, чтобы не перегружать API компонента.
  3. Использовать комбинацию part + классов, если нужны состояния или анимации.
  4. Не использовать сложные вложенные селекторы с ::part, это нарушает совместимость с Shadow DOM.

Ограничения и возможности

  • ::part не поддерживает псевдоэлементы потомков (::part(child)::after нельзя применять).
  • Можно комбинировать с медиазапросами и анимациями.
  • Полезно для библиотек UI, где компонент должен быть настраиваемым внешними стилями без разрушения инкапсуляции.
  • В Haunted функциональные компоненты упрощают работу с динамическими частями через useState и реактивный рендеринг.

Haunted и ::part создают мощный инструмент для настраиваемых и реактивных веб-компонентов, где внутренние элементы остаются инкапсулированными, но при этом доступными для безопасной внешней стилизации. Такой подход обеспечивает чистую архитектуру компонентов и масштабируемость интерфейсов.