Именованные слоты

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


Основы слотов

В веб-компонентах существует концепция слотов, которая позволяет вставлять произвольный HTML-контент внутрь компонента. По умолчанию используется слот без имени, куда попадает любой контент, не привязанный к конкретному слоту:

import { component, html } from 'haunted';

function Card({ title, children }) {
  return html`
    <div class="card">
      <h2>${title}</h2>
      <div class="content">
        <slot></slot>
      </div>
    </div>
  `;
}

customElements.define('my-card', component(Card));

Использование:

<my-card title="Пример">
  <p>Это основной контент карточки.</p>
</my-card>

В этом случае <p> автоматически попадёт в дефолтный слот.


Создание именованных слотов

Именованные слоты позволяют разделить область компонента на несколько логических частей, куда можно вставлять отдельные блоки контента. Для создания именованного слота используется атрибут name:

function Modal() {
  return html`
    <div class="modal">
      <header>
        <slot name="header"></slot>
      </header>
      <section>
        <slot name="body"></slot>
      </section>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
  `;
}

customElements.define('my-modal', component(Modal));

Использование:

<my-modal>
  <h1 slot="header">Заголовок модального окна</h1>
  <p slot="body">Основной текст модального окна.</p>
  <button slot="footer">Закрыть</button>
</my-modal>

В этом примере каждый элемент попадает строго в свой слот: заголовок в <header>, тело в <section>, кнопка — в <footer>.


Взаимодействие с реактивными данными

Haunted позволяет использовать реактивное состояние через хук useState. Именованные слоты совместимы с реактивными изменениями:

import { useState } from 'haunted';

function Alert() {
  const [message, setMessage] = useState('Начальное сообщение');
  return html`
    <div class="alert">
      <slot name="icon"></slot>
      <span>${message}</span>
      <button @click=${() => setMessage('Сообщение обновлено')}>Обновить</button>
    </div>
  `;
}

customElements.define('my-alert', component(Alert));

При этом <slot name="icon"> можно заполнять любым содержимым извне, а реактивный <span> обновляется автоматически при изменении состояния.


Дефолтный контент для именованных слотов

Именованные слоты могут содержать контент по умолчанию, который будет отображаться, если соответствующий слот не заполнен:

function CardWithDefaultSlot() {
  return html`
    <div class="card">
      <header>
        <slot name="header">Заголовок по умолчанию</slot>
      </header>
      <main>
        <slot name="body">Содержимое по умолчанию</slot>
      </main>
    </div>
  `;
}

customElements.define('my-card-default', component(CardWithDefaultSlot));

Использование без слотов:

<my-card-default></my-card-default>

Отобразится стандартный заголовок и тело. Если передать слоты, они заменят дефолтный контент.


Стилизация именованных слотов

Слоты можно стилизовать через CSS-псевдокласс ::slotted:

card-with-default ::slotted([slot="header"]) {
  font-weight: bold;
  color: darkblue;
}

card-with-default ::slotted([slot="body"]) {
  font-style: italic;
}

Особенности: ::slotted позволяет стилизовать только верхний уровень элементов, вложенные теги не затрагиваются.


Комбинирование именованных и дефолтных слотов

В сложных компонентах удобно комбинировать слоты:

function Layout() {
  return html`
    <nav>
      <slot name="nav">Меню по умолчанию</slot>
    </nav>
    <main>
      <slot></slot>
    </main>
    <aside>
      <slot name="aside">Боковая панель по умолчанию</slot>
    </aside>
  `;
}

customElements.define('my-layout', component(Layout));

Использование:

<my-layout>
  <p>Главный контент</p>
  <div slot="nav">Пользовательское меню</div>
</my-layout>

В этом примере <p> попадает в дефолтный слот <main>, <div> заменяет дефолтное меню.


Динамическая генерация слотов

Haunted позволяет динамически формировать слоты на основе данных:

function TabView({ tabs }) {
  return html`
    <div class="tabs">
      ${tabs.map(
        (tab) => html`<slot name="${tab.name}">${tab.defaultContent}</slot>`
      )}
    </div>
  `;
}

customElements.define('my-tab-view', component(TabView));

Такой подход позволяет создавать компоненты с переменным числом областей, каждая из которых может иметь уникальное имя и дефолтный контент.


Именованные слоты в Haunted обеспечивают гибкость и контроль структуры компонентов, позволяя создавать сложные интерфейсы с реактивными данными, стилями и динамическими областями для контента. Они позволяют строить компоненты, которые легко расширяются и переиспользуются без изменения внутренней логики.