@pgcorp/ui-kit

Typed Vue 3 design system with accessible components, semantic themes, and workbench patterns.
6,648
Install
npm install @pgcorp/ui-kit
Latest Version:0.36.0
Vue:3
License:MIT
Last Updated:Oct 19, 2026
Links: npm
Maintainer: pglyanenko

@pgcorp/ui-kit

Типизированная дизайн-система PGCorp для Vue 3: от базовых controls до полноценных workbench-интерфейсов.

A typed PGCorp design system for Vue 3, spanning foundational controls and complete workbench interfaces.

Vue 3 · TypeScript · exact public exports · 6 themes · MIT

Что входит / What is included

  • Vue Single-File Components с типизированными props, slots, events и exposed methods. Vue Single-File Components with typed props, slots, events, and exposed methods.
  • Controls, navigation, containers, data display, overlays и layout-примитивы. Controls, navigation, containers, data display, overlays, and layout primitives.
  • Таблицы, деревья, редакторы, graph surfaces и составные workbench-компоненты. Tables, trees, editors, graph surfaces, and composed workbench components.
  • Semantic CSS tokens и шесть светлых и тёмных тем. Semantic CSS tokens and six light and dark themes.
  • Контракты keyboard navigation, focus, ARIA, loading, disabled, invalid и error states. Contracts for keyboard navigation, focus, ARIA, loading, disabled, invalid, and error states.
  • Только явные package exports: wildcard и private source imports отсутствуют. Explicit package exports only, with no wildcards or private source imports.

Выборы и рабочие инспекторы / Choices and workspace inspectors

SCheckboxGroup.appearance="plain" | "cards" | "rows" и density задают многократный выбор через общую choice surface и native checkbox owner. SChoiceCards использует ту же визуальную основу с radio semantics. busy, readonly и disabled имеют самостоятельные контракты. columns="auto" у checkbox/radio-групп следует ширине контейнера через общую шкалу колонок; числовое количество остаётся явным. Составные подписи checkbox/switch выравниваются по первой строке общего SField. См. контракт выбора.

SDrawer.density="compact" задаёт компактные поля header/body/footer и центрирует заголовок относительно Close, сохраняя длинный текст, native hitbox, scroll owner и retained content lifetime. SModal.returnFocus и SDrawer.returnFocus предоставляют guarded workflow successor, если открывший элемент удалён; SCollapsiblePanel.getSummaryElement() возвращает настоящий summary owner без дополнительной остановки Tab. returnFocusIntent задаёт явный successor даже при живом opener; opener и null выбирают стандартный возврат и отмену. STextarea.getFocusTarget() предоставляет nullable native destination без изменения focus/selection. См. плотность Drawer и жизненный цикл overlay.

Checkbox groups and radio cards share a substantive choice surface while retaining their distinct native selection semantics. Container-sized columns="auto" uses one shared column foundation while exact numeric column counts retain their explicit contract. Compact drawer density controls shared chrome and content spacing without changing lifetime or modality. Cycle-bound returnFocus supplies an explicit workflow successor when the original opener retires. returnFocusIntent explicitly selects a successor even with a live opener, standard opener restoration, or cancellation. The typed STextarea.getFocusTarget() exposes a nullable destination without changing focus or selection. The linked guides describe states, validation and focus priority in Russian and English.

Асинхронные состояния / Async states

SAsyncStateBlock предоставляет синхронный block/overlay entry без table row/cell модулей и CSS. SAsyncState поддерживает тот же state/slot contract и отдельный standalone table-row с обязательным colspan. Состояния, validation и live announcements принадлежат одному общему owner. В STable и табличном представлении SDataGrid сообщение состояния занимает видимую ширину native viewport и переносится внутри неё при сохранённой горизонтальной прокрутке. Колонки, положение прокрутки и scroll owner сохраняются. См. контракт async-состояний.

SAsyncStateBlock exposes synchronous block/overlay feedback without table row/cell modules or CSS. The comprehensive SAsyncState also supports standalone table rows. Both entries share validation, state presentation and accessibility; the guide separates import closure guarantees from application bundle budgets. In STable and the table presentation of SDataGrid, state feedback wraps within the visible native viewport at any retained horizontal offset. Columns, scroll position, and scroll ownership are preserved.

Закрываемые вкладки и SSR / Closable tabs and SSR

STab #close, #actions и #editor сохраняют Vue owner и состояние через Teleport в keyed targets вне role=tablist. Tab triggers принадлежат одному semantic tablist; auxiliary controls используют его общий scroll/layout owner. Editing сохраняет настоящий disabled tab и размещает редактор вне tablist. Props/slots задают reserve/overlay и hover/always presentation; touch не требует hover. Родитель подтверждает удаление и выбирает активную вкладку. Keyed-перестановка сохраняет mounted auxiliary control, его черновик, выделение и текущий keyboard focus; native Tab order соответствует порядку участников. Focused auxiliary host остаётся подключённым при перестановке соседей: native blur/focus events не подавляются, программный refocus не выполняется. Намеренный внешний focus, blur и focus нового popup сохраняют свою обычную семантику.

SSR-приложение вызывает assembleUiKitSsrTeleports из @pgcorp/ui-kit/ssr после renderToString, затем размещает оставшиеся host Teleports. Контракт сборки и гидратации описан в docs/ssr.md.

Public auxiliary slots preserve their Vue owner and local state outside the semantic tablist. SSR applications assemble package-owned Teleports through @pgcorp/ui-kit/ssr and retain responsibility for their host Teleports. Keyed reordering retains the mounted auxiliary control, draft, selection, and current keyboard focus while native Tab order follows participant order. The focused auxiliary host remains connected while its siblings move; native blur/focus events are not suppressed and no programmatic refocus is performed. Intentional external focus, blur, and a new popup's focus retain their normal semantics. See the SSR contract for assembly and hydration.

Полные option-каталоги / Complete option catalogs

SListbox.virtualization задаёт measured viewport над полным набором options: { viewportHeight, estimatedItemHeight, overscan? }. Keyboard navigation, groups, clear, disabled и multiple selection работают по всему каталогу. Для external focus owner событие active-descendant-change передаёт ID смонтированной option в STextarea.suggestions. Полный контракт и примеры включены в пакет: docs/listbox.md. Активная option при внешнем focus имеет общий видимый focus ring; selection background/check отображают выбранное значение независимо от активного пункта.

SListbox.virtualization provides a measured viewport over the complete option catalog. Keyboard navigation and selection use the full inventory; the active-descendant-change event supplies a mounted option ID to an external focus owner. The package includes the complete contract and examples in docs/listbox.md. The externally active option displays the shared visible focus ring; selection background/check independently represent the selected value.

Связь поля и popup / Field-to-popup relationship

SSelect и SCombobox публикуют aria-controls только при смонтированном доступном popup; aria-expanded отражает его фактическое присутствие. Закрытие и скрытие owner удаляют ссылку, не меняя native trigger и стабильный ID popup при повторном открытии. Нативный aria-activedescendant у SCombobox также ссылается только на отрисованный вариант. Приложение использует публичные props, доступное имя и роль; native ARIA-связями владеет компонент.

SSelect and SCombobox expose aria-controls only for their mounted, admitted popup; aria-expanded reflects its actual presence. Closing or hiding the owner removes the reference while retaining the native trigger and stable popup id across reopening. SCombobox exposes aria-activedescendant only for a rendered option. Applications use public props, names and roles; the component owns native ARIA relationships.

Выбор в toolbar / Toolbar selection

SSelect.appearance="toolbar" отображает выбранное значение и одну стрелку без постоянной видимой рамки или тени. Hover/open используют общую field surface, keyboard focus — общий focus ring. field является значением по умолчанию; swatch предназначен для marker-only выбора цвета. Тип SSelectAppearance экспортируется из того же публичного модуля.

<SSelect
  v-model="model"
  :options="modelOptions"
  appearance="toolbar"
  size="xs"
  aria-label="Модель сессии"
/>

Размеры xs/sm/md задают hitbox 32/36/40 px. Ширина принадлежит layout-контейнеру; длинное выбранное имя усекается, а полные имена доступны в listbox. Toolbar popup не уже trigger и ограничен viewport; обычный field popup совпадает с шириной trigger. Single/multiple selection, группировка и markers используют общий SListbox, без отдельного menu/button-контроллера.

readonly сохраняет Tab stop, но запрещает открытие. disabled исключает control из Tab order; host использует его для недоступного или загружаемого каталога. Отдельного loading prop нет. Переход в disabled/readonly закрывает открываемый или открытый popup, сохраняя model и намеренный внешний focus. При снятии блокировки требуется новое явное открытие.

SSelect.appearance="toolbar" displays the selected value and one chevron with no permanent visible frame or shadow. Hover/open and keyboard focus share the field foundation. The default is field; swatch is the marker-only color presentation. SSelectAppearance is exported from the public component module. Sizes xs/sm/md retain 32/36/40 px hitboxes. The layout container owns width; long selected labels truncate while full option labels remain in the listbox. The toolbar popup is at least as wide as its trigger and viewport-bounded; the default field popup matches trigger width. Selection and grouping use the same SListbox owner. readonly remains tabbable without admitting opening; disabled is the host's unavailable/loading admission policy, not a loading prop. Becoming blocked retires pending/open popup work without changing the model or stealing external focus. Unblocking requires a new explicit opening.

See the selection contract.

Адаптивная строка / Adaptive controls

SControlBar и SControlBarItem измеряют actual demands, сохраняют единственный экземпляр каждого control и предлагают typed overflow projection. Активный control и его popup остаются видимыми; обязательные участники переносятся целиком. SSelect.width="fit" использует фактическое содержимое, width="full" сохраняет ширину поля; controlled open отделён от selection. Композиция, strict identity, доступность и SSR описаны в контракте адаптивной строки.

SControlBar and SControlBarItem measure actual demands and retain each control instance. The overflow slot projects domain actions; active popup owners remain visible and mandatory participants wrap as whole controls. SSelect.width="fit" provides intrinsic width, while full is the default. Controlled open is independent of selection. See the contract above for identity, focus, allocation and SSR requirements.

Измеряемый прогресс / Measured progress

SProgressBar использует один numerical/accessibility owner для shape="linear" и shape="circular". Линейная форма является default. Круговая форма имеет размеры xs/sm/md = 24/28/32 px и требует явный value от 0 до 100 в mode="determinate". showValue выводит округлённое число в центре; aria-valuenow сохраняет точное значение, включая дробную часть. valueText передаёт доступное предметное описание, например tokens/limit. Существующий scoped default slot форматирует только linear label и получает точный value; circular label принадлежит владельцу и отклоняет default slot.

Компактная моноширинная метка принадлежит SProgressBar: общий --font-mono, размеры шрифта xs/sm/md = 8/9/10 px, межсимвольный интервал -0.05em и line-height: normal задаются семантическими токенами --s-progress-ring-value-*. Host-гарнитура должна содержать цифры, ≈ и ? и сохранять читаемость и зазор между полной меткой ≈100 и кольцом во всех трёх размерах. Произвольная гарнитура не гарантирует эту геометрию.

accuracy="estimated" требует valueText и показывает приблизительность. mode="unknown" требует valueText, не принимает value и не сообщает aria-valuenow. Эти состояния статичны. mode="indeterminate" обозначает выполняющуюся операцию, не принимает showValue и учитывает reduced motion. severity управляет semantic palette, а не выводится из процента автоматически.

<SProgressBar
  :accessibility="{ mode: 'label', label: 'Заполнение контекста' }"
  shape="circular"
  size="md"
  :value="37.5"
  value-text="37 500 из 100 000 токенов"
  show-value
  severity="info"
/>
<SProgressBar
  :accessibility="{ mode: 'label', label: 'Заполнение контекста' }"
  shape="circular"
  mode="unknown"
  value-text="Размер контекста недоступен"
  show-value
/>

Для действия показатель помещается в default slot SButton с iconOnly, shape="circle", size="lg", density="default" и appearance="ghost": штатная контрольная область равна 48×48 px, то есть не меньше 44 px. Внутренний показатель использует accessibility.mode="decorative"; действие, доступное имя с точным значением и tooltip bindings принадлежат внешней кнопке. Показатель не создаёт вложенный control или дополнительную остановку Tab.

SProgressBar shares numerical and accessibility ownership across linear and circular shapes. Circular xs/sm/md sizes are 24/28/32 px. Determinate circular progress requires an explicit value in 0–100; central display rounds the percentage while aria-valuenow retains its exact precision. valueText supplies a domain description such as tokens/limit. Estimated progress requires valueText and a visible approximation marker. Explicit unknown progress requires valueText, rejects value, omits aria-valuenow, and remains static. Severity is consumer-selected rather than inferred from a percentage. The existing scoped default slot formats linear labels and receives the exact value. Circular labels are owner-controlled and reject a supplied default slot.

SProgressBar owns compact monospace typography through the shared --font-mono and semantic --s-progress-ring-value-* tokens: xs/sm/md font sizes are 8/9/10 px, letter spacing is -0.05em, and line height is normal. A host font must cover digits, ≈, and ? and retain readability and clearance between the full ≈100 label and the ring at all three sizes. Arbitrary font metrics are not guaranteed to fit this geometry.

For an actionable indicator, place decorative progress in the default slot of a circular SButton with iconOnly, size="lg", default density, and appearance="ghost": its target is 48×48 px, not exactly 44 px. The inner progress is decorative; the button owns focus, the action, the accessible name, and tooltip bindings.

Установка / Installation

npm install @pgcorp/ui-kit @vue/devtools-api vue pinia vue-router
Требование / Requirement Версия / Version
Node.js ^22.22.2 || ^24.15.0 || ^26.0.0
npm >=10.9.2 <12
Vue ^3.5.43
Pinia ^4.0.3
Vue Devtools API ^8.2.1
Vue Router ^5.3.1
Build tool Vite with Vue SFC support

vue, pinia, @vue/devtools-api и vue-router являются peer dependencies. Consumer-приложение владеет их единственными runtime-экземплярами.

vue, pinia, @vue/devtools-api, and vue-router are peer dependencies. The consumer application owns their single runtime instances.

Tailwind/PostCSS принадлежат build-контуру приложения и объявляются его прямыми dev dependencies. / Tailwind/PostCSS belong to the application's build pipeline and must be declared as its direct dev dependencies.

npm install -D tailwindcss@^4.3.3 @tailwindcss/postcss@^4.3.3 postcss@^8.5.28

postcss.config.mjs:

export default { plugins: { '@tailwindcss/postcss': {} } }

Контракт установки: Tailwind CSS с PostCSS. / Setup contract: Tailwind CSS with PostCSS.

Быстрый старт / Quick start

Создайте корневой CSS entrypoint приложения. style.css подключает общую основу кита, а явный @source регистрирует собранные модули пакета:

Create the application's root CSS entrypoint. style.css includes the kit's shared foundation, while an explicit @source registers the built package modules:

@import "tailwindcss";
@import "@pgcorp/ui-kit/style.css";
@source "../node_modules/@pgcorp/ui-kit";

@source обязателен: Tailwind 4 не сканирует dependencies автоматически, а путь разрешается относительно CSS-файла приложения. Runtime-импорт reference.css не требуется. / @source is required because Tailwind 4 does not scan dependencies automatically; the path resolves relative to the application CSS. No runtime reference.css import is required.

SDocBlock загружает полную document typography вместе со своим component-модулем, включая lazy imports. Для собственной HTML-разметки с prose, prose-sm, dark:prose-invert и not-prose используйте tailwind.css вместо style.css: этот entrypoint добавляет полный typography plugin к общей основе.

SDocBlock loads its complete document typography with its component module, including lazy imports. For application-owned HTML using prose, prose-sm, dark:prose-invert, and not-prose, use tailwind.css instead of style.css: that entrypoint adds the complete typography plugin to the shared foundation.

Подключите CSS, Pinia и router в entrypoint приложения:

Import the CSS, Pinia, and router from the application entrypoint:

import { createPinia } from 'pinia'
import { createApp } from 'vue'
import './styles.css'

import App from './App.vue'
import router from './router'

createApp(App)
  .use(createPinia())
  .use(router)
  .mount('#app')

Импортируйте каждый компонент через точный публичный subpath:

Import every component through its exact public subpath:

<script setup lang="ts">
import SPanel from '@pgcorp/ui-kit/shared/containers/SPanel.vue'
import SButton from '@pgcorp/ui-kit/shared/controls/SButton.vue'
</script>

<template>
  <SPanel
    title="Профиль"
    description="Настройки рабочей области"
    :heading-level="2"
    content-layout="stack"
    content-gap="content"
  >
    <SButton appearance="solid" severity="primary">
      Сохранить
    </SButton>
  </SPanel>
</template>

Production build должен разрешать package exports напрямую, без alias на исходный monorepo и без копирования файлов UI-kit.

The production build must resolve package exports directly, without a source monorepo alias or copied UI-kit files.

Локализация / Localization

UI-kit поддерживает en, ru, zh-CN и zh-TW. Без provider используется ru; SUiKitLocaleProvider задаёт локаль для одного Vue subtree и не создаёт дополнительный DOM-элемент. / The UI kit supports en, ru, zh-CN, and zh-TW. Without a provider it uses ru; SUiKitLocaleProvider selects a locale for one Vue subtree without adding a DOM element.

<script setup lang="ts">
import SUiKitLocaleProvider from '@pgcorp/ui-kit/shared/containers/SUiKitLocaleProvider.vue'
import type { UiKitLocale } from '@pgcorp/ui-kit/composables/useUiKitLocale'

const locale: UiKitLocale = 'en'
</script>

<template>
  <SUiKitLocaleProvider :locale="locale">
    <AppWorkspace />
  </SUiKitLocaleProvider>
</template>

Provider переводит только kit-owned visible/assistive copy. Доменные данные и пользовательский контент остаются ответственностью consumer. Вложенные provider наследуют ближайший context и принимают типизированный messages override. / The provider translates kit-owned visible and assistive copy only. Domain data and user content remain consumer-owned. Nested providers inherit the nearest context and accept a typed messages override.

Уведомления / Notifications

SControlledToastViewport отображает readonly application records и возвращает serializable dismiss, action и lifetime-expired intents. Consumer остаётся владельцем массива, порядка, ID и durable history. Режим consumer не создаёт таймеры; режим viewport управляет только presentation lifetime и ставит его на паузу при hover/focus. / SControlledToastViewport renders readonly application records and emits serializable dismiss, action, and lifetime-expired intents. The consumer owns the array, ordering, IDs, and durable history. The consumer mode creates no timers; the viewport mode owns presentation lifetime only and pauses it on hover/focus.

import type { SToastPresentation } from '@pgcorp/ui-kit/shared/data-display/toast'
import SControlledToastViewport from '@pgcorp/ui-kit/shared/data-display/SControlledToastViewport.vue'

SUiKitNotifierProvider маршрутизирует внутренний feedback SCodeBlock, SCopyField, SExpandableText и SDocBlock в typed application-owned sink. Provider не создаёт DOM, Pinia store, IDs, history или timers; async publisher ожидается вызывающим действием. Host связывает intent со своим publisher и SControlledToastViewport. / SUiKitNotifierProvider routes internal feedback from SCodeBlock, SCopyField, SExpandableText, and SDocBlock to a typed, application-owned sink. It creates no DOM, Pinia store, IDs, history, or timers; the calling action awaits an asynchronous publisher. The host connects intents to its publisher and SControlledToastViewport.

import SUiKitNotifierProvider from '@pgcorp/ui-kit/shared/containers/SUiKitNotifierProvider.vue'
import type { SUiKitNotificationIntent, SUiKitNotificationSink } from '@pgcorp/ui-kit/composables/useNotifier'

Контекст выбирается в setup, ближайший provider владеет маршрутом; смена sink действует на следующее уведомление. Контракт и интеграция / Contract and integration. Context is selected during setup; the nearest provider owns routing, and sink replacement applies to the next notification.

При отсутствии provider SToastContainer и useNotifier образуют автономный convenience-контракт для одного Pinia root. Store хранит не более 100 history records и освобождает свои таймеры через $dispose()/$reset(). / Without a provider, SToastContainer and useNotifier provide the autonomous convenience contract for one Pinia root. The store keeps at most 100 history records and releases its timers through $dispose()/$reset().

Безопасные rich documents / Safe rich documents

SRichDocument отображает закрытую versioned SRichDocumentV1 AST без raw HTML, VNodes, callbacks и trust flags. Внешний JSON проверяется через validateRichDocument и validateRichDocumentResources; ошибки содержат stable code и точный node path. Consumer-owned resources связываются по ID, а link/resource/action effects возвращаются как typed intents. Link-node с current: true выражает текущее назначение через aria-current="page". Task lists, deletion, keyboard, sup/sub, disclosure и scoped footnotes имеют собственные serializable nodes; raw markup для этих семантик не требуется. Типизированный semantic profile включает hard breaks, inline/picture media, rich summary/header content, definitions, ruby и структурные таблицы с header associations, spans и footer. STable, SDataGrid и rich tables используют общих native table owners. Якоря и повторные сноски имеют локальный scope, раскрывают целевую секцию и переводят фокус без изменения истории. heading.presentation: 'assistive' сохраняет нативный уровень, содержимое, scoped anchors и IDREFs, визуально скрывая сам heading вне normal flow. Значение по умолчанию — visible; интерактивные потомки в assistive heading отклоняются с точным node path. GFM producer сохраняет скрытый heading сносок и его describedBy references, не передавая raw class. Вложенные блоки адаптируются к ширине контейнера при resize. Длинные строки code fences имеют локальную горизонтальную прокрутку; viewport document сохраняет полную высоту кода.

SRichDocument renders the closed, versioned SRichDocumentV1 AST without raw HTML, VNodes, callbacks, or trust flags. External JSON is checked with validateRichDocument and validateRichDocumentResources; failures include a stable code and exact node path. Consumer-owned resources are joined by ID, and link/resource/action effects are emitted as typed intents. A link node with current: true exposes the current destination through aria-current="page". Task lists, deletion, keyboard, sup/sub, disclosure, and scoped footnotes use dedicated serializable nodes; these semantics never require raw markup. The typed semantic profile includes hard breaks, inline/picture media, rich summary/header content, definitions, ruby, and structural tables with header associations, spans, and a footer. STable, SDataGrid, and rich tables share native table owners. Anchors and repeated footnotes are scoped locally, reveal their target disclosure, and move focus without changing browser history. heading.presentation: 'assistive' retains the native level, content, scoped anchors, and IDREFs while placing the heading outside normal flow. The default is visible; interactive descendants fail at their exact node path. A GFM producer preserves its hidden footnote heading and description references without passing raw classes. Nested blocks adapt to the container width on resize. Long code-fence lines scroll horizontally within the block; the document viewport preserves the complete code height.

SCheckbox presentation="inline" и SField contentFlow="phrasing" сохраняют phrasing content model внутри текста. SLink предоставляет actual native getElement()/focus() и semantic props domId, language, direction, ariaLabel, ariaLabelledby, ariaDescribedby; IDREFs задаются непустыми DOM идентификаторами через один ASCII-пробел без автоматического исправления ввода. variant="document" принимает passive flow content в default slot напрямую внутри native anchor, включая pre/table. Вложенные interactive descendants и tabindex запрещены; document wrap принадлежит самому anchor. / SCheckbox presentation="inline" and SField contentFlow="phrasing" preserve the phrasing content model inside text. SLink exposes the actual native getElement()/focus() and the semantic props listed above; IDREFs are explicit non-empty DOM identities separated by one ASCII space, without input repair. variant="document" accepts passive flow content directly inside the native anchor, including pre/table. Interactive descendants and nested tabindex are rejected; the anchor owns document wrapping.

import SRichDocument from '@pgcorp/ui-kit/shared/data-display/SRichDocument.vue'
import {
  validateRichDocument,
  validateRichDocumentResources,
  type SRichDocumentV1,
} from '@pgcorp/ui-kit/shared/data-display/richDocument'

Code highlighting and KaTeX load through local lazy chunks; SSR uses safe text fallbacks. Полный security, resource, event и versioning contract описан в docs/rich-document.md. / The complete security, resource, event, and versioning contract is documented in docs/rich-document.md.

Области прокрутки / Scroll viewports

SScrollViewport.radius="inherit" согласует native clipping и контур фокуса с округлой оболочкой SPopover; token-sized значения задают углы самостоятельной области. Default none сохраняет прямоугольную форму. Контракт углов и фокуса.

SScrollViewport владеет именованной или явно anonymous scroll областью, осями x/y/both, semantic block size, overscroll policy и root-only keyboard navigation. Событие scroll-state возвращает normalized physical и writing-mode-aware semantic offsets. Exposed API предоставляет focus, scrollToBoundary и reveal, но не открывает DOM owner.

semanticRole="region" — default именованного landmark; group задаёт вложенную область без landmark, в том числе повторяемые журналы с одинаковыми именами. focusPolicy="always" сохраняет выбранную роль и имя; focusPolicy="overflow" добавляет их и Tab stop только при переполнении разрешённой оси. Имя задаётся ровно одним из accessibleLabel и accessibleLabelledby для region/group. semanticRole="none" опускает native role/name и запрещает name props, сохраняя focus/keyboard policy. scrolling="delegated" сохраняет visible natural flow без отдельного owner; disabled использует clip. domId задаёт typed viewport id. blockSize="content" сохраняет естественную высоту, maxBlockSize="none|sm|md|lg" ограничивает её общей шкалой viewport. Таблицы STable, SDataGrid и rich-document используют этот общий scroll owner: пустые и пассивные таблицы доступны клавиатуре при переполнении, вложенные controls сохраняют собственную клавиатуру. При исчезновении переполнения focused root сохраняет tabindex="-1" до blur; он не добавляет остановку Tab и не переносит фокус.

SScrollViewport owns a named or explicitly anonymous scroll area, x/y/both axes, semantic block size, overscroll policy, and root-only keyboard navigation. The scroll-state event provides normalized physical and writing-mode-aware semantic offsets. Its exposed API provides focus, scrollToBoundary, and reveal without exposing the DOM owner.

semanticRole="region" is the default named landmark; group represents a nested non-landmark area, including repeated logs with identical names. focusPolicy="always" retains the selected role and name; focusPolicy="overflow" adds them and a Tab stop only when an enabled axis overflows. Exactly one of accessibleLabel and accessibleLabelledby supplies the region/group name. semanticRole="none" omits native role/name and rejects naming props while retaining focus/keyboard policy. scrolling="delegated" keeps visible natural flow without a local owner; disabled clips. domId supplies the typed viewport id. blockSize="content" retains intrinsic height; maxBlockSize="none|sm|md|lg" uses the shared viewport limit scale. STable, SDataGrid, and rich-document tables compose this scroll owner: overflowing empty and passive tables are keyboard reachable, while nested controls retain their own keyboard behavior. When overflow disappears, a focused root retains tabindex="-1" until blur; it adds no Tab stop and does not move focus.

resizeBoundaries?: readonly SScrollViewportResizeBoundary[] задаёт physical edge и обязательный canonical hitbox соседнего boundary-разделителя. Default [] не резервирует clearance. Постоянная пассивная sizing frame включает physical padding (hitbox token - 1px) / 2 в свой внешний logical size/max/fill budget; native viewport сохраняет domId, consumer data-*, ARIA, focus, keyboard, API и observer. $el обозначает frame, не native scroll owner; для команд используется exposed API, для hooks — domId или data-*. Frame не добавляет landmark, Tab stop или второй scroll controller. / resizeBoundaries?: readonly SScrollViewportResizeBoundary[] declares physical edge and required canonical hitbox for an adjacent boundary divider. Default [] reserves no clearance. The stable passive sizing frame includes physical padding (hitbox token - 1px) / 2 in its outer logical size/max/fill budget; the native viewport retains domId, consumer data-*, ARIA, focus, keyboard, API and its observer. $el identifies the frame, not the native scroll owner; commands use the exposed API and hooks use domId or data-*. The frame adds no landmark, Tab stop or second scroll controller.

import SScrollViewport from '@pgcorp/ui-kit/shared/data-display/SScrollViewport.vue'
import type {
  SScrollViewportApi,
  SScrollViewportResizeBoundary,
  SScrollViewportSemanticRole,
  SScrollViewportState,
} from '@pgcorp/ui-kit/shared/data-display/scrollViewport'

SScrollViewportResizeBoundary доступен также из корневого @pgcorp/ui-kit. / SScrollViewportResizeBoundary is also exported from @pgcorp/ui-kit.

Вложенные inputs, editors и terminals сохраняют собственную клавиатуру, selection и native copy. Полный axes, state, API и lifecycle contract описан в docs/scroll-viewport.md. / Nested inputs, editors, and terminals retain their keyboard behavior, selection, and native copy. The complete axes, state, API, and lifecycle contract is documented in docs/scroll-viewport.md.

SPanel компонует canonical owner через scrollBehavior, contentAccessibility и contentFocusPolicy, без отдельной реализации focus или клавиатуры. Built heading задаёт точное SSR-safe имя; untitled/custom header используют anonymous owner либо явное имя приложения. / SPanel composes the canonical owner through scrollBehavior, contentAccessibility and contentFocusPolicy, without separate focus or keyboard mechanics. A built heading provides an exact SSR-safe name; untitled/custom headers use an anonymous owner or an explicit application name. contentGeometry="flow | fill" независимо задаёт natural или bounded высоту содержимого; fill требует grow и конечной высоты внешней композиции. При отсутствии prop сохраняется grow+child → fill, остальные комбинации → flow. SLeftSidebar явно выбирает fill для всех режимов прокрутки. / contentGeometry="flow | fill" independently selects natural or bounded content height; fill requires grow and a finite outer composition height. Omission preserves grow+child → fill and flow for other combinations. SLeftSidebar explicitly selects fill for every scrolling mode. SPanel.resizeBoundaries передаёт тот же canonical type только собственному content viewport. self и auto+grow сохраняют его scrolling; body делегирует scrolling внешнему owner, child — вложенному reader. Для nested reader clearance задаётся на самом reader через его публичный API, не наследуется из prop панели. Padding, end inset и contentGeometry сохраняют собственные контракты. / SPanel.resizeBoundaries forwards the same canonical type only to its own content viewport. self and auto+grow retain its scrolling; body delegates to an outer owner and child to a nested reader. Nested-reader clearance is declared on that reader's public API, not inherited from the panel prop. Padding, end inset and contentGeometry retain their own contracts. Полный контракт / Full contract: docs/panel.md.

Карта компонентов / Component map

Группа / Group Назначение / Purpose Примеры exports / Example exports
Layout App shell, dock regions, stacks, workbench layout layout/SAppShell.vue, layout/SStack.vue, layout/SWorkbenchLayout.vue
Controls Buttons, fields, inputs, switches, selects, comboboxes, menus shared/controls/SButton.vue, shared/controls/SSwitch.vue, shared/controls/SCombobox.vue
Containers Panels, modal, drawer, popover, sidebars shared/containers/SPanel.vue, shared/containers/SModal.vue, shared/containers/SPopover.vue
Data display Tables, charts, metric cards, scroll regions, chips, status, progress, code, JSON, tooltips shared/data-display/STable.vue, shared/data-display/SChart.vue, shared/data-display/SScrollViewport.vue
Navigation Tabs, breadcrumbs, catalog navigation, wizard shared/navigation/STabs.vue, shared/navigation/SBreadcrumbs.vue, shared/navigation/SWizardSteps.vue
Complex surfaces Tree, Kanban, data grid, SQL editor, graph shared/complex/STree.vue, shared/database/SDataGrid.vue, shared/graph/SGraphViewport.vue
Runtime Themes, variants, icons, composables, stores theme, variants, icons, composables/useNotifier, stores/useNotifierStore

Полный машинно-читаемый каталог находится в package.json#exports. IDE и TypeScript показывают доступные subpaths при импорте.

The complete machine-readable catalog is declared in package.json#exports. IDEs and TypeScript expose the available subpaths during import.

Переносимые строки действий / Wrapping action rows

SStack direction="row" wrap="wrap" align="center" gap="control" размещает действия рядом и переносит целые controls при нехватке ширины. wrap принимает nowrap | wrap, default — nowrap; тип SStackWrap экспортируется из layout/SStack.vue. DOM-порядок и Tab-порядок сохраняются. Stack не задаёт ширину, hitbox или типографику controls. Длинная подпись кнопки использует её публичный labelOverflow="wrap". При direction="column" перенос требует ограниченной высоты контейнера, как в нативном flex layout.

SStack direction="row" wrap="wrap" align="center" gap="control" places actions side by side and wraps whole controls when inline space is insufficient. wrap accepts nowrap | wrap, defaulting to nowrap; SStackWrap is exported from layout/SStack.vue. DOM and Tab order remain unchanged. The stack does not set control widths, hitboxes, or typography. A long button label uses its public labelOverflow="wrap". With direction="column", wrapping requires a bounded container height, following native flex layout.

Пагинация разделяет полный диапазон и группу коротких действий. Контракт и consumer example: Пагинация листов и реестров.

Pagination keeps the full range separate from its short action group. The supported composition and consumer example are documented in Sheet and registry pagination.

Реестры и свободный ввод / Registries and custom values

STable владеет служебными колонками выбора и раскрытия. Consumer передаёт controlled keys и доменный контент, не копирует checkbox, disclosure или CSS:

STable owns selection and disclosure columns. Consumers provide controlled keys and domain content instead of copying checkboxes, disclosures, or CSS:

import type {
  STableColumn,
  STableSort,
} from '@pgcorp/ui-kit/shared/data-display/table'
<STable
  v-model:selected-row-keys="selectedRowKeys"
  v-model:expanded-row-keys="expandedRowKeys"
  :columns="columns"
  :data="rows"
  :get-row-key="(row) => row.id"
  :get-row-label="(row) => row.name"
  selection-mode="multiple"
  aria-label="Поставщики"
  @row-contextmenu="openRowMenu"
>
  <template #row-details="{ row }">
    <SupplierDetails :supplier="row" />
  </template>
</STable>

Для служебных столбцов доступны headerPresentation: 'assistive', width: '2xs' и width: 'content'. SCombobox принимает новое строковое значение через allow-custom-value; SPageHeader сочетает appearance="bare" с title-presentation="assistive" для доступного заголовка без визуальной полосы.

Utility columns support headerPresentation: 'assistive', width: '2xs', and width: 'content'. SCombobox accepts a new string value with allow-custom-value; SPageHeader combines appearance="bare" with title-presentation="assistive" for an accessible zero-chrome heading.

Составная сортировка использует controlled v-model:sorts: consumer задаёт sort-mode="multiple" и передаёт ordered STableSort[]. Клик по заголовку добавляет критерий, меняет направление или удаляет его; номер возле заголовка показывает приоритет. Таблица не переставляет строки самостоятельно.

Multi-column sorting uses controlled v-model:sorts: the consumer sets sort-mode="multiple" and provides an ordered STableSort[]. Header activation adds a descriptor, changes its direction, or removes it; the visible number is the priority. The table never reorders rows on its own.

Последовательные действия заголовков формируют внутренний intent draft до подтверждения controlled prop. Consumer может применить sort/sorts через store или асинхронный render-cycle: второй критерий не теряет первый. Любое authoritative обновление prop становится новой базой следующих действий.

Sequential header actions update an internal intent draft until the controlled prop is acknowledged. Consumers may apply sort or sorts through a store or an asynchronous render cycle without losing an earlier criterion. Any authoritative prop update becomes the base for subsequent actions.

<STable
  v-model:sorts="sorts"
  :columns="columns"
  :data="sortedRows"
  :get-row-key="(row) => row.id"
  sort-mode="multiple"
  aria-label="Позиции прайс-листа"
/>

Графики и метрики / Charts and metrics

SChart является единым semantic owner для line, area, grouped/stacked bar, pie и donut. Размеры compact, sm, md, lg, оси, сетка, легенда, маркеры, подписи, auto/fixed domain и таблица данных задаются typed props. Компонент пересчитывает SVG-геометрию по фактическому контейнеру, сохраняет читаемую типографику и переносит длинные подписи категорий на узкой ширине.

SChart is the single semantic owner for line, area, grouped/stacked bar, pie, and donut charts. Typed props control compact, sm, md, and lg sizes, axes, grid, legend, markers, labels, auto/fixed domains, and the data table. The component recomputes SVG geometry for its actual container, preserves readable typography, and wraps long category labels at narrow widths.

Форматированные подписи шкалы Y участвуют в расчёте поля графика. Крайние подписи X привязаны к границам plot-area, поэтому x-labels="auto" сохраняет полные начальное и конечное значения и не обрезает их краем SVG. Если ширины контейнера физически недостаточно, визуальная подпись сокращается, а полное значение остаётся в tooltip и доступной таблице данных.

Formatted Y-axis labels participate in plot-gutter calculation. Endpoint X labels are anchored to the plot boundaries, so x-labels="auto" preserves the full first and last values instead of clipping them at the SVG edge. When the container is physically too narrow, the visual label is shortened while the full value remains available through the tooltip and accessible data table.

import type {
  SChartCategory,
  SChartSeries,
} from '@pgcorp/ui-kit/shared/data-display/chart'
<SChart
  type="area"
  :categories="categories"
  :series="series"
  :accessibility="{
    mode: 'label',
    label: 'Нагрузка VPN по часам',
    description: 'Входящий и исходящий трафик',
  }"
  x-axis-label="Час"
  y-axis-label="Мбит/с"
  legend="top"
/>

Для компактного тренда SMetricCard предоставляет слот #chart. Декоративный график внутри уже озвученной карточки использует accessibility.mode="decorative" и data-table-presentation="none"; содержательный самостоятельный график сохраняет default assistive data table.

SMetricCard exposes #chart for a compact trend. A decorative chart inside an already labelled card uses accessibility.mode="decorative" with data-table-presentation="none"; a meaningful standalone chart keeps the default assistive data table.

Сравнение исходников / Source diff

SDiffViewer является semantic owner для exact-text diff. before.text и after.text сравниваются без нормализации переводов строк или содержимого; label, revision и language являются metadata. layout="unified" сохраняет единый горизонтальный scroll для длинных строк, layout="split" показывает равные колонки с переносом кода. viewport использует те же semantic размеры, что и code surfaces.

SDiffViewer is the semantic owner for exact-text diff. before.text and after.text are compared without newline or payload normalization; label, revision, and language are metadata. layout="unified" preserves one horizontal scroll surface for long lines, while layout="split" renders equal columns with wrapped code. viewport uses the same semantic sizes as the code surfaces.

import type {
  SDiffResult,
  SDiffSource,
} from '@pgcorp/ui-kit/shared/data-display/diff'
import SDiffViewer from '@pgcorp/ui-kit/shared/data-display/SDiffViewer.vue'
<SDiffViewer
  v-model:expanded-gap-ids="expandedGapIds"
  :before="baseSource"
  :after="currentSource"
  :context-lines="3"
  layout="split"
  viewport="expanded"
  ariaLabel="Изменения конфигурации"
  @computed="handleComputed"
  @retry="handleRetry"
/>

contextLines задаёт число видимых неизменённых строк вокруг каждого hunk. Без expandedGapIds компонент владеет раскрытием gap; с v-model:expanded-gap-ids consumer применяет intent как controlled state. computed передаёт валидированный SDiffResult, retry сообщает о повторном запуске после ошибки. Для синхронного вычисления вне интерактивной поверхности экспортируется computeDiff(before, after, options).

contextLines selects the unchanged lines around each hunk. Without expandedGapIds, the component owns gap expansion; with v-model:expanded-gap-ids, the consumer applies expansion intents as controlled state. computed provides a validated SDiffResult, and retry reports a retry after an error. computeDiff(before, after, options) is exported for synchronous computation outside an interactive surface.

Строки виртуализируются независимо от размера source. Дорогие интерактивные вычисления выполняются через управляемый worker: смена source отменяет и освобождает прежнюю работу, unmount освобождает активный worker, а результат неактуального поколения не попадает в UI. Consumer не создаёт worker и не управляет его жизненным циклом.

Lines are virtualized independently of source size. Expensive interactive computations use a managed worker: source replacement cancels and releases the previous job, unmount releases the active worker, and stale-generation results never reach the UI. Consumers neither create the worker nor manage its lifecycle.

Действия chip и адаптивный header панели / Chip actions and responsive panel header

SChip принимает typed surface contract для статичного, action и link режимов и эмитит activate. Закрытие остаётся отдельным sibling-действием, не вложенной кнопкой. SPanel использует header-layout="auto" для переноса группы действий под полный заголовок, если собственная ширина heading/actions и semantic gap не помещаются в content width шапки. При достаточном месте обе группы остаются inline; фиксированного breakpoint нет. Описание переносится в выделенной heading ширине и не задаёт intrinsic ширину группы. Действия внутри группы могут переноситься; inline сохраняет одну внешнюю строку, а stacked всегда размещает действия под заголовком. SLeftSidebar.headerLayout передаёт тот же режим SPanel, с default auto; компактная шапка с одной action использует inline без consumer CSS.

SChip accepts the typed surface contract for static, action, and link modes and emits activate. Closing remains a separate sibling action rather than a nested button. SPanel uses header-layout="auto" to move actions below the complete title when the intrinsic heading/action widths and semantic gap do not fit the header's content width. Both groups remain inline when they fit; there is no fixed breakpoint. The description wraps within the allocated heading width without defining its intrinsic width. Actions within their group can wrap; inline retains one outer row and stacked always places actions below the heading. SLeftSidebar.headerLayout delegates the same axis to SPanel, defaulting to auto; a compact title and action may use inline without consumer CSS.

Action/link chip всегда сохраняет видимый интерактивный контур, включая severity="secondary" на subtle-поверхности; static chip не получает ложного affordance. Для строк и карточек с дополнительными controls используйте SActionCard#actions или SActionListItem#actions. actions-layout/ trailing-layout задаёт inline, stacked или container-responsive композицию. Основное действие и trailing controls остаются sibling-элементами внутри одной визуальной поверхности, без вложенных интерактивных элементов и дублирования рамки.

Action and link chips always retain a visible interactive outline, including a secondary chip on a subtle surface; static chips do not gain a false action affordance. For rows and cards with additional controls, use SActionCard#actions or SActionListItem#actions. The actions-layout and trailing-layout props select inline, stacked, or container-responsive composition. The primary action and trailing controls remain siblings inside a single visual surface, with no nested interactive elements or duplicated chrome.

SActionListItem предоставляет постоянный #leading, краткий #metadata и actionsVisibility="interaction" для обмена metadata/actions в одной trailing-области. Keyboard, touch и недоступный primary сохраняют прямой доступ к действиям. tooltipTrigger у List/Card передаётся canonical activation owner. trailingWidth="stable" — default: exchange-область резервирует ширину обоих наборов без суммирования. trailingWidth="current" при interaction учитывает только текущие metadata или actions в normal flow; скрытый набор не резервирует место. Смена policy сохраняет mounted controls, focus, draft и overlay lifetime; вложенные строки задают policy независимо. Тип ActionListItemTrailingWidth экспортируется из shared/data-display/SActionListItem.vue. appearance="card" | "plain" | "bare" у SActionListItem и SActionCard передаётся владельцу SInteractiveSurface: default card задаёт карточную поверхность, plain — прозрачный idle-фон и рамку, bare — отсутствие рамки и clipping. Выделение, tone, hover/focus и lifetime слотов сохраняются. SActionList.surface="plain" управляет списком, а не видом его строк. titleOverflow="wrap" показывает полный буквальный title, включая пробелы и переводы строк, в доступной ширине; default ellipsis сохраняет однострочное усечение. Политика принадлежит собственному title, не вложенным строкам. Публичный тип ActionListItemTitleOverflow экспортируется из .vue subpath. В stacked и узкой responsive-композиции статусы и действия переносятся по ширине карточки; полные подписи отдельных действий сохраняют собственную строку. API, размеры, RTL и пример композиции: action-list-item.md. #body="{ disabled }" у List/Card размещает полноширинный редактор под primary/actions без вложения в activation. Высота определяется содержимым; consumer владеет раскрытием и явно передаёт disabled полям. С body fill означает минимальное заполнение, а внешний viewport владеет прокруткой. SActionList.maxHeight="none" — default: список сохраняет естественную высоту и видимое переполнение, не создаёт собственный scroll container и не удерживает wheel-прокрутку над обычными строками внутри внешнего viewport. sm/md/lg ограничивают высоту, включают собственную прокрутку списка и удерживают её цепочку на границе.

SActionListItem provides persistent #leading, short #metadata and actionsVisibility="interaction" to exchange metadata/actions within one trailing region. Keyboard, touch and unavailable primary retain direct action access. List/Card tooltipTrigger forwards to the canonical activation owner. trailingWidth="stable" is the default: the exchange region reserves both regions' widths without adding them. With interaction, trailingWidth="current" uses only the current metadata or actions in normal flow; the hidden region reserves no space. Policy changes retain mounted controls, focus, drafts and overlay lifetime; nested rows choose their own policy. The public ActionListItemTrailingWidth type is exported from shared/data-display/SActionListItem.vue. SActionListItem and SActionCard forward appearance="card" | "plain" | "bare" to SInteractiveSurface: default card supplies card chrome, plain keeps a transparent idle background and border, and bare removes the border and clipping. Selection, tone, hover/focus and slot lifetime remain intact. SActionList.surface="plain" controls the list, not its rows' appearance. titleOverflow="wrap" displays the complete literal title, preserving spaces and line breaks within the available width; default ellipsis keeps single-line truncation. The policy belongs to the own title, not nested rows. The public ActionListItemTitleOverflow type is exported from the .vue subpath. Stacked and narrow responsive compositions wrap status regions and actions to the card width while preserving complete ordinary action labels. API, sizing, RTL and composition example: action-list-item.md. List/Card #body="{ disabled }" places a full-width editor below primary/actions, outside activation. Height is content-driven; consumers own expansion and bind disabled to their fields. With body, fill is minimum-fill and an outer viewport owns scrolling. SActionList.maxHeight="none" is the default: the list keeps its natural height and visible overflow, does not create its own scroll container, and lets wheel input over ordinary rows reach the outer viewport. sm/md/lg cap height, enable the list's own scrolling, and contain its scroll chain at the boundary.

Лёгкий и редактируемый код / Lightweight and editable code

SCodeBlock загружает CodeMirror отдельным async chunk только для presentation="editor". Для read-only текста, логов и конфигурации используйте presentation="plain": header, copy action, accessible name, line numbers и semantic viewport сохраняются, а editor/language runtime не загружается.

SCodeBlock loads CodeMirror in a separate async chunk only for presentation="editor". Use presentation="plain" for read-only text, logs, and configuration: the header, copy action, accessible name, line numbers, and semantic viewport remain available without loading the editor/language runtime.

Read-only plain и highlighted используют именованную group на native pre: требуется ровно один ariaLabel/ariaLabelledby, включая пустой вывод. Текст остаётся доступным внутри группы; номера строк скрыты от screen reader. code.textContent сохраняет буквальный source, включая пустой текст и завершающие newline. Номера строк расположены вне code; высота пустых строк задаётся layout без текстовых struts. DOM Range и кнопка Copy возвращают исходник точно. Нативное выделение и keyboard Copy следуют rendered-text правилам браузера для pre; номера или синтетические символы исключены. focus() и Tab фокусируют саму поверхность. PageDown/Home/End прокручивают её нативно при переполнении выбранного viewport, без дополнительных tab stops. Scroll-контейнер удерживает boundary-прокрутку внутри себя; Tab выходит к следующему control. Отрисовка текста после раскрытия и возврата в viewport принадлежит SCodeBlock; потребителю не нужны чтение геометрии, принудительный focus или CSS-переопределения.

Read-only plain and highlighted expose a named group on the native pre. Exactly one ariaLabel/ariaLabelledby is required, including empty output. Text remains readable within the group; line numbers are hidden from screen readers. focus() and Tab focus the surface itself. PageDown/Home/End use native scrolling when its viewport overflows, without additional tab stops. The scroll container contains boundary scrolling; Tab exits to the next control. code.textContent retains the literal source, including empty text and trailing newlines. Line numbers live outside code; empty-row height is layout-owned, without text struts. DOM Range and the Copy action return the exact source. Native selection and keyboard Copy follow the browser's rendered-text rules for pre, excluding line numbers and synthetic characters. SCodeBlock owns text paint after disclosure and viewport return; consumers do not need geometry reads, forced focus, or CSS overrides.

<SCodeBlock
  :code="diagnostic"
  :language="{ mode: 'explicit', id: 'text' }"
  presentation="plain"
  aria-label="Диагностика подключения"
/>

Plain presentation допускает только readOnly=true и явно отклоняет editor-only interceptKeys, nextSelectionPos и applySelection.

Plain presentation accepts readOnly=true only and explicitly rejects the editor-only interceptKeys, nextSelectionPos, and applySelection contracts.

headerActions и явный headerActionContext добавляют consumer-owned действия к шапке SCodeBlock; headerActionsPresentation="menu" использует общее меню. header-action сохраняет точные code/language и source identity. Для SRichDocument используются sparse codeActions и codeActionSource принятого snapshot; code-action адресует конкретный fence. Типы и occurrence helper экспортируются из shared/data-display/codeActions. Выполнение остаётся у приложения. Контракт: rich-document.md.

SCodeBlock header actions use an explicit source context and inline/menu presentation; SRichDocument binds actions to exact accepted-snapshot code occurrences through its sidecar API. Typed intents retain literal code/language and source identity. Execution remains application-owned.

SCodeEditor сохраняет одну native view/history при изменении theme, language, capabilities и controlled source. Mixed LF/CRLF/CR и literal UTF-16 offsets сохраняются в undo/redo, включая разные длины и separator-only replacements. captureSource() и applyEdits() предоставляют serializable optimistic batch с одной isolated Undo/Redo transaction, mapped selection и защитой от stale source. Контракт: code-editor.md.

SCodeEditor retains one native view/history across configuration and controlled source updates; undo/redo preserves mixed literal separators and UTF-16 coordinates, including length and separator-only replacements. captureSource() and applyEdits() provide serializable optimistic batches with one isolated Undo/Redo transaction, mapped selections and stale-source guards.

--font-mono задаёт общую гарнитуру исходного текста редактора и diff. SCodeEditor наследует токен через CodeMirror scroller для контента и номеров строк; SDiffViewer использует тот же токен для строк и gutter. Базовый стек включает IBM Plex Mono и системные моноширинные гарнитуры macOS, Windows и Linux. Приложение задаёт гарнитуру на host-контейнере без .cm-* CSS.

--font-mono owns the editor and diff source family. SCodeEditor inherits the token through the CodeMirror scroller for text and line numbers; SDiffViewer uses it for lines and gutters. The default stack includes IBM Plex Mono and platform monospace families for macOS, Windows and Linux. Applications configure the host token without .cm-* CSS overrides.

Принятые матрицы и команды редактора / Accepted matrices and editor commands

SDataGrid.mode="matrix" показывает принятую readonly страницу буквальных строк, используя общих table/scroll owners и bounded виртуализацию обеих осей. Host сохраняет source, paging и чтение файлов; props реестра и редактирования не смешиваются с matrix API. Полное значение active cell доступно через SKeyValueGrid.valueWhitespace="preserve". Контракт и пример: data-grid.md.

SDataGrid.mode="matrix" presents an accepted read-only literal-string page through shared table/scroll owners and bounded two-axis virtualization. Source, paging and file reads remain host-owned; records/editing props cannot mix with matrix APIs. The active value uses SKeyValueGrid.valueWhitespace="preserve". See the data-grid contract.

Detect-язык редактора допускает unknownLanguage="plain-text" для неизвестных текстовых файлов, сохраняя parsers поддерживаемых языков. Публичный shared/codeLanguages предоставляет immutable getCodeLanguageCatalogue() с canonical id, aliases и extensions для 82 языков миграционного каталога и отдельного Octave. Неоднозначное .pp требует явного языка; .m обозначает Objective-C. Подсветка использует CodeMirror без runtime Monaco, workers или language services. Контракт: code-languages.md. capabilities.keyInterceptorPolicy="always" разрешает host-команды в focused locked editor без разрешения ввода. / Editor detection accepts explicit unknownLanguage="plain-text" for unknown text files while retaining supported parsers; capabilities.keyInterceptorPolicy="always" permits host commands in a focused locked editor without unlocking input.

Пассивный текст и подписи ячеек / Passive text and cell labels

SText принимает точный text, включая пустую строку и пробелы. typography="body" | "title" | "label" | "meta" задаёт визуальную роль; default body не создаёт heading semantics. overflow="wrap" сохраняет переносы и полное содержимое, ellipsis ограничивает представление одной строкой, сохраняя исходный текст в DOM. monospace меняет только семейство шрифта и по умолчанию выключен. Пассивный span не создаёт роль или Tab stop. Body/title/label используют 14/24 px, meta — 12/20 px на root 16 px; типографические роли сохраняются при monospace. Цвет принадлежит --s-text-body-foreground, --s-text-title-foreground (также для label) и --s-text-meta-foreground. Внутри SButton текст всех ролей следует цвету пассивного содержимого кнопки, включая hover/pressed/focus, без изменения размера и начертания.

Ячейка с названием и путём использует SStack gap="none" fill="container", SText с typography="label" и typography="meta" overflow="ellipsis". Для полного пути при hover и keyboard focus композиция использует STooltip и STooltipAnchor width="full" с одним доступным именем. Стек занимает доступную ширину anchor. STable сохраняет собственный focus owner строки; SText не добавляет остановок табуляции. Внутри интерактивного предка используется split-контракт focusOwner / pointerTarget: focus принадлежит предку, а SText.getElement() связывает пассивную цель. Обёртка STooltipAnchor внутри кнопки не применяется.

SText accepts exact text, including an empty string and whitespace. typography="body" | "title" | "label" | "meta" selects visual typography; the default body does not create heading semantics. The default overflow="wrap" preserves line breaks and full content; ellipsis presents one bounded line while retaining the original DOM text. monospace changes only the font family and defaults to false. The passive span adds neither a role nor a Tab stop. Body/title/label use 14/24 px and meta uses 12/20 px at a 16 px root; monospace retains the selected typography metrics. Color belongs to --s-text-body-foreground, --s-text-title-foreground (also for label), and --s-text-meta-foreground. Inside SButton, every role follows the button's passive-content foreground across hover/pressed/focus without changing its size or weight.

Compose a name/path cell with SStack gap="none" fill="container", a label SText, and a meta SText overflow="ellipsis". STooltip with a full-width STooltipAnchor provides one named focus stop for the complete path. The stack explicitly fills the anchor's available width. STable retains its row focus owner; SText adds no tab stops. Inside an interactive ancestor, use the existing split focusOwner / pointerTarget contract and SText.getElement() for the passive target; the ancestor retains focus ownership, without a nested tooltip anchor.

Пустое рабочее пространство / Empty workspace

SEmptyState presentation="hero" размещает декоративную иконку, заголовок, описание и действия вертикально по центру. Компонент занимает доступную ширину без рамки, фона, margin, p

Related Packages

@ecoma-io/loom

An Application Interface System for Vue — accessible components, design tokens a...

2,445 5
@mart-booking/ui

MART UI — Vue 3 component library built on Reka UI

2,119
@dskripchenko/ui

Vue 3 component, token, and theme library on CSS custom properties

6,792 0
@shtabapp/ui-kit

Vue 3 component library by Shtab — 30+ typed components, design tokens, light/da...

10,050
@dillingerstaffing/strand-vue

Strand UI - Vue 3 component library built on the Strand Design Language

15,710 0

Version History

Version Released License
0.36.0 MIT
0.35.0 MIT
0.34.2 MIT
0.34.1 MIT
0.34.0 MIT