К содержимому

5. CSS Scoping ​

Блок <style> в .sharq — часть SFC (тот же файл, что и <template> и <script>). Есть два режима: global (по умолчанию) и scoped.

Глобальный <style> (по умолчанию, рекомендуется) ​

Без атрибута scoped CSS выводится как есть (raw) в сгенерированный USS компонента. Селекторы совпадают по имени класса в любом месте поддерева компонента, поэтому BEM-именование уже изолирует ваши стили:

xml
<style>
.card { flex-grow: 1; background-color: #1f1f1f; }
.card__title { font-size: 18px; color: white; }
.card__title:hover { opacity: 0.8; }
</style>
css
/* Compiled Card.g.uss — identical, no rewriting: */
.card { flex-grow: 1; background-color: #1f1f1f; }
.card__title { font-size: 18px; color: white; }
.card__title:hover { opacity: 0.8; }

Совпадают и корневое правило (.card), и дочерние правила (.card__title), потому что элементы несут эти классы (из class="…" в шаблоне). Это надёжный вариант по умолчанию — предпочитайте его, используя уникальные BEM-имена классов (component__part) для изоляции.

Scoped <style scoped> ​

С scoped компилятор добавляет к каждому селектору class-scope компонента .s-{hash} и добавляет этот scope-класс только корню компонента (ApplyScopedAttribute → AddToClassList("s-xxxxxx") в сгенерированном Build()):

xml
<style scoped>
.card { flex-grow: 1; }
.card:hover { opacity: 0.9; }
</style>
css
/* Compiled Card_scoped.g.uss: */
.card.s-a1b2c3 { flex-grow: 1; }
.card.s-a1b2c3:hover { opacity: 0.9; }

Поскольку scope-класс оказывается на корне, .card.s-a1b2c3 корректно совпадает с корнем.

Важное ограничение. Только корневой элемент получает .s-{hash} — дочерние элементы нет. Поэтому scoped-селектор для дочернего элемента, например .card__title, превращается в .card__title.s-a1b2c3, что требует обоих классов на одном и том же элементе и, соответственно, не будет совпадать с дочерними элементами. Используйте scoped-стили только для правил уровня root/host. Для стилизации дочерних элементов используйте глобальный <style> с BEM- именами классов (которые надёжно совпадают с дочерними элементами).

Рецепты вариантов (@variants) ​

Свойство, которое управляет семейством классов — размер, цвет, округление углов — раньше означало написание всех комбинаций вручную: одно условие :class для каждого класса, одно правило CSS на значение, синхронизированные вручную при добавлении или переименовании шага. @variants at-rule внутри <style> (scoped или global) компилирует это отображение из единого объявления вместо этого:

@variants <axis> [from <Prop>] [as "<prefix>"] [ambient] {
    <value> ["<alias>" …] {
        <declarations, optionally with nested `&` rules>
    }
    …
}
  • <axis> — имя в kebab-case (size, density, rounded, …).
  • from <Prop> — вспомогательное свойство. Для оси, которая выдаёт класс (любая ось без ambient), это должно быть поле/свойство Prop<string> — генератор проверяет его с помощью UseAllowed, перегрузка которого для строк принимает только Prop<string>. По умолчанию PascalCase формы имени оси, если опущено. ambient ось (ниже) не привязана к свойству вообще — она только записывает имя свойства для инструментария, поэтому её объявленный тип не имеет значения.
  • as "<prefix>" — переопределяет выданный префикс класса для унаследованной формы класса, которая не соответствует <block>--<axis>-<value>: as "" на оси variant выдаёт sus-button--elevated вместо sus-button--variant-elevated.
  • <value> — имя шага; любые строки в кавычках после него — это aliases — дополнительные значения свойства, которые также выбирают этот шаг. Шаг с именем default дополнительно совпадает с пустым значением свойства.
  • ambient — отмечает ось, которая не выдаёт класс или правило самостоятельно (см. ниже); она не принимает блок.

Для каждого значения компилятор выдаёт CSS-правило под классом значения и соответствующее условие в сгенерированном Build() компонента; для оси без ambient он также выдаёт guard времени выполнения — если свойству когда-либо присваивается значение вне рецепта, оно заменяется шагом default (или первое объявленное значение, если нет default), и предупреждение [PropAllowed] … not in allowed set логируется в редакторе/сборках разработки. Это сетка безопасности времени выполнения, а не проверка времени компиляции или редактирования — ничто не помешает вам напечатать значение вне рецепта, оно просто не останется.

Пример — ось size (иллюстративная) ​

Ни один отправленный компонент ещё не использует @variants — это выдуманная ось, показывающая форму, а не копия из реального файла .sharq:

@variants size from Size {
    xs "x-small" {
        min-height: 28px;
        padding-left: 10px; padding-right: 10px;
        font-size: 12px;
        &.icon-only    { width: 28px; max-width: 28px; }
        &.rounded-pill { border-radius: 14px; }
        .icon-wrap     { min-width: 20px; min-height: 20px; }
    }
    default {
        min-height: 40px;
        &.icon-only    { width: 40px; max-width: 40px; }
    }
}

& внутри блока значения следует стандарту CSS Nesting: он стоит для собственного выданного селектора значения, поэтому &.icon-only становится <block>--size-xs.icon-only, а не потомком правила. Строка без & (.icon-wrap выше) вложена как потомок селектора значения вместо этого.

rung(). Компилятор также понимает вызов rung(<family>, <rung>), который разрешается против собственной общей лестницы размеров SUS во время компиляции. Лестница поставляется в виде таблицы, сгенерированной внутри пакета com.sharq-it.sus.core, поэтому вызов компилируется везде, где этот пакет установлен как GitHub UPM git-пакет, без дополнительной настройки. Например, rung(control-height, xs) становится var(--sk-control-h-xs, 28px): оба имя токена и запасной номер берутся из лестницы. Без установленного kit ничего не устанавливает --sk-control-h-xs, поэтому объявление разрешается на его px запасной вариант (28px); с установленным kit значение темы kit вместо этого победит. Неизвестное имя семейства или rung — это ошибка компиляции, которая перечисляет доступные.

Ambient оси — переопределение токена, а не класс ​

Некоторые свойства (плотность — это обычное) не добавляют класс и не пишут правило самостоятельно — они работают, имея другое значение того же --sk-* токена уже в игре (через класс каскада установленный выше, например, на корне приложения) разрешаются иначе внизу. Объявление оси ambient говорит об этом явно вместо того, чтобы оставлять это невидимым:

@variants density from Density ambient;

Это утверждение, а не ярлык: оно говорит компилятору (и собственному описанию рецепта компонента, используемому инструментарием) "это свойство управляет эффектом, который происходит в другом месте", что ровно то, что отделяет предполагаемое ambient свойство от одного, чья ось просто никогда не была написана.

Граница ​

Рецепт компилирует фиксированное перекрёстное произведение оси × значение; это не небольшой язык таблиц стилей. Нет extend, нет определённых пользователем функций и нет импорта между телом стиля .sharq и листом утилит-класса — ни один из трёх не планируется. Компонент, который нуждается в большем, чем перекрёстное произведение названных шагов, продолжает писать простые правила в <style> (см. выше), что остаётся полностью поддерживаемой, неперенесённой формой.

Companion unscoped USS (escape hatch) ​

Для правил, которые нельзя выразить ни в одном из блоков — например, таргетинг компонента снаружи, или theme-уровневые переменные :root — положите unscoped USS рядом с компонентом (Resources/SusRuntime/Card.uss); он автоматически загружается в рантайме через LoadCompanionStyleSheets().

Имена сгенерированных файлов ​

ИсточникСгенерированный USS
Глобальный <style>Component.g.uss
<style scoped>Component_scoped.g.uss
inline style="…" на элементах шаблонаComponent_static.g.uss

См. также формат .sharq — раздел Style.