# Конфигурация

> Дефолты на поддерево через GrConfigProvider, порядок их разрешения, авто-импорт и runtime-адаптер.

- Page: https://granularity.tech/ru/docs/configuration
- Last reviewed: 2026-10-16

Конфигурация здесь двухслойная и слои не пересекаются. На сборке настраивается
**что попадёт в CSS** — это `granum.config.mjs` и страница
[установки](/ru/docs/installation). В рантайме настраивается **как компоненты
ведут себя по умолчанию** — и это `GrConfigProvider`, о котором вся страница.

## Дефолты на поддерево

```vue
<GrConfigProvider
  size="sm"
  :component-defaults="{ GrButton: { variant: 'outline' }, GrInput: { clearable: true } }"
>
  <App />
</GrConfigProvider>
```

Провайдер рендерится прозрачно (`display: contents`) и работает через
`provide`/`inject`, поэтому раскладку не меняет и может стоять где угодно — в
том числе несколько раз вложенно. Дочерний провайдер мержится поверх
родительского **на уровне пропа**, а не блока: переопределив один `variant`, вы
не потеряете остальные дефолты `GrButton`.

## Порядок разрешения

:::steps
1. Локальный проп на самом компоненте.
2. `componentDefaults[Component]` ближайшего провайдера.
3. Глобальный `size` провайдера.
4. Собственный дефолт компонента.
:::

Шаг 3 касается только пропа `size` у контролов: у оверлеев своя шкала, и
глобальный `size` их не трогает. Кнопка внутри `GrButtonGroup` берёт значение
группы раньше конфига.

Отсюда правило, которое стоит знать и потребителю: проп, настраиваемый через
провайдер, объявлен с дефолтом `undefined`. Иначе Vue подставил бы своё значение
раньше, чем компонент заглянет в конфиг, и провайдер молча перестал бы работать.

Прочитать эффективный конфиг из приложения можно `useGrConfig()` — это публичный
API, а не внутренность.

## Две шкалы размеров

`size` у провайдера — про **контролы**: `xs | sm | md | lg`. У оверлеев шкала
своя — `sm | md | lg | xl | full`, — и глобальный `size` её не трогает, потому
что `xs` для модального окна не значит ничего. Размер окна задаётся точечно:

```vue
<GrConfigProvider :component-defaults="{ GrModal: { size: 'lg' } }">
```

Так настраиваются `GrModal`, `GrDialog`, `GrConfirmDialog`, `GrPromptDialog`,
`GrCommandPalette` и `GrDrawer`.

## Тема поддерева

`theme` кладёт значение в `data-theme` на обёртку. Темы объявлены атрибутным
селектором, поэтому тёмный остров внутри светлой страницы работает без
дополнительных стилей.

:::callout{type="info"}
Панели оверлеев телепортируются в `body` и в DOM живут вне обёртки — но в дереве
компонентов остаются внутри, поэтому `inject` до них доходит и тему они ставят
себе сами. Модалка, дровер, дропдаун, поповер, тултип, селекты, тостер и
просмотрщик изображений покрыты.
:::

Тема **документа** — не эта работа: ею занимаются `useTheme` и `initThemeEarly`,
см. [темизацию](/ru/docs/theming). Двух механизмов на одно и то же в пакете нет,
и проп провайдера именно про остров.

## Шкала слоёв и точка монтирования

`zIndexBase` пересчитывает `--gr-z-*` от базы (`dropdown` +0, `tooltip` +50,
`modal` +100, `toast` +200) и ставит их **на `<html>`**, возвращая прежние
значения при размонтировании. На `:root`, а не на обёртку, — по той же причине,
по которой панели ставят тему сами: панель уезжает в `body` и переменных
поддерева не видит. Шкала одна на документ, и второй провайдер с другой базой в
dev-сборке предупредит о конфликте.

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

`portalTarget` называет контейнер, куда уезжают оверлеи поддерева. По умолчанию
это общий `#gr-portal` в `body`, который пакет создаёт сам при первом рендере
оверлея в браузере.

:::callout{type="warning"}
Провайдер только называет цель — DOM он не создаёт, контейнер должен
существовать к моменту открытия оверлея. Требование к контейнеру одно, зато
жёсткое: **никаких `transform`, `filter`, `contain`, `perspective` и
`will-change`.** Они создают containing block для `position: fixed`, и
floating-панели начнут считать позицию от контейнера, а не от вьюпорта.
:::

## Авто-импорт

Рекомендуемый способ для боевого приложения — резолвер для
`unplugin-vue-components`. Он вставляет в SFC статические импорты на подпути
пакета, поэтому tree-shaking остаётся плотным: в бандл попадает ровно то, что
встретилось в шаблонах.

```bash
yarn add -D @feugene/unplugin-granularity unplugin-vue-components
```

```ts title="vite.config.ts"
import Components from 'unplugin-vue-components/vite'
import { GranularityResolver } from '@feugene/unplugin-granularity'

export default defineConfig({
  plugins: [vue(), Components({ resolvers: [GranularityResolver()] })],
})
```

CSS подключать при этом не нужно: у большинства компонентов своего CSS нет
вовсе, а у остальных (22 из 84) его по манифесту пакета собирает granum в слой
`granum.components` листа `virtual:granum.css`. Чанки компонентов CSS не
импортируют, поэтому `importStyle` у резолвера ядра не включайте: такого
подпути пакет не публикует.

:::callout{type="info"}
Резолвер один на ядро и все пакеты-спутники: пакеты он находит в `package.json`
приложения, компоненты — в их `exports`, поэтому порядок резолверов не важен.
Часть составного компонента (`GrListItem`) приезжает из подпути родителя, а имя,
которого ни один пакет не экспортирует, не резолвится вовсе — вместо импорта
несуществующего пути Vue предупредит о неизвестном компоненте.
:::

## Runtime-адаптер

Резолвер сканирует шаблоны. Директивы, применённые в `render`, JSX или TSX, он
не видит — для них есть `createGranularity`:

```ts title="src/main.ts"
import { createApp } from 'vue'
import { createGranularity } from '@feugene/granularity/vue'
import { GrButton } from '@feugene/granularity/components/GrButton'
import { vHotkey } from '@feugene/granularity/directives'
import App from './App.vue'

createApp(App)
  .use(createGranularity({
    components: [GrButton],
    directives: [{ name: 'hotkey', directive: vHotkey }],
  }))
  .mount('#app')
```

Адаптер сам не импортирует ни одного компонента — он принимает их аргументом.
В бандл попадёт ровно то, что вы передали, и ничего сверх. Обратная сторона того
же свойства: передать сюда «всё» — значит потерять гранулярность, и tree-shaking
не спасёт, потому что импорт сделали вы.

Кроме компонентов и директив адаптер принимает `provides` и `globalProperties` —
это удобный один вход для bootstrap-файла вместо россыпи `app.use(...)`.
