@pgcorp/ui-kit 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +188 -9
  2. package/docs/getting-started.md +135 -0
  3. package/docs/public-api.md +53 -4
  4. package/docs/rich-document.md +216 -0
  5. package/docs/scroll-viewport.md +167 -0
  6. package/package.json +103 -50
  7. package/src/components/layout/SAppShell.vue +3 -1
  8. package/src/components/layout/SDockRegion.vue +5 -3
  9. package/src/components/layout/SWorkbenchPanelShell.vue +4 -2
  10. package/src/components/shared/_internal/SDialogSurface.css +5 -0
  11. package/src/components/shared/_internal/SDialogSurface.vue +3 -1
  12. package/src/components/shared/_internal/SToastItem.css +4 -0
  13. package/src/components/shared/_internal/SToastItem.vue +44 -8
  14. package/src/components/shared/_internal/STreeNode.vue +24 -11
  15. package/src/components/shared/complex/SKanbanLane.vue +4 -2
  16. package/src/components/shared/containers/SCloudChromeRow.vue +3 -2
  17. package/src/components/shared/containers/SDrawer.vue +5 -2
  18. package/src/components/shared/containers/SModal.vue +5 -2
  19. package/src/components/shared/containers/SPanel.vue +4 -2
  20. package/src/components/shared/containers/SPopover.vue +7 -3
  21. package/src/components/shared/containers/SUiKitLocaleProvider.vue +57 -0
  22. package/src/components/shared/controls/SButton.css +14 -10
  23. package/src/components/shared/controls/SCombobox.vue +8 -6
  24. package/src/components/shared/controls/SComboboxTrigger.css +7 -4
  25. package/src/components/shared/controls/SComboboxTrigger.vue +5 -2
  26. package/src/components/shared/controls/SContextToggleButton.vue +6 -4
  27. package/src/components/shared/controls/SDragHandle.vue +8 -4
  28. package/src/components/shared/controls/SDropdownMenu.vue +7 -4
  29. package/src/components/shared/controls/SInputText.css +5 -4
  30. package/src/components/shared/controls/SListbox.vue +4 -2
  31. package/src/components/shared/controls/SSelect.vue +4 -2
  32. package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +4 -2
  33. package/src/components/shared/data-display/SChart.vue +7 -5
  34. package/src/components/shared/data-display/SChip.vue +3 -1
  35. package/src/components/shared/data-display/SCodeBlock.css +4 -0
  36. package/src/components/shared/data-display/SCodeBlock.vue +42 -20
  37. package/src/components/shared/data-display/SCodeEditor.css +17 -0
  38. package/src/components/shared/data-display/SCodeEditor.vue +173 -8
  39. package/src/components/shared/data-display/SCodeSearchPanel.vue +48 -33
  40. package/src/components/shared/data-display/SCodeWorkspaceEditor.css +13 -0
  41. package/src/components/shared/data-display/SCodeWorkspaceEditor.vue +355 -0
  42. package/src/components/shared/data-display/SControlledToastViewport.css +89 -0
  43. package/src/components/shared/data-display/SControlledToastViewport.vue +205 -0
  44. package/src/components/shared/data-display/SCopyField.vue +12 -10
  45. package/src/components/shared/data-display/SDiffViewer.css +122 -0
  46. package/src/components/shared/data-display/SDiffViewer.vue +499 -0
  47. package/src/components/shared/data-display/SDocBlock.vue +99 -71
  48. package/src/components/shared/data-display/SExpandableText.vue +10 -8
  49. package/src/components/shared/data-display/SJsonTree.vue +4 -2
  50. package/src/components/shared/data-display/SLinkedSystemsList.vue +4 -2
  51. package/src/components/shared/data-display/SMessage.vue +6 -6
  52. package/src/components/shared/data-display/SRichDocument.css +8 -0
  53. package/src/components/shared/data-display/SRichDocument.vue +65 -0
  54. package/src/components/shared/data-display/SScrollViewport.css +53 -0
  55. package/src/components/shared/data-display/SScrollViewport.vue +470 -0
  56. package/src/components/shared/data-display/STable.vue +38 -19
  57. package/src/components/shared/data-display/SToastContainer.vue +22 -17
  58. package/src/components/shared/data-display/_internal/SCodeTokenLine.css +72 -0
  59. package/src/components/shared/data-display/_internal/SCodeTokenLine.vue +79 -0
  60. package/src/components/shared/data-display/_internal/SRichBlockNode.css +85 -0
  61. package/src/components/shared/data-display/_internal/SRichBlockNode.vue +282 -0
  62. package/src/components/shared/data-display/_internal/SRichInlineNode.css +6 -0
  63. package/src/components/shared/data-display/_internal/SRichInlineNode.vue +76 -0
  64. package/src/components/shared/data-display/_internal/SRichMath.css +42 -0
  65. package/src/components/shared/data-display/_internal/SRichMath.vue +102 -0
  66. package/src/components/shared/data-display/codeEditor.ts +200 -0
  67. package/src/components/shared/data-display/codeWorkspace.ts +233 -0
  68. package/src/components/shared/data-display/diff.ts +541 -0
  69. package/src/components/shared/data-display/richDocument.ts +876 -0
  70. package/src/components/shared/data-display/scrollViewport.ts +47 -0
  71. package/src/components/shared/data-display/toast.ts +194 -0
  72. package/src/components/shared/database/SDataGrid.vue +7 -4
  73. package/src/components/shared/database/SSqlEditor.vue +39 -39
  74. package/src/components/shared/feedback/SAsyncState.vue +3 -1
  75. package/src/components/shared/graph/SGraphViewport.vue +11 -9
  76. package/src/components/shared/navigation/SBreadcrumbs.vue +8 -5
  77. package/src/components/shared/navigation/SCatalogNavigator.vue +26 -20
  78. package/src/components/shared/navigation/STabList.vue +10 -6
  79. package/src/components/shared/navigation/STabs.vue +15 -11
  80. package/src/components/shared/navigation/SWizardSteps.vue +4 -2
  81. package/src/components/shared/persona/SPersonaProjectPicker.vue +31 -27
  82. package/src/components/shared/persona/SPersonaRunWorkbench.vue +39 -33
  83. package/src/components/shared/persona/_internal/SPersonaRunConsole.vue +8 -6
  84. package/src/components/shared/persona/_internal/labels.ts +66 -57
  85. package/src/components/shared/persona/presentation.ts +140 -110
  86. package/src/composables/useFloatingPosition.ts +39 -2
  87. package/src/composables/useUiKitLocale.ts +17 -0
  88. package/src/internal/codeHighlight.ts +110 -0
  89. package/src/internal/diffEngine.ts +464 -0
  90. package/src/internal/diffRuntime.ts +189 -0
  91. package/src/internal/feedbackPresentation.ts +33 -14
  92. package/src/internal/localeContext.ts +22 -0
  93. package/src/internal/ownedAttrs.ts +8 -0
  94. package/src/internal/runtimeContract.ts +11 -9
  95. package/src/internal/semanticSizing.ts +13 -0
  96. package/src/locales/catalog.ts +634 -0
  97. package/src/locales/locale.ts +472 -0
  98. package/src/locales/messages.generated.ts +1826 -0
  99. package/src/locales/messages.ts +150 -0
  100. package/src/stores/useNotifierStore.ts +61 -44
  101. package/src/styles/tokens.css +20 -2
  102. package/src/theme.ts +59 -32
  103. package/src/workers/diff.worker.ts +73 -0
  104. package/src/workers/diffProtocol.ts +155 -0
  105. package/src/components/shared/data-display/SToastContainer.css +0 -53
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  **A typed PGCorp design system for Vue 3, spanning foundational controls and
7
7
  complete workbench interfaces.**
8
8
 
9
- `Vue 3` · `TypeScript` · `122 exact exports` · `6 themes` · `MIT`
9
+ `Vue 3` · `TypeScript` · `exact public exports` · `6 themes` · `MIT`
10
10
 
11
11
  ## Что входит / What is included
12
12
 
@@ -26,22 +26,23 @@ complete workbench interfaces.**
26
26
  ## Установка / Installation
27
27
 
28
28
  ```bash
29
- npm install @pgcorp/ui-kit vue pinia vue-router
29
+ npm install @pgcorp/ui-kit @vue/devtools-api vue pinia vue-router
30
30
  ```
31
31
 
32
32
  | Требование / Requirement | Версия / Version |
33
33
  | --- | --- |
34
- | Node.js | `>=20.19.0` |
34
+ | Node.js | `^22.22.2 \|\| ^24.15.0 \|\| ^26.0.0` |
35
35
  | npm | `>=10.9.2 <12` |
36
- | Vue | `^3.5.0` |
37
- | Pinia | `^3.0.4` |
38
- | Vue Router | `^5.0.0` |
36
+ | Vue | `^3.5.43` |
37
+ | Pinia | `^4.0.3` |
38
+ | Vue Devtools API | `^8.2.1` |
39
+ | Vue Router | `^5.3.1` |
39
40
  | Build tool | Vite with Vue SFC support |
40
41
 
41
- `vue`, `pinia` и `vue-router` являются peer dependencies. Consumer-приложение
42
+ `vue`, `pinia`, `@vue/devtools-api` и `vue-router` являются peer dependencies. Consumer-приложение
42
43
  владеет их единственными runtime-экземплярами.
43
44
 
44
- `vue`, `pinia`, and `vue-router` are peer dependencies. The consumer application
45
+ `vue`, `pinia`, `@vue/devtools-api`, and `vue-router` are peer dependencies. The consumer application
45
46
  owns their single runtime instances.
46
47
 
47
48
  ## Быстрый старт / Quick start
@@ -113,6 +114,120 @@ Production build должен разрешать package exports напряму
113
114
  The production build must resolve package exports directly, without a source
114
115
  monorepo alias or copied UI-kit files.
115
116
 
117
+ ### Локализация / Localization
118
+
119
+ UI-kit поддерживает `en`, `ru`, `zh-CN` и `zh-TW`. Без provider используется
120
+ `ru`; `SUiKitLocaleProvider` задаёт локаль для одного Vue subtree и не создаёт
121
+ дополнительный DOM-элемент. / The UI kit supports `en`, `ru`, `zh-CN`, and
122
+ `zh-TW`. Without a provider it uses `ru`; `SUiKitLocaleProvider` selects a
123
+ locale for one Vue subtree without adding a DOM element.
124
+
125
+ ```vue
126
+ <script setup lang="ts">
127
+ import SUiKitLocaleProvider from '@pgcorp/ui-kit/shared/containers/SUiKitLocaleProvider.vue'
128
+ import type { UiKitLocale } from '@pgcorp/ui-kit/composables/useUiKitLocale'
129
+
130
+ const locale: UiKitLocale = 'en'
131
+ </script>
132
+
133
+ <template>
134
+ <SUiKitLocaleProvider :locale="locale">
135
+ <AppWorkspace />
136
+ </SUiKitLocaleProvider>
137
+ </template>
138
+ ```
139
+
140
+ Provider переводит только kit-owned visible/assistive copy. Доменные данные и
141
+ пользовательский контент остаются ответственностью consumer. Вложенные provider
142
+ наследуют ближайший context и принимают типизированный `messages` override. /
143
+ The provider translates kit-owned visible and assistive copy only. Domain data
144
+ and user content remain consumer-owned. Nested providers inherit the nearest
145
+ context and accept a typed `messages` override.
146
+
147
+ ### Уведомления / Notifications
148
+
149
+ `SControlledToastViewport` отображает readonly application records и возвращает
150
+ serializable `dismiss`, `action` и `lifetime-expired` intents. Consumer остаётся
151
+ владельцем массива, порядка, ID и durable history. Режим `consumer` не создаёт
152
+ таймеры; режим `viewport` управляет только presentation lifetime и ставит его на
153
+ паузу при hover/focus. / `SControlledToastViewport` renders readonly application
154
+ records and emits serializable `dismiss`, `action`, and `lifetime-expired`
155
+ intents. The consumer owns the array, ordering, IDs, and durable history. The
156
+ `consumer` mode creates no timers; the `viewport` mode owns presentation
157
+ lifetime only and pauses it on hover/focus.
158
+
159
+ ```ts
160
+ import type { SToastPresentation } from '@pgcorp/ui-kit/shared/data-display/toast'
161
+ import SControlledToastViewport from '@pgcorp/ui-kit/shared/data-display/SControlledToastViewport.vue'
162
+ ```
163
+
164
+ `SToastContainer` и `useNotifier` образуют автономный convenience-контракт для
165
+ одного Pinia root. Store хранит не более 100 history records и освобождает свои
166
+ таймеры через `$dispose()`/`$reset()`. / `SToastContainer` and `useNotifier`
167
+ provide the autonomous convenience contract for one Pinia root. The store keeps
168
+ at most 100 history records and releases its timers through `$dispose()`/`$reset()`.
169
+
170
+ ### Безопасные rich documents / Safe rich documents
171
+
172
+ `SRichDocument` отображает закрытую versioned `SRichDocumentV1` AST без raw
173
+ HTML, VNodes, callbacks и trust flags. Внешний JSON проверяется через
174
+ `validateRichDocument` и `validateRichDocumentResources`; ошибки содержат
175
+ stable code и точный node path. Consumer-owned resources связываются по ID, а
176
+ link/resource/action effects возвращаются как typed intents. Link-node с
177
+ `current: true` выражает текущее назначение через `aria-current="page"`.
178
+
179
+ `SRichDocument` renders the closed, versioned `SRichDocumentV1` AST without raw
180
+ HTML, VNodes, callbacks, or trust flags. External JSON is checked with
181
+ `validateRichDocument` and `validateRichDocumentResources`; failures include a
182
+ stable code and exact node path. Consumer-owned resources are joined by ID, and
183
+ link/resource/action effects are emitted as typed intents. A link node with
184
+ `current: true` exposes the current destination through `aria-current="page"`.
185
+
186
+ ```ts
187
+ import SRichDocument from '@pgcorp/ui-kit/shared/data-display/SRichDocument.vue'
188
+ import {
189
+ validateRichDocument,
190
+ validateRichDocumentResources,
191
+ type SRichDocumentV1,
192
+ } from '@pgcorp/ui-kit/shared/data-display/richDocument'
193
+ ```
194
+
195
+ Code highlighting and KaTeX load through local lazy chunks; SSR uses safe text
196
+ fallbacks. Полный security, resource, event и versioning contract описан в
197
+ [`docs/rich-document.md`](https://github.com/pglyanenko/sputnig/blob/master/packages/ui-kit/docs/rich-document.md). / The complete security,
198
+ resource, event, and versioning contract is documented in
199
+ [`docs/rich-document.md`](https://github.com/pglyanenko/sputnig/blob/master/packages/ui-kit/docs/rich-document.md).
200
+
201
+ ### Области прокрутки / Scroll viewports
202
+
203
+ `SScrollViewport` владеет именованной scroll region, осями `x`/`y`/`both`,
204
+ semantic block size, overscroll policy и root-only keyboard navigation.
205
+ Событие `scroll-state` возвращает normalized physical и writing-mode-aware
206
+ semantic offsets. Exposed API предоставляет `focus`, `scrollToBoundary` и
207
+ `reveal`, но не открывает DOM owner.
208
+
209
+ `SScrollViewport` owns a named scroll region, `x`/`y`/`both` axes, semantic
210
+ block size, overscroll policy, and root-only keyboard navigation. The
211
+ `scroll-state` event provides normalized physical and writing-mode-aware
212
+ semantic offsets. Its exposed API provides `focus`, `scrollToBoundary`, and
213
+ `reveal` without exposing the DOM owner.
214
+
215
+ ```ts
216
+ import SScrollViewport from '@pgcorp/ui-kit/shared/data-display/SScrollViewport.vue'
217
+ import type {
218
+ SScrollViewportApi,
219
+ SScrollViewportState,
220
+ } from '@pgcorp/ui-kit/shared/data-display/scrollViewport'
221
+ ```
222
+
223
+ Вложенные inputs, editors и terminals сохраняют собственную клавиатуру,
224
+ selection и native copy. Полный axes, state, API и lifecycle contract описан в
225
+ [`docs/scroll-viewport.md`](https://github.com/pglyanenko/sputnig/blob/master/packages/ui-kit/docs/scroll-viewport.md). /
226
+ Nested inputs, editors, and terminals retain their keyboard behavior,
227
+ selection, and native copy. The complete axes, state, API, and lifecycle
228
+ contract is documented in
229
+ [`docs/scroll-viewport.md`](https://github.com/pglyanenko/sputnig/blob/master/packages/ui-kit/docs/scroll-viewport.md).
230
+
116
231
  ## Карта компонентов / Component map
117
232
 
118
233
  | Группа / Group | Назначение / Purpose | Примеры exports / Example exports |
@@ -120,7 +235,7 @@ monorepo alias or copied UI-kit files.
120
235
  | Layout | App shell, dock regions, stacks, workbench layout | `layout/SAppShell.vue`, `layout/SStack.vue`, `layout/SWorkbenchLayout.vue` |
121
236
  | Controls | Buttons, fields, inputs, switches, selects, comboboxes, menus | `shared/controls/SButton.vue`, `shared/controls/SSwitch.vue`, `shared/controls/SCombobox.vue` |
122
237
  | Containers | Panels, modal, drawer, popover, sidebars | `shared/containers/SPanel.vue`, `shared/containers/SModal.vue`, `shared/containers/SPopover.vue` |
123
- | Data display | Tables, charts, metric cards, chips, status, progress, code, JSON, tooltips | `shared/data-display/STable.vue`, `shared/data-display/SChart.vue`, `shared/data-display/SMetricCard.vue` |
238
+ | 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` |
124
239
  | Navigation | Tabs, breadcrumbs, catalog navigation, wizard | `shared/navigation/STabs.vue`, `shared/navigation/SBreadcrumbs.vue`, `shared/navigation/SWizardSteps.vue` |
125
240
  | Complex surfaces | Tree, Kanban, data grid, SQL editor, graph | `shared/complex/STree.vue`, `shared/database/SDataGrid.vue`, `shared/graph/SGraphViewport.vue` |
126
241
  | Runtime | Themes, variants, icons, composables, stores | `theme`, `variants`, `icons`, `composables/useNotifier`, `stores/useNotifierStore` |
@@ -264,6 +379,70 @@ already labelled card uses `accessibility.mode="decorative"` with
264
379
  `data-table-presentation="none"`; a meaningful standalone chart keeps the
265
380
  default assistive data table.
266
381
 
382
+ ### Сравнение исходников / Source diff
383
+
384
+ `SDiffViewer` является semantic owner для exact-text diff. `before.text` и
385
+ `after.text` сравниваются без нормализации переводов строк или содержимого;
386
+ `label`, `revision` и `language` являются metadata. `layout="unified"` сохраняет
387
+ единый горизонтальный scroll для длинных строк, `layout="split"` показывает
388
+ равные колонки с переносом кода. `viewport` использует те же semantic размеры,
389
+ что и code surfaces.
390
+
391
+ `SDiffViewer` is the semantic owner for exact-text diff. `before.text` and
392
+ `after.text` are compared without newline or payload normalization; `label`,
393
+ `revision`, and `language` are metadata. `layout="unified"` preserves one
394
+ horizontal scroll surface for long lines, while `layout="split"` renders equal
395
+ columns with wrapped code. `viewport` uses the same semantic sizes as the code
396
+ surfaces.
397
+
398
+ ```ts
399
+ import type {
400
+ SDiffResult,
401
+ SDiffSource,
402
+ } from '@pgcorp/ui-kit/shared/data-display/diff'
403
+ import SDiffViewer from '@pgcorp/ui-kit/shared/data-display/SDiffViewer.vue'
404
+ ```
405
+
406
+ ```vue
407
+ <SDiffViewer
408
+ v-model:expanded-gap-ids="expandedGapIds"
409
+ :before="baseSource"
410
+ :after="currentSource"
411
+ :context-lines="3"
412
+ layout="split"
413
+ viewport="expanded"
414
+ aria-label="Изменения конфигурации"
415
+ @computed="handleComputed"
416
+ @retry="handleRetry"
417
+ />
418
+ ```
419
+
420
+ `contextLines` задаёт число видимых неизменённых строк вокруг каждого hunk.
421
+ Без `expandedGapIds` компонент владеет раскрытием gap; с
422
+ `v-model:expanded-gap-ids` consumer применяет intent как controlled state.
423
+ `computed` передаёт валидированный `SDiffResult`, `retry` сообщает о повторном
424
+ запуске после ошибки. Для синхронного вычисления вне интерактивной поверхности
425
+ экспортируется `computeDiff(before, after, options)`.
426
+
427
+ `contextLines` selects the unchanged lines around each hunk. Without
428
+ `expandedGapIds`, the component owns gap expansion; with
429
+ `v-model:expanded-gap-ids`, the consumer applies expansion intents as controlled
430
+ state. `computed` provides a validated `SDiffResult`, and `retry` reports a
431
+ retry after an error. `computeDiff(before, after, options)` is exported for
432
+ synchronous computation outside an interactive surface.
433
+
434
+ Строки виртуализируются независимо от размера source. Дорогие интерактивные
435
+ вычисления выполняются через управляемый worker: смена source отменяет и
436
+ освобождает прежнюю работу, unmount освобождает активный worker, а результат
437
+ неактуального поколения не попадает в UI. Consumer не создаёт worker и не
438
+ управляет его жизненным циклом.
439
+
440
+ Lines are virtualized independently of source size. Expensive interactive
441
+ computations use a managed worker: source replacement cancels and releases the
442
+ previous job, unmount releases the active worker, and stale-generation results
443
+ never reach the UI. Consumers neither create the worker nor manage its
444
+ lifecycle.
445
+
267
446
  ### Действия chip и адаптивный header панели / Chip actions and responsive panel header
268
447
 
269
448
  `SChip` принимает typed `surface` contract для статичного, action и link
@@ -39,6 +39,94 @@ import App from './App.vue'
39
39
  createApp(App).use(createPinia()).use(router).mount('#app')
40
40
  ```
41
41
 
42
+ ### Локаль UI-kit
43
+
44
+ Без provider UI-kit использует `ru`. Для локали конкретного Vue subtree
45
+ оберните его в DOM-less `SUiKitLocaleProvider`. Поддерживаются `en`, `ru`,
46
+ `zh-CN` и `zh-TW`:
47
+
48
+ ```vue
49
+ <script setup lang="ts">
50
+ import SUiKitLocaleProvider from '@pgcorp/ui-kit/shared/containers/SUiKitLocaleProvider.vue'
51
+ import type { UiKitLocale } from '@pgcorp/ui-kit/composables/useUiKitLocale'
52
+
53
+ const locale: UiKitLocale = 'en'
54
+ </script>
55
+
56
+ <template>
57
+ <SUiKitLocaleProvider :locale="locale">
58
+ <AppWorkspace />
59
+ </SUiKitLocaleProvider>
60
+ </template>
61
+ ```
62
+
63
+ Provider локализует только текст, принадлежащий UI-kit: labels controls,
64
+ assistive names, loading/empty copy и системные UI-сообщения. Доменные данные,
65
+ заголовки экранов и пользовательский контент передаются consumer-ом уже на
66
+ нужном языке. Вложенный provider наследует locale и messages ближайшего
67
+ родителя; prop `messages` принимает типизированный deep-partial override для
68
+ своего subtree. Неизвестная locale, неизвестный ключ override или неверные
69
+ параметры интерполяции завершаются явной диагностикой.
70
+
71
+ ### Controlled-уведомления
72
+
73
+ `SControlledToastViewport` принимает readonly-массив сериализуемых records.
74
+ Consumer владеет ID, порядком, application state и durable history; viewport не
75
+ изменяет входной массив. Действия records содержат только ID, label и appearance,
76
+ без callback-полей.
77
+
78
+ ```vue
79
+ <script setup lang="ts">
80
+ import { ref } from 'vue'
81
+ import SControlledToastViewport from '@pgcorp/ui-kit/shared/data-display/SControlledToastViewport.vue'
82
+ import type {
83
+ SToastActionIntent,
84
+ SToastDismissIntent,
85
+ SToastPresentation,
86
+ } from '@pgcorp/ui-kit/shared/data-display/toast'
87
+
88
+ const notifications = ref<readonly SToastPresentation[]>([
89
+ {
90
+ id: 'sync-ready',
91
+ severity: 'success',
92
+ title: 'Синхронизация завершена',
93
+ message: 'Данные рабочего пространства обновлены.',
94
+ actions: [{ id: 'open', label: 'Открыть', appearance: 'outline' }],
95
+ },
96
+ ])
97
+ const selectedAction = ref<SToastActionIntent | null>(null)
98
+
99
+ function dismiss(intent: SToastDismissIntent): void {
100
+ notifications.value = notifications.value.filter(({ id }) => id !== intent.id)
101
+ }
102
+
103
+ function act(intent: SToastActionIntent): void {
104
+ selectedAction.value = intent
105
+ }
106
+ </script>
107
+
108
+ <template>
109
+ <SControlledToastViewport
110
+ :notifications="notifications"
111
+ accessible-label="Системные уведомления"
112
+ lifetime-owner="consumer"
113
+ position="top-end"
114
+ @dismiss="dismiss"
115
+ @action="act"
116
+ />
117
+ </template>
118
+ ```
119
+
120
+ `lifetime-owner="consumer"` не создаёт auto-dismiss timers. В режиме
121
+ `viewport` record задаёт `durationMs`; истечение эмитит `lifetime-expired`, а
122
+ consumer удаляет record. Hover и focus приостанавливают оставшееся время.
123
+ Удаление record и unmount очищают связанные timers.
124
+
125
+ Для автономного сценария разместите `SToastContainer` один раз в app root и
126
+ вызывайте `useNotifier`. Его store принадлежит конкретному Pinia root, хранит
127
+ 100 последних history records и очищает timers при `$dispose()` или `$reset()`.
128
+ Не создавайте локальную копию notifier store с тем же Pinia ID.
129
+
42
130
  Каждый компонент импортируется через точный публичный subpath:
43
131
 
44
132
  ```ts
@@ -94,6 +182,53 @@ import App from './App.vue'
94
182
  createApp(App).use(createPinia()).use(router).mount('#app')
95
183
  ```
96
184
 
185
+ ### UI-kit locale
186
+
187
+ Without a provider, the UI kit uses `ru`. Wrap a Vue subtree in the DOM-less
188
+ `SUiKitLocaleProvider` to select its locale. Supported locales are `en`, `ru`,
189
+ `zh-CN`, and `zh-TW`:
190
+
191
+ ```vue
192
+ <script setup lang="ts">
193
+ import SUiKitLocaleProvider from '@pgcorp/ui-kit/shared/containers/SUiKitLocaleProvider.vue'
194
+ import type { UiKitLocale } from '@pgcorp/ui-kit/composables/useUiKitLocale'
195
+
196
+ const locale: UiKitLocale = 'en'
197
+ </script>
198
+
199
+ <template>
200
+ <SUiKitLocaleProvider :locale="locale">
201
+ <AppWorkspace />
202
+ </SUiKitLocaleProvider>
203
+ </template>
204
+ ```
205
+
206
+ The provider localizes UI-kit-owned copy only: control labels, assistive names,
207
+ loading and empty copy, and system UI messages. Domain data, screen titles, and
208
+ user content are supplied by the consumer in the required language. A nested
209
+ provider inherits the nearest parent's locale and messages; the `messages` prop
210
+ accepts a typed deep-partial override for that subtree. An unknown locale,
211
+ unknown override key, or invalid interpolation parameters fail with an explicit
212
+ diagnostic.
213
+
214
+ ### Controlled notifications
215
+
216
+ `SControlledToastViewport` accepts a readonly array of serializable records.
217
+ The consumer owns IDs, ordering, application state, and durable history; the
218
+ viewport never mutates the input array. Record actions contain IDs, labels, and
219
+ an optional appearance without callback fields.
220
+
221
+ Use `lifetime-owner="consumer"` when the application owns expiry; this mode
222
+ creates no auto-dismiss timers. In `viewport` mode, a record supplies
223
+ `durationMs`, expiry emits `lifetime-expired`, and the consumer removes the
224
+ record. Hover and focus pause the remaining duration. Record removal and
225
+ component unmount release associated timers.
226
+
227
+ For the autonomous path, mount `SToastContainer` once at the application root
228
+ and call `useNotifier`. Its store belongs to one Pinia root, retains 100 history
229
+ records, and clears timers on `$dispose()` or `$reset()`. Do not create a local
230
+ notifier store with the same Pinia ID.
231
+
97
232
  Import every component through an exact public subpath:
98
233
 
99
234
  ```ts
@@ -13,7 +13,7 @@
13
13
  - `shared/data-display/*` — badges, tables, code, JSON, progress, status и tooltips;
14
14
  - `shared/navigation/*` — tabs, breadcrumbs, catalog navigation и wizard steps;
15
15
  - `shared/database/*`, `shared/graph/*`, `shared/persona/*` — доменные сложные поверхности;
16
- - `composables/*`, `stores/*`, `theme`, `variants`, `icons` — typed runtime modules;
16
+ - `composables/*`, `stores/*`, `theme`, `variants`, `icons` — typed runtime modules, включая locale context;
17
17
  - `tailwind.css` — полный Tailwind 4 integration entrypoint;
18
18
  - `style.css`, `tokens.css`, `reference.css` — global styles, token-only и compile-time reference entrypoints.
19
19
 
@@ -27,7 +27,12 @@ compiler регистрирует их без межфайлового filesyste
27
27
  Публикуемый production-source компилируется с `target/lib: ES2020`, совпадающим
28
28
  с baseline `@vue/tsconfig/tsconfig.dom.json`, и не требует скрытых полифиллов.
29
29
  Типы таблицы импортируются из `shared/data-display/table`, типы графиков — из
30
- `shared/data-display/chart`. `SChart` объединяет line/area/bar/pie/donut,
30
+ `shared/data-display/chart`, типы и чистая функция diff — из
31
+ `shared/data-display/diff`. `SDiffViewer` принимает exact-text snapshots,
32
+ показывает unified/split представление, виртуализирует строки и управляет
33
+ вычислением в worker для больших входов. Смена source и unmount отменяют
34
+ неактуальную работу; завершившийся устаревший результат не публикуется.
35
+ `SChart` объединяет line/area/bar/pie/donut,
31
36
  responsive geometry, semantic palette и доступную таблицу данных. `SMetricCard`
32
37
  принимает компактный график через `#chart`. `SCodeBlock` сохраняет CodeMirror за
33
38
  async boundary; `presentation="plain"` является lightweight read-only контрактом
@@ -37,6 +42,25 @@ async boundary; `presentation="plain"` является lightweight read-only к
37
42
  `SActionListItem` держат primary activation и дополнительные controls
38
43
  sibling-элементами внутри одного chrome; `actionsLayout`/`trailingLayout`
39
44
  управляют inline, stacked и container-responsive раскладкой.
45
+ `SUiKitLocaleProvider` задаёт `en | ru | zh-CN | zh-TW` для одного Vue subtree,
46
+ а `composables/useUiKitLocale` экспортирует readonly context, полный список
47
+ локалей и типы messages/overrides/ключей. Provider не создаёт DOM owner и
48
+ поддерживает независимые локали в соседних app roots.
49
+ `shared/data-display/SControlledToastViewport.vue` является controlled owner
50
+ стека уведомлений; `shared/data-display/toast` экспортирует presentation records,
51
+ action/dismiss/lifetime intents, position и lifetime types. `SToastContainer`
52
+ является adapter над `stores/useNotifierStore`, а не вторым layout owner.
53
+ `shared/data-display/SRichDocument.vue` отображает только валидированную
54
+ `SRichDocumentV1` AST. `shared/data-display/richDocument` экспортирует закрытую
55
+ модель, consumer-owned resource records, typed intents и validators со stable
56
+ error code/path. Link-node поддерживает `current: true` для
57
+ `aria-current="page"`. Raw HTML, VNodes, callbacks и trust flags не входят в
58
+ API.
59
+ `shared/data-display/SScrollViewport.vue` владеет accessible scroll region,
60
+ разрешёнными осями, semantic block size, root-only keyboard navigation и
61
+ ResizeObserver lifecycle. `shared/data-display/scrollViewport` экспортирует
62
+ сериализуемое physical/semantic state и exposed API `focus`,
63
+ `scrollToBoundary`, `reveal` без доступа к внутреннему DOM.
40
64
 
41
65
  ## English
42
66
 
@@ -51,7 +75,7 @@ Primary groups:
51
75
  - `shared/data-display/*` — badges, tables, code, JSON, progress, status, and tooltips;
52
76
  - `shared/navigation/*` — tabs, breadcrumbs, catalog navigation, and wizard steps;
53
77
  - `shared/database/*`, `shared/graph/*`, `shared/persona/*` — complex domain surfaces;
54
- - `composables/*`, `stores/*`, `theme`, `variants`, `icons` — typed runtime modules;
78
+ - `composables/*`, `stores/*`, `theme`, `variants`, `icons` — typed runtime modules, including locale context;
55
79
  - `tailwind.css` — the complete Tailwind 4 integration entrypoint;
56
80
  - `style.css`, `tokens.css`, `reference.css` — global styles, token-only, and compile-time reference entrypoints.
57
81
 
@@ -65,7 +89,12 @@ compiler registers them without a cross-file filesystem type resolver.
65
89
  Published production source compiles with the `target/lib: ES2020` baseline from
66
90
  `@vue/tsconfig/tsconfig.dom.json` and does not require implicit polyfills.
67
91
  Table types are imported from `shared/data-display/table`; chart types are
68
- imported from `shared/data-display/chart`. `SChart` unifies
92
+ imported from `shared/data-display/chart`; diff types and its pure computation
93
+ function are imported from `shared/data-display/diff`. `SDiffViewer` accepts
94
+ exact-text snapshots, renders unified or split presentation, virtualizes lines,
95
+ and manages worker computation for large inputs. Source replacement and unmount
96
+ cancel obsolete work, and a completed stale result is never published.
97
+ `SChart` unifies
69
98
  line/area/bar/pie/donut charts, responsive geometry, the semantic palette, and
70
99
  an accessible data table. `SMetricCard` accepts a compact chart through
71
100
  `#chart`. `SCodeBlock` keeps CodeMirror behind an async boundary;
@@ -76,3 +105,23 @@ the editor runtime. `SChip.surface` owns action/link semantics, while
76
105
  compositions keep primary activation and additional controls as siblings inside
77
106
  one chrome; `actionsLayout`/`trailingLayout` control inline, stacked, and
78
107
  container-responsive layout.
108
+ `SUiKitLocaleProvider` selects `en | ru | zh-CN | zh-TW` for one Vue subtree,
109
+ while `composables/useUiKitLocale` exports the readonly context, the complete
110
+ locale list, and message/override/key types. The provider renders no DOM owner
111
+ and supports independent locales in adjacent application roots.
112
+ `shared/data-display/SControlledToastViewport.vue` is the controlled owner of
113
+ the notification stack; `shared/data-display/toast` exports presentation
114
+ records, action/dismiss/lifetime intents, positions, and lifetime types.
115
+ `SToastContainer` is an adapter over `stores/useNotifierStore`, not a second
116
+ layout owner.
117
+ `shared/data-display/SRichDocument.vue` renders validated `SRichDocumentV1` AST
118
+ only. `shared/data-display/richDocument` exports the closed model,
119
+ consumer-owned resource records, typed intents, and validators with stable
120
+ error code/path diagnostics. Link nodes support `current: true` for
121
+ `aria-current="page"`. Raw HTML, VNodes, callbacks, and trust flags are not part
122
+ of the API.
123
+ `shared/data-display/SScrollViewport.vue` owns the accessible scroll region,
124
+ enabled axes, semantic block size, root-only keyboard navigation, and
125
+ ResizeObserver lifecycle. `shared/data-display/scrollViewport` exports
126
+ serializable physical and semantic state plus the `focus`, `scrollToBoundary`,
127
+ and `reveal` exposed API without access to internal DOM.