5. CSS Scoping
Блок <style> в .sharq — часть SFC (тот же файл, что и <template> и <script>). Есть два режима: global (по умолчанию) и scoped.
Глобальный <style> (по умолчанию, рекомендуется)
Без атрибута scoped CSS выводится как есть (raw) в сгенерированный USS компонента. Селекторы совпадают по имени класса в любом месте поддерева компонента, поэтому BEM-именование уже изолирует ваши стили:
<style>
.card { flex-grow: 1; background-color: #1f1f1f; }
.card__title { font-size: 18px; color: white; }
.card__title:hover { opacity: 0.8; }
</style>/* 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()):
<style scoped>
.card { flex-grow: 1; }
.card:hover { opacity: 0.9; }
</style>/* 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.