Темы¶
Основной тем являются фрагменты. Фрагменты состоят из секций. Секции состоят из разделов и свойств.
Секция - это раздел, находящийся в корне темы. Каждая секция описывает один из конкретных или один из псевдо-компонентов.
Свойства могут быть системными или пользовательскими. Системные свойства имеют четко определенный смысл и правила обработки. Пользовательские могут содержать произвольные значения, с оговоркой, что они не должны нарушать правил CSS.
Тема является результатом процесса сборки фрагментов. Сборка происходит следующим образом:
- Фрагменты, составляющие тему, объединяются в один в указанном порядке. При этом свойства, имеющие одинаковый путь с уже имеющимися, переопределяют предыдущие значения.
- Внутренние переменные заменяются на их значения.
- Юниты конвертируются в конкретные единицы.
- Создаются объекты, описывающие ресурсы темы (если есть).
- Создаются CSS-переменные. Именем переменной становится путь до нее. Т.е.
--секция-раздел-имя: значение
.
Комбинация фрагментов и внутренних переменных позволяет создавать вариации тем без их полного дублирования.
Глобальная тема управляется через статичный класс Themes
. Результат сборки глобальной темы доступен в Themes.current
.
Фрагменты¶
Назначение фрагментов — дать возможность изолировать разные аспекты тем и комбинировать их по необходимости.
Текущий набор фрагментов выглядит следующим образом:
Base.json
— тема по умолчанию, содержит в себе все, что необходимо для построения интерфейса.
Warm.json
, Dark.json
— содержат цвета теплой и темной тем, а также локальные переопределения цветов для некоторых свойств.
Condensed.json
— содержит величины для отступов, размеров и т.п. используемые в компактном дизайне.
Без фрагментов было бы необходимо создавать отдельный файл для каждого набора цветов и компактного/обычного дизайна с полным кодом темы. Т.е. что-то вроде Base.json, BaseCondensed.json, Warm.json, WarmCondensed.json и так далее.
В отличие от такого монолитного подхода, фрагменты достаточно объединить в нужном порядке. Т.е., например, компактная тема формируется объединением фрагментов «base» + «condensed». Компактная темная тема «base» + «dark» + «condensed». И так далее.
Структура фрагмента¶
В общем виде структура фрагмента выглядит следующим образом:
{
"Name",
"meta": {
"name": "",
"caption": "",
"comments": [],
"variables": {},
},
"resources": {
"images": {},
"icons": {
"sizes": {}
}
},
"section1": {},
"section2": {},
"section3": {
"@annotation":{
...
}
},
...
}
Name
— используется, только если фрагмент размещается на сервере.
meta
— обязательная секция. Содержит общую информацию о фрагменте.
meta.name
— идентификатор фрагмента. Единственное обязательное поле.
meta.caption
— заголовок, в настоящее время не используется.
meta.comments
— массив строк, можно использовать чтобы оставить комментарии к фрагменту. При сборке отбрасывается.
meta.variables
— внутренние переменные темы. Позволяют избежать хардкода одинаковых значений и облегчают процесс переопределения этих значений. При объединении фрагментов значения переменных с одинаковыми именами переопределяются.
Переменные имеют формат "name":"value"
. Для подстановки значения переменной нужно использовать ее имя вместе со знаком $
. Можно использовать в комбинации со знаком +
. В этом случае часть, идущая за +
, добавляется as is. Например, можно добавить прозрачность (в hex-формате):
font: {
color: "$primary+e6";
}
resources
— необязательный раздел для описания внешних ресурсов системы.
resources.icons
— необязательный раздел, позволяет создать собственные наборы иконок. Внутри может быть один зарезервированный раздел "sizes"
, в который можно добавить свои размеры для иконок. Для каждого свойства внутри будет сгенерирован класс с именем .icon-size-<name>
.
В настоящий момент иконки, переданные через темы, не масштабируются. Т.е. иконка с размером 16х16 не будет растягиваться под размер 24х24, например. Для каждого размера нужно создавать отдельный svg.
Любой другой раздел представляет собой набор иконок со следующими свойствами:
size
— имя размера иконки.
sprite
— путь до файла, начиная с Tessa\web\wwwroot\icons\
. Один спрайт может использоваться для нескольких наборов.
resources.icons
— иконки внутри спрайта. Имя свойства будет использоваться для создания класса, значение — это идентификатор иконки. Они могут совпадать, могут не совпадать. После генерации для иконки будет сгенерирован класс в формате .<size>-<name>
, который можно использовать аналогично шрифтовым иконкам в <Icon />
, <Button />
и так далее.
Вместо строки с идентификатором, можно использовать объект со следующими свойствами:
id
— идентификатор.
preserveColors: "true"
— позволяет использовать цвета, явно указанные в самом svg.
width
— позволяет передать ширину для иконки.
resources.images
— необязательный раздел, позволяет добавлять изображения. Каждый раздел внутри описывает одно изображение. Разделы могут иметь следующие свойства:
path
— путь до файла начиная с Tessa\web\wwwroot\images\
.
generateClass: "true"
— необязательное свойство, позволяет сгенерировать класс, который можно напрямую присвоить тегу <img>
. Без этого флага генерируется только переменная с путем до файла в формате --images-<name>
.
width
— необязательное свойство, позволяет явно задать ширину.
height
— необязательное свойство, позволяет явно задать высоту.
@annotation
— аннотации, позволяют группировать разделы и свойства. При сборке отбрасываются и не участвуют в формировании имен переменных. Используются для группировки частей темы по смыслу в сложных разделах.
Например, внутри @states
обычно располагаются состояния вроде active
/inactive
/disabled
и так далее. Внутри @components
части сложных компонентов вроде header
/content
/footer
и так далее.
Общие правила формирования секций¶
При создании новых секций важно понимать, что темы не имеют никакого отношения к CSS или верстке. Возможность передавать CSS-значения напрямую через темы и схожесть синтаксиса для значений вроде calc и var оставлены для удобства. По структуре секция должна быть ближе к макету/общему описанию. Это необходимо, чтобы избежать переноса структуры верстки в темы, что ведет только к ее чрезмерному усложнению.
В идеале, секция описывает компонент в максимально общем виде и меняется, только когда меняется дизайн, но не когда меняется верстка. Это очень важно для локальных тем (о них ниже). Если секция будет постоянно изменяться - это будет создавать необходимость постоянно обновлять локальные темы и все компоненты, использующие эту секцию.
Ввиду многообразия возможных вариантов, охватить их все и дать конкретные рекомендации по каждому в рамках документации нереалистично. Поэтому, ниже процесс создания секции описан на примере секции table
, который выглядит достаточно универсальным, чтобы создать базу для решения большинства кейсов. Но, в конечном счете, компоненты могут быть очень сложными, и возможны кейсы, которые будут противоречить каким-то положениям, описанным в примере, и в таком случае решения нужно принимать case-by-case, руководствуясь здравым смыслом.
Еще одним важным моментом является то, что технологически и методологически темы все еще находятся в стадии формирования. Поэтому в текущей реализации могут быть секции, которые описаны не самым качественным образом, и, следовательно, смотреть на реальные фрагменты как на примеры нужно с осторожностью.
Пример создания секции¶
В самом простом виде секция выглядит так:
{
"table": {}
}
Имена секций, разделов и свойств почти всегда должны быть односложными, без конструкций вида table-default
, padding-left
и так далее. Есть исключения в виде свойств min-height
, max-height
, min-width
, max-width
. В редких случаях возможны названия, которые состоят из двух слов, и как-то разделить их без потери смысла нельзя. В таких кейсах можно использовать несколько слов, но почти всегда этого нужно избегать.
Секции следует располагать по алфавиту, чтобы максимально облегчить навигацию внутри фрагментов.
Для начала возьмем самый простой кейс, в котором в таблице все элементы имеют одинаковые свойства. Т.е. одинаковый фон, одинаковые отступы и так далее:
В этом случае свойства внутри table
будут описывать свойства, общие для всех элементов таблицы:
{
"table": {
"background": "$controlBackground",
"border": "1 solid $controlBorderColor",
"corners": "$controlCorners",
"padding: "10"
}
}
Цвета всегда нужно выносить в переменные и всегда без прозрачности (прозрачность отбрасывается программно). Другие значения выносятся в переменные, опираясь на их смысл, если подразумевается их совместное использование.
После сборки темы эти свойства будут конвертированы в CSS-переменные:
--table-background
;--table-border
,--table-border-width
,--table-border-style
,--table-border-color
;--table-corners
,--table-corners-top-left
,--table-corners-top-right
,--table-corners-bottom-right
,--table-corners-bottom-left
;--table-padding
,--table-padding-top
,--table-padding-right
,--table-padding-bottom
,--table-padding-left
.
Подробнее о генерации свойств в разделе Системные свойства.
На текущей стадии раздел с переменными будет выглядеть так:
{
"meta": {
"variables": {
"controlBackground": "#F5F5F5",
"controlBorderColor": "#DADADA",
"controlCorners": "10"
}
},
...
"table": {
"background": "$controlBackground",
"border": "1 solid $controlBorderColor",
"corners": "$controlCorners",
"padding: "10"
}
}
Для простого кейса этого было бы достаточно. Но, допустим, что стиль таблицы более сложный, и мы хотим иметь больше контроля над её элементами. Тогда следующий шаг — это определение основных элементов. В случае таблицы это header
, row
, footer
:
"table": {
"background": "$controlBackground",
"border": "1 solid $controlBorderColor",
"corners": "$controlCorners",
"padding: "10"
"header": {},
"row": {},
"footer": {}
}
При этом важно понимать, что эти разделы не имеют никакого отношения к верстке и CSS. Например, в платформе аналогичная секция используется для табличных контролов, для таблицы в автокомплите и для заданий. Каждый из этих элементов имеет свою верстку и свой CSS, но при этом они могут ссылаться на одну секцию именно из-за того, что она описана в максимально общем, абстрактном виде.
Для примера некорректного подхода, последний пример секции можно описать, следуя, например, верстке табличного контрола, использующего для верстки <table>
:
"table": {
"border": "1 solid $controlBorderColor",
"header": {
"row": {
"corners": "$controlCorners $controlCorners 0 0",
"td": {
"background": "$controlBackground",
"padding: "0 10 $controlGap 10",
}
}
},
"row": {
"padding: "0 10 $controlGap 10",
"td": {
"background": "$controlBackground",
"border-bottom": "1 solid $controlBorderColor",
},
"first": {
"no-header": {
"corners": "$controlCorners $controlCorners 0 0",
"td": {
"background": "$controlBackground",
"padding: "0 10 $controlGap 10",
}
}
},
"last": {
"padding: "0 10 0 10",
}
},
"footer": {
"corners": "0 0 $controlCorners $controlCorners",
"padding: "0 10 10 10",
}
}
Если секцию описать подобным образом, то результат и для первого, и для второго варианта внешне может выглядеть идентично. И хотя в случае с <table>
не так много узко-специфичных компонентов, общая проблема все равно просматривается. Переиспользовать эту секцию для контрола, например, сверстанного с использованием display: grid
, уже не совсем тривиально. В частности, для таблицы свойство corners
применяется к строке, в то время как в элементе с display: grid
физических строк нет, есть только ячейки.
В общих чертах тут два типа ошибок. Первая — секция повторяет структуру <table>
и, соответственно, сильно связана со спецификой этого элемента. Вторая — свойства не описывают свойства элемента, а описывают одновременно свойства и способ их применения. Чуть более подробно:
border-bottom
— это деталь верстки. В теме используется border
в общем смысле. Информация о том, к каким именно частям HTML-элемента он применяется, относится только к верстке. Помимо этого, система не знает свойства border-bottom
и не будет генерировать CSS-переменные вроде border-width
для его компонентов. В теме таких свойств быть не должно. Список допустимых свойств перечислен ниже, в разделе Системные свойства.
"corners": "$controlCorners $controlCorners 0 0"
и так далее — в отличие от border-bottom
использовано корректное имя свойства, но, как и border-bottom
, оно фиксирует не только параметры, но и способ применения.
Для улучшения читаемости можно группировать разделы или свойства, используя аннотации:
"table": {
"background": "$controlBackground",
"border": "1 solid $controlBorderColor",
"corners": "$controlCorners",
"padding: "10",
"@components": {
"header": {},
"row": {},
"footer": {}
}
}
При сборке аннотации отбрасываются и не отображаются в именах переменных. На текущий момент есть ряд устоявшихся имен:
@components
— субкомпоненты описываемого элемента.
@states
— состояния компонента или субкомпонентов.
@types
— некие архетипы компонента. Например, compact
, mobile
и так далее. Содержание может сильно меняться от компонента к компоненту.
Каждый раздел может содержать свои свойства и подразделы. Субкомпоненты могут как использовать свойства основного компонента, так и иметь свои. Какие-то четкие рекомендации дать трудно, все зависит от сути каждого конкретного компонента.
Допустим, мы не хотим отрывать субкомпоненты от общих настроек полностью, а менять что-то только при каких-то условиях, например, менять фон при выделении строки:
"table": {
"background": "$controlBackground",
"border": "1 solid $controlBorderColor",
"corners": "$controlCorners",
"padding: "10",
"@components": {
"header": {},
"row": {
"@state": {
"selected": {
"background": "$controlSelectionBackground"
}
}
},
"footer": {}
}
}
Для примера, CSS для строки будет выглядеть примерно таким образом:
.table {
.row {
background: var(--table-background);
padding: var(--table-padding);
&.selected {
background: var(--table-row-selected-background);
}
}
}
Т.е. по умолчанию строка использует настройки фона, общие для всей таблицы. А в состоянии, когда она выделена, использует свой собственный.
Допустим, мы хотим иметь возможность контролировать настройки фона для каждого субкомпонента в отдельности, тогда в общем свойстве отпадает надобность.
"table": {
"border": "1 solid $controlBorderColor",
"corners": "$controlCorners",
"padding: "10",
"@components": {
"header": {
"background": "$controlBackground",
},
"row": {
"background": "$controlBackground",
"@state": {
"selected": {
"background": "$controlSelectionBackground"
}
}
},
"footer": {
"background": "$tableFooterBackground",
}
}
}
Тогда CSS для строки будет выглядеть так:
.table {
.row {
background: var(--table-row-background);
padding: var(--table-padding);
&.selected {
background: var(--table-row-selected-background);
}
}
}
При этом изначальные значения могут быть идентичными, но сам факт наличия свойств позволит изменять их независимо от других субкомпонентов. Например, в локальных темах.
Таким образом, в общем смысле есть два подхода. Первый подразумевает использование общих, максимально глобальных свойств. Второй подразумевает создание локальных свойств для каждого субкомпонента. В текущей реализации (и в примере в том числе) используется гибридный формат, часть свойств являются общими, часть локальными. При создании новой секции лучше начинать с общих свойств и переносить их в субкомпоненты по мере надобности.
В перспективе, возможно, движение в сторону формата с полностью локальными свойствами, так как он предоставляет наибольшую гибкость в плане настройки компонентов. В этом случае он будет реализован в комбинации с автогенерацией классов для сокращения объема ручного труда.
Далее, допустим, что у таблицы должны быть разные отступы между строками и заголовком/футером. В этом случае оправдано добавление дополнительного раздела:
"table": {
"background": "$controlBackground",
"border": "1 solid $controlBorderColor",
"corners": "$controlCorners",
"padding: "10",
"gap": "10",
"@components": {
"header": {},
"content": {
"gap": "5",
"row": {}
},
"footer": {}
}
}
И снова, важный момент здесь, что структура секции не обязательно должна быть связана с версткой. --table-content-gap
может быть применен разными способами к разным элементам в зависимости от того, как реализована таблица. Задача темы фиксировать свойства и отношения между компонентами в максимально общем виде, а не повторять структуру верстки один в один.
Для иллюстрации, допустим, что есть элемент с версткой, созданной с помощью нескольких вложенных контейнеров с display: flex
.
В этом случае, если опираться на верстку при описании темы, можно прийти к чему-то подобному:
"component": {
"gap": "5",
"first": {
"gap": "10",
"right": {
"gap": "5",
...
}
},
"second": {
"gap": "10"
}
}
Но, если смотреть в общем, то будет понятно, что отступы в элементах следуют единой логике, а именно — отступ между строками равен 5 пикселям, отступ между колонками 10. Тогда вместо сложной конструкции из предыдущего фрагмента, настройки можно зафиксировать в следующем виде:
"component": {
"gap": "5 10",
}
Резюмируя, рекомендации будут звучать следующим образом:
- Разделы должны быть максимально простыми.
- Для разделов и свойств нужно использовать односложные имена. Для свойств нельзя использовать имена, основанные на CSS-свойствах вроде
border-bottom
,background-color
и так далее. - Нельзя создавать секции, основываясь на CSS и верстке. Начинать нужно с перечисления основных элементов, увеличивая глубину секции только по мере необходимости.
- Работу со свойствами лучше начинать с использования глобальных свойств и создавать локальные по мере необходимости.
ThemeBuilder¶
Для сборки темы используется ThemeBuilder
. Процесс сборки запускается вызовом ThemeBuilder.build
и состоит из нескольких стадий:
- Объединение фрагментов.
- Конвертация системных разделов.
- Конвертация пользовательских разделов.
При конвертации разделов происходит конвертация юнитов в конкретные единицы измерения и генерация CSS-переменных. При конвертации переменных ThemeBuilder
может осуществлять поиск значения по цепочке используя ThemeBuilder.parent
.
ThemeBuilder.setParent
— задает предка для текущего ThemeBuilder
. Это позволяет реализовать переопределение переменных на программном уровне. При поиске значений ThemeBuilder
сначала проверяет локальные переменные, если не находит нужного значения, то передает запрос предкам по цепочке. Это позволяет переопределить какие-то конкретные значения на разных уровнях.
Сейчас существует цепочка, идущая от глобальной темы к каждому контролу в карточке:
Themes.current
→ Form.formTheme
→ Block.blockTheme
→ Control.controlTheme
.
ThemeBuilder.getValue
— возвращает значение указанной переменной, в формате имени CSS-переменной. Если не находит локально, проверяет родителей по цепочке. Возвращает значение в формате строки как есть, без конвертации.
Theme.setValue
— устанавливает значение для указанной переменной. Имя указывается в формате имени CSS-переменной. Сбрасывает значения ThemeBuilder.CSS
и ThemeBuilder.string
.
ThemeBuilder.getInternalVariable
— возвращает значение указанной локальной переменной. Если не находит локально, проверяет родителей по цепочке. Возвращает значение в формате строки как есть, без конвертации.
ThemeBuilder.getThemeNode
— возвращает раздел фрагмента, находящийся по пути, указанному в формате массива имен разделов. Например, ["filter", "parameter"]
. При вывозе с пустым массивом вернет весь текущий фрагмент.
ThemeBuilder.getThemeNodeCopy
— аналогичен ThemeBuilder.getThemeNode
, но возвращает глубокую копию. Если необходимо сохранить текущий фрагмент, нужно использовать этот метод.
ThemeBuilder.merge
— статичный метод. Объединяет несколько фрагментов в один.
ThemeBuilder.build
— выполняет объединение и конвертацию переданных фрагментов. Может вызываться многократно, при вызове сбрасывает текущие данные билдера.
ThemeBuilder.appendFragment
— выполняет объединение и конвертацию переданных фрагментов. Может вызываться многократно. Сбрасывает значения ThemeBuilder.CSS
и ThemeBuilder.string
.
ThemeBuilder.clearCache
— сбрасывает значения ThemeBuilder.CSS
и ThemeBuilder.string
.
ThemeBuilder.string
— вычисляемое поле, возвращает CSS-переменные текущего ThemeBuilder
в виде строки. Создается только при обращении.
ThemeBuilder.CSS
— вычисляемое поле, возвращает CSS-переменные текущего ThemeBuilder
в формате React.CSSProperties
. Создается только при обращении.
ThemeBuilder.images
— список изображений текущего ThemeBuilder
. Изображения добавляются в DOM только для глобальной темы. Вручную можно добавить, использовав компонент <Resources/>
. Не использует цепочку предков.
ThemeBuilder.icons
— список наборов иконок текущего ThemeBuilder
. Наборы добавляются в DOM только для глобальной темы. Вручную можно добавить, использовав компонент <Resources/>
. Не использует цепочку предков.
ThemeBuilder.wallpapers
— содержит список фоновых изображений, которые можно установить через диалог «Фон и тема». Учитывается только в глобальной теме.
ThemeBuilder.defaultWallpaper
— фоновое изображение по умолчанию. Учитывается только в глобальной теме.
ThemeBuilder.activeFragments
— содержит список имен используемых фрагментов.
ThemeBuilder.resolveVariables
— метод, с помощью которого в рантайме можно выполнить конвертацию CSS-переменных, внутренних переменных и юнитов в передаваемой строке. Работает рекурсивно. Отслеживает глубину и в случае превышения ThemeBuilder.maxResolveDepth
прерывает процесс.
ThemeBuilder.compute
— метод, с помощью которого в рантайме можно выполнить расчет выражений calc()
, либо получить значение конкретной переменной var()
. Внутри calc
могут быть использованы внутренние переменные ($
) и CSS-переменные (var
).
Локальные темы (WIP)¶
Общая идея заключается в том, чтобы дать возможность задавать настройки на промежуточных уровнях, а не только глобально или на уровне каждого отдельного элемента управления.
В чуть более конкретном виде суть идеи в том, чтобы дать возможность, например, переопределить --control-background
на уровне блока и повлиять сразу на все элементы управления внутри него, а не настраивать их по одному.
Условно, сейчас существует четыре уровня: глобальная тема (Themes.current
), тема формы (formTheme
), блока (blockTheme
) и контрола (controlTheme
).
В настоящий момент эта функциональность достаточно сильно ограничена. Программно, сама цепочка Глобальная тема
→ Тема формы
→ Тема блока
→ Тема контрола
работает. Но ввиду того, что часть элементов управления переведена на новую систему тем только частично, поддержка отдельных свойств и практика их применения может различаться, и их переопределение не всегда приводит к ожидаемым результатам. Тем не менее, эти возможности стоит учитывать при разработке.
По мере доработки элементов управления поддержка естественным образом постепенно улучшается.
Выражения и CSS-переменные¶
В свойствах тем можно использовать вычисляемые выражения calc()
, по аналогии с CSS. При необходимости подобные выражения можно рассчитать в рантайме с помощью ThemeBuilder.compute
.
В них можно использовать внутренние переменные и юниты. После сборки выражения calc()
выводятся в DOM в корректном для CSS формате. Т.е. calc($fontSizePrimary + 2)
будет выведено как calc(14px + 2x)
. Сами выражения при сборке не рассчитываются.
Из важных нюансов — по правилам CSS свойства с calc()
не пересчитываются если использованные переменные были переопределены в элементах ниже элемента, в котором было добавлено само выражение. Но, при этом если переопределение произошло где-то по пути по цепочке к текущему ThemeBuilder
, то ThemeBuilder.compute("calc(…)")
будет учитывать такие переопределения.
ControlContainer и темы¶
По умолчанию ControlContainer
получает свойства из секции control
и переменных вида --control-padding
. При необходимости это поведение можно изменить с помощью параметра ControlContainerProps.section
, который позволяет явно указать секцию, из которой ControlContainer
должен брать свои значения. Внутренне, ControlContainer
продолжит использовать переменные с оригинальными именами (--control-padding
, --control-gap
и так далее), но как значения будет указывать переменные с именами, начинающимися с имени пользовательской секции.
Например, при section="component"
контрол контейнер сформирует название переменной и заменит значение --control-padding
на --control-padding: var(--component-padding)
. section
может указывать на вложенные разделы, тогда в section
передается путь до раздела со словами, разделенными знаком -
.
Само по себе указание секции ничего не изменит, в дополнение нужно будет явно указать какие свойства должны ее использовать. Это можно сделать, установив значение custom-section
для одного или нескольких свойств ControlContainer
: border
, padding
, background
, font
, border
, gap
.
Нормализация¶
Это процесс, позволяющий выполнить преобразование и очистку настроек, приходящих с сервера при загрузке клиента. В ходе нормализации выполняется преобразование цветов, размеров и преобразование настроек в локальные темы в соответствии с заданными правилами.
Нормализация запускается вызовом Normalization.normalize(response: InitializationResponse)
при вызове DefaultApplicationInitializationExtension.afterMetadataReceived
. Если необходимо внести какие-то изменения в правила нормализации, они должны быть произведены до вызова DefaultApplicationInitializationExtension.afterMetadataReceived
.
Стандартный набор правил формируется при первом обращении к Normalization.instance
.
Элементы управления в легком клиенте исходят из правила, что в настройках явно указываются только значения, отличные от значений по умолчанию. Нормализатор позволяет отформатировать серверные настройки под это правило. В настоящий момент нормализатор выполняет следующие действия:
- Удаляет значения, считающиеся значениями по умолчанию как для Легкого Клиента, так и для Толстого Клиента. По большей части это легаси настройки Толстого Клиента, необходимые для его работы. Сами значения перечислены в публичном объекте
ServerDefaults
. - Увеличивает некоторые числовые настройки: горизонтальные и вертикальные отступы в блоках (x4 и x3); отступы между блоками (x2); размер шрифта (x1.2).
- Конвертирует
ControlSettings
во фрагмент темы, который принимаетсяThemeBuilder
.
Работа нормализатора основана на правилах. По охвату правила могут быть двух типов: глобальные и локализованные для конкретного типа элемента управления. Локализация охвата происходит за счет передачи идентификатора типа элемента управления при создании правила (например, CardControlTypes.LabelControlType.id
). При нормализации сначала выполняется поиск локального правила и, если оно не было найдено, то выполняется поиск глобального.
Правила задаются либо для комбинации свойства и значения (глобальное правило), либо для комбинации свойства, значения и типа элемента управления (локальное правило). При добавлении нескольких правил одного охвата старое правило удаляется. Т.е. активным будет только правило, добавленное последним.
Методы для работы с правилами:
Normalization.remove
— удаляет значение для указанной комбинации свойства и значения. Можно локализовать для конкретного контрола.
Normalization.replace
— заменяет значение для указанной комбинации свойства и значения. Можно локализовать для конкретного контрола.
Normalization.custom
— позволяет выполнить колбек для указанной комбинации свойства и значения. Можно локализовать для конкретного контрола.
Normalization.ignore
— позволяет проигнорировать комбинацию свойства и значения для конкретного контрола.
При нормализации ControlSettings
конвертируется во фрагмент темы, понятный ThemeBuilder
и по умолчанию настройки добавляются в секцию control
. При необходимости для конкретного типа контрола можно задать имя секции с помощью Normalization.createSection
.
Для преобразования ControlSettings
используется следующий вызов:
Normalization.custom('ControlSettings', { anyObject: true }, Normalization.generateControlTheme);
Normalization.generateControlTheme
является публичным.
Системные свойства¶
В темах есть ряд слов со строго-определенным смыслом, форматом и способом обработки: background
, border
, bottom
, corners
, color
, column
, font
, gap
, height
, left
, offset
, padding
, margin
, right
, row
, size
, top
, weight
, width
.
background
— используется для задания цвета фона (background-color
использовать не нужно). Формат: строка.
border
— описывает свойства границы элемента. При парсинге для этого свойства создаются CSS переменные *-border
, *-border-width
, *-border-style
, *-border-color
. Формат: строка вида <width> <style> <color>
или объект вида { width, style, color }
.
font
— описывает свойства текста. При указании размера шрифта рекомендуется всегда указывать высоту строки. Это позволяет стабилизировать высоту элементов. Формат: { background, color, decoration, family, height, size, style, weight }
.
gap
— расстояние между элементами внутри контейнера. Формат: строка или { row, column }
.
row
, column
- субкомпоненты для свойства gap
.
top
, right
, bottom
, left
– субкомпоненты свойств padding
, margin
, border
.
Юниты¶
Внутри тем нет конкретных единиц измерений вроде px, rem, em и так далее. Вместо них используются юниты. При сборке темы юниты конвертируются в необходимые типы. Для web-клиента это px.
Основная цель введения юнитов — это обеспечить униформность величин, что позволяет упростить расчеты выражений и избавиться от побочных эффектов использования относительных единиц вроде em.
Для того, чтобы избежать преобразования числа в пиксели можно использовать знак равенства:
"font" {
"height": "=1.3"
}
Для таких значений знак равенства будет отброшен, а значение оставлено в исходном виде. Но если были указаны единицы измерения, то они тоже будут отброшены.
Значения с %
в конце выводятся как есть.
Преобразование выполняется для следующих свойств: border
, bottom
, column
, gap
, height
, left
, padding
, margin
, right
, row
, size
, top
, width
, corners
, offset
и всех внутренних переменных.
Работа с цветами¶
Цвета нужно указывать в hex-формате без указания прозрачности и всегда выносить в переменные. Если цвет вынесен в переменные, то при создании альтернативных цветовых схем переопределение цвета будет почти всегда сводиться к переопределению этой конкретной переменной, а не к дублированию разделов для переопределения свойств с этим цветом.
Форматы вроде rgb()
, hsl()
, lab()
и так далее не используются.
hex, rgb, hsl имеют один и тот же охват (8 бит на канал), и поддерживать их все не имеет смысла. Из них hex проще всего комбинировать с alpha-каналом, он имеет более компактную запись и легче читается.
lab имеет более высокий (условно бесконечный) цветовой охват, но потребности в таком охвате в рамках системы нет. При таких недостатках как более длинная запись, потеря точности при конвертации и проблемы определения схожести между цветами при чтении/поиске, возникающей из-за дробной части, использовать его достаточно проблематично.
Поэтому используется только hex.