@pgcorp/ui-kit 0.1.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 (251) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/docs/accessibility.md +27 -0
  4. package/docs/getting-started.md +80 -0
  5. package/docs/licensing.md +30 -0
  6. package/docs/public-api.md +39 -0
  7. package/docs/security.md +21 -0
  8. package/docs/theming.md +35 -0
  9. package/docs/versioning.md +23 -0
  10. package/index.ts +3 -0
  11. package/package.json +592 -0
  12. package/src/components/icons/_internal/generatedSputnigUiIconMarkup.ts +971 -0
  13. package/src/components/icons/sputnigUiIcons.ts +530 -0
  14. package/src/components/layout/SAppShell.vue +232 -0
  15. package/src/components/layout/SDockRegion.vue +200 -0
  16. package/src/components/layout/SResizeHandle.vue +251 -0
  17. package/src/components/layout/SSideMenuButton.vue +104 -0
  18. package/src/components/layout/SSideMenuRail.vue +47 -0
  19. package/src/components/layout/SStack.css +25 -0
  20. package/src/components/layout/SStack.vue +57 -0
  21. package/src/components/layout/SStatusBarShell.css +15 -0
  22. package/src/components/layout/SStatusBarShell.vue +26 -0
  23. package/src/components/layout/SWorkbenchLayout.vue +53 -0
  24. package/src/components/layout/SWorkbenchPanelShell.css +161 -0
  25. package/src/components/layout/SWorkbenchPanelShell.vue +286 -0
  26. package/src/components/layout/_internal/SAppBrand.css +45 -0
  27. package/src/components/layout/_internal/SAppBrand.vue +37 -0
  28. package/src/components/layout/_internal/SAppBrandContent.vue +30 -0
  29. package/src/components/layout/types.ts +28 -0
  30. package/src/components/shared/_internal/SDialogSurface.css +127 -0
  31. package/src/components/shared/_internal/SDialogSurface.vue +117 -0
  32. package/src/components/shared/_internal/SDisclosureSummary.css +92 -0
  33. package/src/components/shared/_internal/SDisclosureSummary.vue +121 -0
  34. package/src/components/shared/_internal/SListboxGroup.css +18 -0
  35. package/src/components/shared/_internal/SListboxGroup.vue +24 -0
  36. package/src/components/shared/_internal/SMenuItem.css +112 -0
  37. package/src/components/shared/_internal/SMenuItem.vue +213 -0
  38. package/src/components/shared/_internal/SMenuLayer.css +21 -0
  39. package/src/components/shared/_internal/SMenuLayer.vue +48 -0
  40. package/src/components/shared/_internal/SMenuSeparator.css +5 -0
  41. package/src/components/shared/_internal/SMenuSeparator.vue +13 -0
  42. package/src/components/shared/_internal/SToastItem.css +65 -0
  43. package/src/components/shared/_internal/SToastItem.vue +58 -0
  44. package/src/components/shared/_internal/STreeNode.vue +761 -0
  45. package/src/components/shared/_internal/dialogSurface.ts +105 -0
  46. package/src/components/shared/_internal/focusTrap.ts +40 -0
  47. package/src/components/shared/_internal/listboxContext.ts +16 -0
  48. package/src/components/shared/_internal/markerContract.ts +10 -0
  49. package/src/components/shared/_internal/menuContext.ts +23 -0
  50. package/src/components/shared/_internal/selectionContract.ts +239 -0
  51. package/src/components/shared/_internal/sidebarGroupContext.ts +31 -0
  52. package/src/components/shared/_internal/tabsContext.ts +57 -0
  53. package/src/components/shared/_internal/treeRuntime.ts +203 -0
  54. package/src/components/shared/_internal/useSelectionRoving.ts +162 -0
  55. package/src/components/shared/codeLanguages.ts +168 -0
  56. package/src/components/shared/complex/SKanbanCard.css +8 -0
  57. package/src/components/shared/complex/SKanbanCard.vue +76 -0
  58. package/src/components/shared/complex/SKanbanLane.vue +82 -0
  59. package/src/components/shared/complex/STree.css +24 -0
  60. package/src/components/shared/complex/STree.vue +288 -0
  61. package/src/components/shared/complex/treeAdapter.ts +210 -0
  62. package/src/components/shared/complex/types.ts +102 -0
  63. package/src/components/shared/containers/SCloudChrome.css +103 -0
  64. package/src/components/shared/containers/SCloudChrome.vue +87 -0
  65. package/src/components/shared/containers/SCloudChromeEmpty.css +10 -0
  66. package/src/components/shared/containers/SCloudChromeEmpty.vue +18 -0
  67. package/src/components/shared/containers/SCloudChromeRow.css +78 -0
  68. package/src/components/shared/containers/SCloudChromeRow.vue +108 -0
  69. package/src/components/shared/containers/SCollapsiblePanel.css +57 -0
  70. package/src/components/shared/containers/SCollapsiblePanel.vue +360 -0
  71. package/src/components/shared/containers/SDrawer.css +60 -0
  72. package/src/components/shared/containers/SDrawer.vue +86 -0
  73. package/src/components/shared/containers/SFormGrid.css +26 -0
  74. package/src/components/shared/containers/SFormGrid.vue +46 -0
  75. package/src/components/shared/containers/SFormGridItem.css +18 -0
  76. package/src/components/shared/containers/SFormGridItem.vue +29 -0
  77. package/src/components/shared/containers/SLeftSidebar.vue +74 -0
  78. package/src/components/shared/containers/SModal.css +27 -0
  79. package/src/components/shared/containers/SModal.vue +105 -0
  80. package/src/components/shared/containers/SPageHeader.css +66 -0
  81. package/src/components/shared/containers/SPageHeader.vue +45 -0
  82. package/src/components/shared/containers/SPanel.css +134 -0
  83. package/src/components/shared/containers/SPanel.vue +169 -0
  84. package/src/components/shared/containers/SPopover.css +35 -0
  85. package/src/components/shared/containers/SPopover.vue +551 -0
  86. package/src/components/shared/containers/SSidebarGroup.css +12 -0
  87. package/src/components/shared/containers/SSidebarGroup.vue +443 -0
  88. package/src/components/shared/containers/SSidebarSection.css +41 -0
  89. package/src/components/shared/containers/SSidebarSection.vue +268 -0
  90. package/src/components/shared/containers/disclosureSummary.ts +48 -0
  91. package/src/components/shared/containers/popover.ts +72 -0
  92. package/src/components/shared/containers/sidebar.ts +206 -0
  93. package/src/components/shared/controls/SButton.css +274 -0
  94. package/src/components/shared/controls/SButton.vue +211 -0
  95. package/src/components/shared/controls/SCheckbox.css +65 -0
  96. package/src/components/shared/controls/SCheckbox.vue +130 -0
  97. package/src/components/shared/controls/SCheckboxGroup.vue +134 -0
  98. package/src/components/shared/controls/SChoiceCards.css +29 -0
  99. package/src/components/shared/controls/SChoiceCards.vue +145 -0
  100. package/src/components/shared/controls/SColorSelect.vue +146 -0
  101. package/src/components/shared/controls/SComboboxTrigger.css +35 -0
  102. package/src/components/shared/controls/SComboboxTrigger.vue +122 -0
  103. package/src/components/shared/controls/SContextMenu.css +11 -0
  104. package/src/components/shared/controls/SContextMenu.vue +186 -0
  105. package/src/components/shared/controls/SContextToggleButton.vue +64 -0
  106. package/src/components/shared/controls/SDragHandle.vue +100 -0
  107. package/src/components/shared/controls/SDropdownMenu.css +5 -0
  108. package/src/components/shared/controls/SDropdownMenu.vue +188 -0
  109. package/src/components/shared/controls/SField.css +79 -0
  110. package/src/components/shared/controls/SField.vue +89 -0
  111. package/src/components/shared/controls/SFieldGroup.css +58 -0
  112. package/src/components/shared/controls/SFieldGroup.vue +117 -0
  113. package/src/components/shared/controls/SFieldLabel.css +12 -0
  114. package/src/components/shared/controls/SFieldLabel.vue +51 -0
  115. package/src/components/shared/controls/SInlineTokenEditor.css +61 -0
  116. package/src/components/shared/controls/SInlineTokenEditor.vue +1081 -0
  117. package/src/components/shared/controls/SInputText.css +66 -0
  118. package/src/components/shared/controls/SInputText.vue +321 -0
  119. package/src/components/shared/controls/SInteractiveSurface.css +72 -0
  120. package/src/components/shared/controls/SInteractiveSurface.vue +326 -0
  121. package/src/components/shared/controls/SLink.css +114 -0
  122. package/src/components/shared/controls/SLink.ts +85 -0
  123. package/src/components/shared/controls/SLink.vue +114 -0
  124. package/src/components/shared/controls/SListbox.css +31 -0
  125. package/src/components/shared/controls/SListbox.vue +388 -0
  126. package/src/components/shared/controls/SListboxOption.css +63 -0
  127. package/src/components/shared/controls/SListboxOption.vue +85 -0
  128. package/src/components/shared/controls/SMarker.css +19 -0
  129. package/src/components/shared/controls/SMarker.vue +33 -0
  130. package/src/components/shared/controls/SMenuSurface.css +20 -0
  131. package/src/components/shared/controls/SMenuSurface.vue +333 -0
  132. package/src/components/shared/controls/SSegmentedControl.css +38 -0
  133. package/src/components/shared/controls/SSegmentedControl.vue +134 -0
  134. package/src/components/shared/controls/SSelect.css +12 -0
  135. package/src/components/shared/controls/SSelect.vue +338 -0
  136. package/src/components/shared/controls/STextarea.css +31 -0
  137. package/src/components/shared/controls/STextarea.vue +316 -0
  138. package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +224 -0
  139. package/src/components/shared/controls/menu.ts +178 -0
  140. package/src/components/shared/data-display/SActionCard.css +6 -0
  141. package/src/components/shared/data-display/SActionCard.vue +49 -0
  142. package/src/components/shared/data-display/SActionList.css +15 -0
  143. package/src/components/shared/data-display/SActionList.vue +34 -0
  144. package/src/components/shared/data-display/SActionListItem.css +114 -0
  145. package/src/components/shared/data-display/SActionListItem.vue +230 -0
  146. package/src/components/shared/data-display/SBadge.css +73 -0
  147. package/src/components/shared/data-display/SBadge.vue +52 -0
  148. package/src/components/shared/data-display/SCodeBlock.css +56 -0
  149. package/src/components/shared/data-display/SCodeBlock.vue +188 -0
  150. package/src/components/shared/data-display/SCodeEditor.css +178 -0
  151. package/src/components/shared/data-display/SCodeEditor.vue +274 -0
  152. package/src/components/shared/data-display/SCodeSearchPanel.vue +327 -0
  153. package/src/components/shared/data-display/SCopyField.vue +77 -0
  154. package/src/components/shared/data-display/SDocBlock.css +136 -0
  155. package/src/components/shared/data-display/SDocBlock.vue +358 -0
  156. package/src/components/shared/data-display/SEmptyState.css +42 -0
  157. package/src/components/shared/data-display/SEmptyState.vue +79 -0
  158. package/src/components/shared/data-display/SExpandableText.vue +314 -0
  159. package/src/components/shared/data-display/SJsonTree.css +6 -0
  160. package/src/components/shared/data-display/SJsonTree.vue +111 -0
  161. package/src/components/shared/data-display/SKeyValueGrid.css +37 -0
  162. package/src/components/shared/data-display/SKeyValueGrid.vue +277 -0
  163. package/src/components/shared/data-display/SLinkedSystemsList.vue +270 -0
  164. package/src/components/shared/data-display/SMessage.css +88 -0
  165. package/src/components/shared/data-display/SMessage.vue +152 -0
  166. package/src/components/shared/data-display/SMetricCard.css +32 -0
  167. package/src/components/shared/data-display/SMetricCard.vue +117 -0
  168. package/src/components/shared/data-display/SProgressBar.css +84 -0
  169. package/src/components/shared/data-display/SProgressBar.vue +114 -0
  170. package/src/components/shared/data-display/SProgressIndicator.css +59 -0
  171. package/src/components/shared/data-display/SProgressIndicator.vue +104 -0
  172. package/src/components/shared/data-display/SSectionHeader.css +84 -0
  173. package/src/components/shared/data-display/SSectionHeader.vue +58 -0
  174. package/src/components/shared/data-display/SStatus.css +90 -0
  175. package/src/components/shared/data-display/SStatus.vue +79 -0
  176. package/src/components/shared/data-display/STable.css +83 -0
  177. package/src/components/shared/data-display/STable.vue +339 -0
  178. package/src/components/shared/data-display/SToastContainer.css +53 -0
  179. package/src/components/shared/data-display/SToastContainer.vue +31 -0
  180. package/src/components/shared/data-display/STooltip.css +114 -0
  181. package/src/components/shared/data-display/STooltip.vue +550 -0
  182. package/src/components/shared/data-display/STooltipAnchor.css +21 -0
  183. package/src/components/shared/data-display/STooltipAnchor.vue +119 -0
  184. package/src/components/shared/data-display/SVirtualList.css +24 -0
  185. package/src/components/shared/data-display/SVirtualList.ts +19 -0
  186. package/src/components/shared/data-display/SVirtualList.vue +361 -0
  187. package/src/components/shared/data-display/json.ts +136 -0
  188. package/src/components/shared/data-display/table.ts +76 -0
  189. package/src/components/shared/database/SDataGrid.css +3 -0
  190. package/src/components/shared/database/SDataGrid.vue +464 -0
  191. package/src/components/shared/database/SSqlEditor.vue +363 -0
  192. package/src/components/shared/database/dataGrid.ts +61 -0
  193. package/src/components/shared/feedback/SAsyncState.css +26 -0
  194. package/src/components/shared/feedback/SAsyncState.vue +190 -0
  195. package/src/components/shared/graph/SGraphViewport.ts +1 -0
  196. package/src/components/shared/graph/SGraphViewport.vue +858 -0
  197. package/src/components/shared/graph/_internal/graphViewportContract.ts +362 -0
  198. package/src/components/shared/markdown.ts +169 -0
  199. package/src/components/shared/navigation/SBottomNav.vue +692 -0
  200. package/src/components/shared/navigation/SBreadcrumbs.css +26 -0
  201. package/src/components/shared/navigation/SBreadcrumbs.vue +155 -0
  202. package/src/components/shared/navigation/SCatalogNavigator.css +100 -0
  203. package/src/components/shared/navigation/SCatalogNavigator.vue +308 -0
  204. package/src/components/shared/navigation/STab.vue +363 -0
  205. package/src/components/shared/navigation/STabList.vue +442 -0
  206. package/src/components/shared/navigation/STabPanel.vue +165 -0
  207. package/src/components/shared/navigation/STabPanels.vue +33 -0
  208. package/src/components/shared/navigation/STabs.vue +617 -0
  209. package/src/components/shared/navigation/SWizardSteps.css +69 -0
  210. package/src/components/shared/navigation/SWizardSteps.vue +171 -0
  211. package/src/components/shared/navigation/_internal/SCatalogNavigatorGroup.vue +63 -0
  212. package/src/components/shared/persona/SPersonaProjectPicker.css +36 -0
  213. package/src/components/shared/persona/SPersonaProjectPicker.vue +361 -0
  214. package/src/components/shared/persona/SPersonaRunWorkbench.css +109 -0
  215. package/src/components/shared/persona/SPersonaRunWorkbench.vue +549 -0
  216. package/src/components/shared/persona/_internal/SPersonaRunConsole.css +40 -0
  217. package/src/components/shared/persona/_internal/SPersonaRunConsole.vue +298 -0
  218. package/src/components/shared/persona/_internal/inventory.ts +110 -0
  219. package/src/components/shared/persona/_internal/labels.ts +79 -0
  220. package/src/components/shared/persona/presentation.ts +371 -0
  221. package/src/components/shared/persona/types.ts +256 -0
  222. package/src/composables/uiPreferencesReset.ts +52 -0
  223. package/src/composables/useClipboard.ts +37 -0
  224. package/src/composables/useDialogOverlay.ts +85 -0
  225. package/src/composables/useDisclosure.ts +40 -0
  226. package/src/composables/useFloatingPosition.ts +245 -0
  227. package/src/composables/useNotifier.ts +26 -0
  228. package/src/composables/useSidebarPanelState.ts +610 -0
  229. package/src/internal/codeEditorContract.ts +110 -0
  230. package/src/internal/codeEditorExtensions.ts +27 -0
  231. package/src/internal/feedbackPresentation.ts +136 -0
  232. package/src/internal/fieldSurface.ts +59 -0
  233. package/src/internal/focusNavigation.ts +69 -0
  234. package/src/internal/inlineTokenEditorContract.ts +136 -0
  235. package/src/internal/interactiveElement.ts +33 -0
  236. package/src/internal/layerStack.ts +202 -0
  237. package/src/internal/linkTarget.ts +256 -0
  238. package/src/internal/ownedAttrs.ts +464 -0
  239. package/src/internal/passiveContentContract.ts +129 -0
  240. package/src/internal/pointerInteractionLease.ts +81 -0
  241. package/src/internal/runtimeContract.ts +194 -0
  242. package/src/internal/runtimeTheme.ts +159 -0
  243. package/src/internal/semanticSizing.ts +60 -0
  244. package/src/internal/useMenuLayer.ts +129 -0
  245. package/src/stores/useNotifierStore.ts +69 -0
  246. package/src/styles/reference.css +4 -0
  247. package/src/styles/style.css +151 -0
  248. package/src/styles/tokens.css +836 -0
  249. package/src/theme.ts +449 -0
  250. package/src/types/highlight-js-lib.d.ts +76 -0
  251. package/src/variants.ts +15 -0
@@ -0,0 +1,464 @@
1
+ import {
2
+ useAttrs,
3
+ type AnchorHTMLAttributes,
4
+ type HTMLAttributes,
5
+ type InputHTMLAttributes,
6
+ type TextareaHTMLAttributes,
7
+ } from 'vue'
8
+
9
+ type OwnedAttributeValueKind =
10
+ | 'aria'
11
+ | 'aria-boolean'
12
+ | 'aria-current'
13
+ | 'aria-haspopup'
14
+ | 'aria-pressed'
15
+ | 'boolean-attribute'
16
+ | 'data'
17
+ | 'input-mode'
18
+ | 'listener'
19
+ | 'string'
20
+ | 'string-or-boolean'
21
+ | 'string-or-number'
22
+
23
+ type OwnedAttributeSchema = Readonly<Record<string, OwnedAttributeValueKind>>
24
+
25
+ function defineOwnedAttributeSchema<const Schema extends OwnedAttributeSchema>(schema: Schema): Readonly<Schema> {
26
+ return Object.freeze(schema)
27
+ }
28
+
29
+ /**
30
+ * Exact runtime allowlist каждого public owner.
31
+ * Exact runtime allowlist for every public owner.
32
+ *
33
+ * Пустая схема означает, что компонент не принимает fallthrough attrs: его API целиком выражен props/emits/slots.
34
+ * An empty schema means that the component accepts no fallthrough attrs: props/emits/slots express its entire API.
35
+ */
36
+ export const ownedAttributeSchemas = Object.freeze({
37
+ SAppShell: defineOwnedAttributeSchema({}),
38
+ SDockRegion: defineOwnedAttributeSchema({}),
39
+ SResizeHandle: defineOwnedAttributeSchema({
40
+ 'data-test-id': 'data',
41
+ }),
42
+ SSideMenuButton: defineOwnedAttributeSchema({
43
+ 'data-testid': 'data',
44
+ }),
45
+ SSideMenuRail: defineOwnedAttributeSchema({
46
+ 'data-testid': 'data',
47
+ }),
48
+ SStack: defineOwnedAttributeSchema({}),
49
+ SStatusBarShell: defineOwnedAttributeSchema({}),
50
+ SWorkbenchLayout: defineOwnedAttributeSchema({}),
51
+ SWorkbenchPanelShell: defineOwnedAttributeSchema({}),
52
+ SKanbanCard: defineOwnedAttributeSchema({
53
+ 'data-testid': 'data',
54
+ }),
55
+ SKanbanLane: defineOwnedAttributeSchema({}),
56
+ STree: defineOwnedAttributeSchema({
57
+ 'data-testid': 'data',
58
+ }),
59
+ SCloudChrome: defineOwnedAttributeSchema({
60
+ 'data-chat-chrome-drop-zone': 'data',
61
+ 'data-testid': 'data',
62
+ }),
63
+ SCloudChromeEmpty: defineOwnedAttributeSchema({}),
64
+ SCloudChromeRow: defineOwnedAttributeSchema({}),
65
+ SCollapsiblePanel: defineOwnedAttributeSchema({
66
+ 'data-testid': 'data',
67
+ }),
68
+ SDrawer: defineOwnedAttributeSchema({}),
69
+ SFormGrid: defineOwnedAttributeSchema({}),
70
+ SFormGridItem: defineOwnedAttributeSchema({}),
71
+ SLeftSidebar: defineOwnedAttributeSchema({}),
72
+ SModal: defineOwnedAttributeSchema({
73
+ 'data-testid': 'data',
74
+ }),
75
+ SPageHeader: defineOwnedAttributeSchema({}),
76
+ SPanel: defineOwnedAttributeSchema({
77
+ 'data-testid': 'data',
78
+ }),
79
+ SPopover: defineOwnedAttributeSchema({
80
+ id: 'string',
81
+ 'aria-label': 'string',
82
+ 'data-testid': 'data',
83
+ onClick: 'listener',
84
+ }),
85
+ SSidebarGroup: defineOwnedAttributeSchema({}),
86
+ SSidebarSection: defineOwnedAttributeSchema({}),
87
+ SButton: defineOwnedAttributeSchema({
88
+ 'aria-controls': 'string',
89
+ 'aria-describedby': 'string',
90
+ 'aria-current': 'aria-current',
91
+ 'aria-expanded': 'aria-boolean',
92
+ 'aria-haspopup': 'aria-haspopup',
93
+ 'aria-hidden': 'aria-boolean',
94
+ 'aria-keyshortcuts': 'string',
95
+ 'aria-label': 'string',
96
+ 'aria-pressed': 'aria-pressed',
97
+ 'aria-selected': 'aria-boolean',
98
+ 'data-s-popover-trigger': 'data',
99
+ 'data-s-drag-handle': 'data',
100
+ 'data-sidebar-section-toggle': 'data',
101
+ 'data-state': 'data',
102
+ 'data-test': 'data',
103
+ 'data-testid': 'data',
104
+ 'data-tree-selection-control': 'data',
105
+ autofocus: 'boolean-attribute',
106
+ id: 'string',
107
+ onClick: 'listener',
108
+ onFocusin: 'listener',
109
+ onFocusout: 'listener',
110
+ onKeydown: 'listener',
111
+ onMousedown: 'listener',
112
+ onPointercancel: 'listener',
113
+ onPointerdown: 'listener',
114
+ onPointerenter: 'listener',
115
+ onPointerleave: 'listener',
116
+ onPointerup: 'listener',
117
+ role: 'string',
118
+ tabindex: 'string-or-number',
119
+ title: 'string',
120
+ }),
121
+ SCheckbox: defineOwnedAttributeSchema({
122
+ 'aria-label': 'string',
123
+ 'aria-describedby': 'string',
124
+ 'data-testid': 'data',
125
+ onClick: 'listener',
126
+ onPointerdown: 'listener',
127
+ tabindex: 'string-or-number',
128
+ }),
129
+ SCheckboxGroup: defineOwnedAttributeSchema({
130
+ 'aria-describedby': 'string',
131
+ 'data-testid': 'data',
132
+ }),
133
+ SChoiceCards: defineOwnedAttributeSchema({
134
+ 'aria-describedby': 'string',
135
+ 'data-testid': 'data',
136
+ }),
137
+ SColorSelect: defineOwnedAttributeSchema({
138
+ 'aria-label': 'string',
139
+ }),
140
+ SDragHandle: defineOwnedAttributeSchema({
141
+ 'data-testid': 'data',
142
+ }),
143
+ SContextMenu: defineOwnedAttributeSchema({}),
144
+ SContextToggleButton: defineOwnedAttributeSchema({}),
145
+ SField: defineOwnedAttributeSchema({}),
146
+ SFieldGroup: defineOwnedAttributeSchema({
147
+ 'aria-describedby': 'string',
148
+ 'data-testid': 'data',
149
+ }),
150
+ SFieldLabel: defineOwnedAttributeSchema({
151
+ 'aria-label': 'string',
152
+ 'data-testid': 'data',
153
+ }),
154
+ SDropdownMenu: defineOwnedAttributeSchema({
155
+ 'aria-label': 'string',
156
+ 'data-testid': 'data',
157
+ id: 'string',
158
+ title: 'string',
159
+ }),
160
+ SInputText: defineOwnedAttributeSchema({
161
+ 'aria-describedby': 'string',
162
+ 'aria-invalid': 'aria-boolean',
163
+ 'aria-label': 'string',
164
+ 'data-testid': 'data',
165
+ autocomplete: 'string',
166
+ inputmode: 'input-mode',
167
+ list: 'string',
168
+ max: 'string-or-number',
169
+ min: 'string-or-number',
170
+ onBlur: 'listener',
171
+ onFocus: 'listener',
172
+ onKeydown: 'listener',
173
+ onKeyup: 'listener',
174
+ pattern: 'string',
175
+ step: 'string-or-number',
176
+ title: 'string',
177
+ }),
178
+ SInlineTokenEditor: defineOwnedAttributeSchema({
179
+ 'aria-describedby': 'string',
180
+ 'aria-label': 'string',
181
+ 'aria-labelledby': 'string',
182
+ 'data-testid': 'data',
183
+ autocomplete: 'string',
184
+ }),
185
+ SInteractiveSurface: defineOwnedAttributeSchema({}),
186
+ SLink: defineOwnedAttributeSchema({
187
+ 'aria-label': 'string',
188
+ 'data-s-app-shell-brand-passive': 'data',
189
+ 'data-testid': 'data',
190
+ download: 'string-or-boolean',
191
+ title: 'string',
192
+ }),
193
+ SListbox: defineOwnedAttributeSchema({
194
+ 'aria-describedby': 'string',
195
+ 'data-testid': 'data',
196
+ }),
197
+ SMarker: defineOwnedAttributeSchema({}),
198
+ SMenuSurface: defineOwnedAttributeSchema({
199
+ id: 'string',
200
+ }),
201
+ SSegmentedControl: defineOwnedAttributeSchema({
202
+ 'aria-describedby': 'string',
203
+ 'data-testid': 'data',
204
+ }),
205
+ SSelect: defineOwnedAttributeSchema({
206
+ 'aria-describedby': 'string',
207
+ 'aria-label': 'string',
208
+ 'data-task-mapping-internal-value': 'data',
209
+ 'data-task-mapping-kind': 'data',
210
+ 'data-test': 'data',
211
+ 'data-testid': 'data',
212
+ }),
213
+ STextarea: defineOwnedAttributeSchema({
214
+ 'aria-describedby': 'string',
215
+ 'aria-label': 'string',
216
+ 'data-testid': 'data',
217
+ autocomplete: 'string',
218
+ onBlur: 'listener',
219
+ }),
220
+ SActionList: defineOwnedAttributeSchema({}),
221
+ SActionListItem: defineOwnedAttributeSchema({
222
+ 'data-testid': 'data',
223
+ role: 'string',
224
+ }),
225
+ SActionCard: defineOwnedAttributeSchema({
226
+ 'data-testid': 'data',
227
+ onFocusout: 'listener',
228
+ }),
229
+ SBadge: defineOwnedAttributeSchema({
230
+ 'data-testid': 'data',
231
+ title: 'string',
232
+ }),
233
+ SCodeBlock: defineOwnedAttributeSchema({
234
+ 'aria-label': 'string',
235
+ 'data-testid': 'data',
236
+ }),
237
+ SCodeEditor: defineOwnedAttributeSchema({
238
+ 'aria-label': 'string',
239
+ 'aria-labelledby': 'string',
240
+ autofocus: 'boolean-attribute',
241
+ }),
242
+ SCodeSearchPanel: defineOwnedAttributeSchema({}),
243
+ SCopyField: defineOwnedAttributeSchema({}),
244
+ SDocBlock: defineOwnedAttributeSchema({}),
245
+ SEmptyState: defineOwnedAttributeSchema({}),
246
+ SExpandableText: defineOwnedAttributeSchema({
247
+ 'data-testid': 'data',
248
+ }),
249
+ SJsonTree: defineOwnedAttributeSchema({}),
250
+ SKeyValueGrid: defineOwnedAttributeSchema({}),
251
+ SLinkedSystemsList: defineOwnedAttributeSchema({}),
252
+ SMessage: defineOwnedAttributeSchema({
253
+ 'data-testid': 'data',
254
+ }),
255
+ SMetricCard: defineOwnedAttributeSchema({}),
256
+ SProgressBar: defineOwnedAttributeSchema({}),
257
+ SProgressIndicator: defineOwnedAttributeSchema({}),
258
+ SSectionHeader: defineOwnedAttributeSchema({}),
259
+ SStatus: defineOwnedAttributeSchema({}),
260
+ STable: defineOwnedAttributeSchema({}),
261
+ SToastContainer: defineOwnedAttributeSchema({}),
262
+ STooltip: defineOwnedAttributeSchema({}),
263
+ STooltipAnchor: defineOwnedAttributeSchema({
264
+ 'aria-describedby': 'string',
265
+ 'data-testid': 'data',
266
+ id: 'string',
267
+ }),
268
+ SVirtualList: defineOwnedAttributeSchema({}),
269
+ SAsyncState: defineOwnedAttributeSchema({}),
270
+ SDataGrid: defineOwnedAttributeSchema({}),
271
+ SSqlEditor: defineOwnedAttributeSchema({}),
272
+ SGraphViewport: defineOwnedAttributeSchema({}),
273
+ SBottomNav: defineOwnedAttributeSchema({}),
274
+ SBreadcrumbs: defineOwnedAttributeSchema({
275
+ 'aria-label': 'string',
276
+ }),
277
+ SCatalogNavigator: defineOwnedAttributeSchema({
278
+ 'aria-label': 'string',
279
+ }),
280
+ STab: defineOwnedAttributeSchema({
281
+ 'aria-label': 'string',
282
+ 'data-terminal-id': 'data',
283
+ 'data-testid': 'data',
284
+ onDblclick: 'listener',
285
+ onMousedown: 'listener',
286
+ title: 'string',
287
+ }),
288
+ STabList: defineOwnedAttributeSchema({
289
+ 'aria-label': 'string',
290
+ }),
291
+ STabPanel: defineOwnedAttributeSchema({
292
+ 'data-terminal-id': 'data',
293
+ 'data-testid': 'data',
294
+ }),
295
+ STabPanels: defineOwnedAttributeSchema({}),
296
+ STabs: defineOwnedAttributeSchema({
297
+ 'aria-label': 'string',
298
+ }),
299
+ SWizardSteps: defineOwnedAttributeSchema({
300
+ 'data-wizard-owner': 'data',
301
+ }),
302
+ SPersonaProjectPicker: defineOwnedAttributeSchema({}),
303
+ SPersonaRunWorkbench: defineOwnedAttributeSchema({}),
304
+ } as const)
305
+
306
+ export type OwnedAttributeComponent = keyof typeof ownedAttributeSchemas
307
+ type OwnedAttributeName<Component extends OwnedAttributeComponent> = keyof (typeof ownedAttributeSchemas)[Component] & string
308
+ type OwnedAttributeKind<Component extends OwnedAttributeComponent, Name extends OwnedAttributeName<Component>> =
309
+ (typeof ownedAttributeSchemas)[Component][Name] & OwnedAttributeValueKind
310
+
311
+ type KindAttributeValue<Kind extends OwnedAttributeValueKind> =
312
+ Kind extends 'listener' ? (payload: Event) => void
313
+ : Kind extends 'string' ? string
314
+ : Kind extends 'string-or-boolean' ? string | boolean
315
+ : Kind extends 'string-or-number' ? string | number
316
+ : Kind extends 'data' | 'aria' ? string | number | boolean
317
+ : Kind extends 'aria-boolean' ? boolean | 'true' | 'false'
318
+ : Kind extends 'aria-current' ? boolean | 'page' | 'step' | 'location' | 'date' | 'time'
319
+ : Kind extends 'aria-haspopup' ? boolean | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'
320
+ : Kind extends 'aria-pressed' ? boolean | 'mixed'
321
+ : Kind extends 'input-mode' ? 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url'
322
+ : Kind extends 'boolean-attribute' ? string | boolean
323
+ : never
324
+
325
+ type OwnedAttributeValue<Name extends string, Kind extends OwnedAttributeValueKind> =
326
+ Name extends keyof HTMLAttributes ? NonNullable<HTMLAttributes[Name]>
327
+ : Name extends keyof InputHTMLAttributes ? NonNullable<InputHTMLAttributes[Name]>
328
+ : Name extends keyof TextareaHTMLAttributes ? NonNullable<TextareaHTMLAttributes[Name]>
329
+ : Name extends keyof AnchorHTMLAttributes ? NonNullable<AnchorHTMLAttributes[Name]>
330
+ : KindAttributeValue<Kind>
331
+
332
+ export type OwnedAttributeBindings<Component extends OwnedAttributeComponent> = Readonly<Partial<{
333
+ [Name in OwnedAttributeName<Component>]: OwnedAttributeValue<Name, OwnedAttributeKind<Component, Name>>
334
+ }>>
335
+
336
+ export interface OwnedAttrsOptions<Component extends OwnedAttributeComponent> {
337
+ /** Имя public-компонента для диагностики контракта. / Public component name used in contract diagnostics. */
338
+ component: Component
339
+ /** Семантический/native-элемент, владеющий атрибутами. / Semantic/native element that owns the attributes. */
340
+ owner: string
341
+ }
342
+
343
+ export interface OwnedAttrsContract<Component extends OwnedAttributeComponent> {
344
+ /**
345
+ * Возвращает проверенные exact non-prop атрибуты для семантического owner.
346
+ * Returns validated exact non-prop attributes for the semantic owner.
347
+ */
348
+ bindings: () => OwnedAttributeBindings<Component>
349
+ /**
350
+ * Отклоняет любые attrs, когда у состояния нет семантического owner.
351
+ * Rejects every attr when the current state has no semantic owner.
352
+ */
353
+ reject: (reason: string) => void
354
+ /** Читает разрешённый строковый attr и отклоняет неверный runtime-тип. / Reads an allowed string attr and rejects an invalid runtime type. */
355
+ string: (name: OwnedAttributeName<Component>) => string | undefined
356
+ }
357
+
358
+ /**
359
+ * Объединяет ARIA ID-reference lists без повторов. / Merges ARIA ID-reference lists without duplicates.
360
+ */
361
+ export function mergeIdReferences(...references: Array<string | undefined>): string | undefined {
362
+ const tokens = references.flatMap((reference) => reference?.trim().split(/\s+/).filter(Boolean) ?? [])
363
+ return tokens.length > 0 ? [...new Set(tokens)].join(' ') : undefined
364
+ }
365
+
366
+ function isListener(value: unknown): boolean {
367
+ return typeof value === 'function'
368
+ || (Array.isArray(value) && value.length > 0 && value.every((candidate) => typeof candidate === 'function'))
369
+ }
370
+
371
+ function isAllowedAttributeValue(kind: OwnedAttributeValueKind, value: unknown): boolean {
372
+ if (value === undefined || value === null) return true
373
+ if (kind === 'listener') return isListener(value)
374
+ if (kind === 'string') return typeof value === 'string'
375
+ if (kind === 'aria-boolean') return typeof value === 'boolean' || value === 'true' || value === 'false'
376
+ if (kind === 'aria-current') {
377
+ return typeof value === 'boolean' || ['page', 'step', 'location', 'date', 'time'].includes(String(value))
378
+ }
379
+ if (kind === 'aria-haspopup') {
380
+ return typeof value === 'boolean' || ['menu', 'listbox', 'tree', 'grid', 'dialog'].includes(String(value))
381
+ }
382
+ if (kind === 'aria-pressed') return typeof value === 'boolean' || value === 'mixed'
383
+ if (kind === 'input-mode') {
384
+ return ['none', 'text', 'decimal', 'numeric', 'tel', 'search', 'email', 'url'].includes(String(value))
385
+ }
386
+ if (kind === 'string-or-boolean' || kind === 'boolean-attribute') {
387
+ return typeof value === 'string' || typeof value === 'boolean'
388
+ }
389
+ if (kind === 'string-or-number') return typeof value === 'string' || typeof value === 'number'
390
+ return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
391
+ }
392
+
393
+ function attributeBoundaryError(component: string, owner: string, message: string): Error {
394
+ return new Error(`${component}: ${message} на границе ${owner}. / ${message} at the ${owner} boundary.`)
395
+ }
396
+
397
+ /**
398
+ * Маршрутизирует только exact-audited native/data/ARIA/listener attrs к одному owner.
399
+ * Routes only exact-audited native/data/ARIA/listener attrs to one owner.
400
+ */
401
+ export function useOwnedAttrs<Component extends OwnedAttributeComponent>(
402
+ options: OwnedAttrsOptions<Component>,
403
+ ): OwnedAttrsContract<Component> {
404
+ const attrs = useAttrs()
405
+ const schema: OwnedAttributeSchema = ownedAttributeSchemas[options.component]
406
+
407
+ const validate = (): void => {
408
+ for (const [name, value] of Object.entries(attrs)) {
409
+ if (name === 'class' || name === 'style') {
410
+ throw attributeBoundaryError(
411
+ options.component,
412
+ options.owner,
413
+ `raw "${name}" запрещён; атрибут "${name}" не объявлен в exact owned-attrs schema; используйте типизированные props/tokens UI-kit или consumer layout wrapper. Raw "${name}" is forbidden and is not declared by the exact owned-attrs schema; use typed UI-kit props/tokens or a consumer layout wrapper`,
414
+ )
415
+ }
416
+ const kind = schema[name]
417
+ if (!kind) {
418
+ throw attributeBoundaryError(
419
+ options.component,
420
+ options.owner,
421
+ `атрибут "${name}" не объявлен в exact owned-attrs schema; используйте типизированные props/emits/tokens UI-kit или consumer layout wrapper. Attribute "${name}" is not declared by the exact owned-attrs schema; use typed UI-kit props/emits/tokens or a consumer layout wrapper`,
422
+ )
423
+ }
424
+ if (!isAllowedAttributeValue(kind, value)) {
425
+ throw attributeBoundaryError(
426
+ options.component,
427
+ options.owner,
428
+ `атрибут "${name}" имеет недопустимый runtime-тип ${typeof value}; ожидается ${kind}. Attribute "${name}" has invalid runtime type ${typeof value}; expected ${kind}`,
429
+ )
430
+ }
431
+ }
432
+ }
433
+
434
+ const bindings = (): OwnedAttributeBindings<Component> => {
435
+ validate()
436
+ return Object.fromEntries(Object.entries(attrs)) as OwnedAttributeBindings<Component>
437
+ }
438
+
439
+ const string = (name: OwnedAttributeName<Component>): string | undefined => {
440
+ const kind = schema[name]
441
+ if (kind !== 'string') {
442
+ throw attributeBoundaryError(
443
+ options.component,
444
+ options.owner,
445
+ `атрибут "${name}" не объявлен строковым. Attribute "${name}" is not declared as a string`,
446
+ )
447
+ }
448
+ validate()
449
+ const value = attrs[name]
450
+ if (value === undefined || value === null) return undefined
451
+ return value as string
452
+ }
453
+
454
+ const reject = (reason: string): void => {
455
+ const names = Object.keys(attrs)
456
+ if (names.length === 0) return
457
+ throw new Error(
458
+ `${options.component}: attrs ${names.map((name) => `"${name}"`).join(', ')} не имеют владельца (${reason}). `
459
+ + `Attributes ${names.map((name) => `"${name}"`).join(', ')} have no owner (${reason}).`,
460
+ )
461
+ }
462
+
463
+ return { bindings, reject, string }
464
+ }
@@ -0,0 +1,129 @@
1
+ import {
2
+ defineComponent,
3
+ inject,
4
+ onBeforeUnmount,
5
+ provide,
6
+ watchEffect,
7
+ type InjectionKey,
8
+ type PropType,
9
+ } from 'vue'
10
+
11
+ export interface PassiveContentContract {
12
+ /** Имя компонента-владельца для точной runtime-диагностики. / Owner name used in precise runtime diagnostics. */
13
+ owner: string
14
+ /** Имя пассивной области публичного slot contract. / Name of the passive public slot region. */
15
+ region: string
16
+ /** Отключает запрет, когда поверхность семантически не интерактивна. / Disables the guard while the surface is semantically passive. */
17
+ active?: () => boolean
18
+ /** Подсказка о правильном sibling API владельца. / Guidance to the owner's sibling API. */
19
+ guidance?: string
20
+ }
21
+
22
+ export interface InteractiveLeafContract {
23
+ /** Канонический leaf owner интерактивного поведения. / Canonical leaf owner of the interactive behavior. */
24
+ owner: string
25
+ /** Учитывает leaf только когда он действительно интерактивен. / Counts the leaf only while it is actually interactive. */
26
+ active?: () => boolean
27
+ }
28
+
29
+ interface InteractiveLeafRegistration extends InteractiveLeafContract {
30
+ readonly id: symbol
31
+ }
32
+
33
+ interface PassiveContentBoundary {
34
+ register: (registration: InteractiveLeafRegistration) => () => void
35
+ assert: () => void
36
+ }
37
+
38
+ const passiveContentBoundaryKey: InjectionKey<PassiveContentBoundary> = Symbol('passiveContentBoundary')
39
+
40
+ /**
41
+ * Ограничивает typed passive-content contract точным slot-поддеревом без дополнительного DOM-узла.
42
+ * Scopes a typed passive-content contract to the exact slot subtree without adding a DOM node.
43
+ */
44
+ export const PassiveContentBoundary = defineComponent({
45
+ name: 'PassiveContentBoundary',
46
+ props: {
47
+ contract: {
48
+ type: Object as PropType<PassiveContentContract>,
49
+ required: true,
50
+ },
51
+ },
52
+ setup(props, { slots }) {
53
+ usePassiveContentContract(props.contract)
54
+ return () => slots.default?.()
55
+ },
56
+ })
57
+
58
+ function passiveContentError(
59
+ contract: PassiveContentContract,
60
+ registration: InteractiveLeafRegistration,
61
+ ): Error {
62
+ const guidance = contract.guidance ? ` ${contract.guidance}` : ''
63
+ return new Error(
64
+ `${contract.owner}: ${contract.region} не допускает интерактивный leaf ${registration.owner}; `
65
+ + `интерактивное действие должно быть sibling-узлом.`
66
+ + ` / ${contract.owner}: ${contract.region} does not allow the interactive leaf `
67
+ + `${registration.owner}; render the interactive action as a sibling.${guidance}`,
68
+ )
69
+ }
70
+
71
+ /**
72
+ * Создаёт typed registration boundary для пассивного slot-контента без DOM-инспекции.
73
+ * Creates a typed registration boundary for passive slot content without DOM inspection.
74
+ */
75
+ export function usePassiveContentContract(contract: PassiveContentContract): void {
76
+ const parentBoundary = inject(passiveContentBoundaryKey, null)
77
+ const registrations = new Map<symbol, InteractiveLeafRegistration>()
78
+
79
+ const assert = (): void => {
80
+ if (contract.active?.() === false) return
81
+ const interactive = [...registrations.values()].find((registration) => registration.active?.() !== false)
82
+ if (interactive) throw passiveContentError(contract, interactive)
83
+ }
84
+
85
+ const boundary: PassiveContentBoundary = {
86
+ register: (registration) => {
87
+ if (registrations.has(registration.id)) {
88
+ throw new Error(`${contract.owner}: duplicate interactive leaf registration from ${registration.owner}`)
89
+ }
90
+ registrations.set(registration.id, registration)
91
+ let unregisterParent: (() => void) | undefined
92
+ try {
93
+ assert()
94
+ unregisterParent = parentBoundary?.register(registration)
95
+ } catch (error) {
96
+ registrations.delete(registration.id)
97
+ unregisterParent?.()
98
+ throw error
99
+ }
100
+ return () => {
101
+ registrations.delete(registration.id)
102
+ unregisterParent?.()
103
+ }
104
+ },
105
+ assert,
106
+ }
107
+
108
+ provide(passiveContentBoundaryKey, boundary)
109
+ watchEffect(assert)
110
+ onBeforeUnmount(() => registrations.clear())
111
+ }
112
+
113
+ /**
114
+ * Регистрирует UI-kit leaf во всех родительских passive boundaries.
115
+ * Registers a UI-kit leaf with every parent passive boundary.
116
+ */
117
+ export function useInteractiveLeafRegistration(contract: InteractiveLeafContract): void {
118
+ const boundary = inject(passiveContentBoundaryKey, null)
119
+ if (!boundary) return
120
+ const registration: InteractiveLeafRegistration = {
121
+ ...contract,
122
+ id: Symbol(contract.owner),
123
+ }
124
+ const unregister = boundary.register(registration)
125
+ watchEffect(() => {
126
+ if (contract.active?.() !== false) boundary.assert()
127
+ })
128
+ onBeforeUnmount(unregister)
129
+ }
@@ -0,0 +1,81 @@
1
+ export type PointerInteractionCursor = 'col-resize' | 'row-resize'
2
+
3
+ export interface PointerInteractionLeaseOptions {
4
+ cursor: PointerInteractionCursor
5
+ }
6
+
7
+ export interface PointerInteractionLease {
8
+ release: () => void
9
+ }
10
+
11
+ interface ActivePointerInteraction {
12
+ id: symbol
13
+ cursor: PointerInteractionCursor
14
+ }
15
+
16
+ const activeInteractions: ActivePointerInteraction[] = []
17
+ let lockedBody: HTMLElement | null = null
18
+ let preservedCursor = ''
19
+ let preservedUserSelect = ''
20
+
21
+ const applyActiveInteraction = (): void => {
22
+ const interaction = activeInteractions.at(-1)
23
+ if (!lockedBody || !interaction) return
24
+ lockedBody.style.cursor = interaction.cursor
25
+ lockedBody.style.userSelect = 'none'
26
+ }
27
+
28
+ const restoreBodyContract = (): void => {
29
+ if (!lockedBody) return
30
+ lockedBody.style.cursor = preservedCursor
31
+ lockedBody.style.userSelect = preservedUserSelect
32
+ lockedBody = null
33
+ preservedCursor = ''
34
+ preservedUserSelect = ''
35
+ }
36
+
37
+ /**
38
+ * Захватывает общий cursor/user-select контракт на время pointer-взаимодействия.
39
+ * Acquires the shared cursor/user-select contract for one pointer interaction.
40
+ */
41
+ export function acquirePointerInteractionLease(
42
+ options: PointerInteractionLeaseOptions,
43
+ ): PointerInteractionLease {
44
+ if (typeof document === 'undefined') {
45
+ throw new Error('pointerInteractionLease: document недоступен вне браузерного runtime')
46
+ }
47
+
48
+ const body = document.body
49
+ if (activeInteractions.length === 0) {
50
+ lockedBody = body
51
+ preservedCursor = body.style.cursor
52
+ preservedUserSelect = body.style.userSelect
53
+ } else if (lockedBody !== body) {
54
+ throw new Error('pointerInteractionLease: document.body изменился во время активного interaction')
55
+ }
56
+
57
+ const interaction: ActivePointerInteraction = {
58
+ id: Symbol('pointer-interaction'),
59
+ cursor: options.cursor,
60
+ }
61
+ let active = true
62
+ activeInteractions.push(interaction)
63
+ applyActiveInteraction()
64
+
65
+ return {
66
+ release: () => {
67
+ if (!active) return
68
+ active = false
69
+ const index = activeInteractions.findIndex(candidate => candidate.id === interaction.id)
70
+ if (index < 0) {
71
+ throw new Error('pointerInteractionLease: активный lease отсутствует в реестре')
72
+ }
73
+ activeInteractions.splice(index, 1)
74
+ if (activeInteractions.length === 0) {
75
+ restoreBodyContract()
76
+ return
77
+ }
78
+ applyActiveInteraction()
79
+ },
80
+ }
81
+ }