@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,442 @@
1
+ <template>
2
+ <div
3
+ v-if="placement === 'top'"
4
+ v-bind="ownedAttrs.bindings()"
5
+ class="s-tabs-header"
6
+ data-tabs-part="header"
7
+ :data-placement="placement"
8
+ :data-position="headerPosition"
9
+ :data-inset="inset"
10
+ :data-divider="divider"
11
+ :class="{
12
+ 's-tabs-header--dividerless': !divider,
13
+ }"
14
+ >
15
+ <div class="s-tabs-header__content" :style="{ paddingRight: controlsPadding }">
16
+ <div
17
+ ref="tabListWrapperRef"
18
+ class="s-tab-list-wrapper"
19
+ :class="scrollClasses"
20
+ @wheel.passive="onWheelTabList"
21
+ >
22
+ <div
23
+ ref="tabListRef"
24
+ class="s-tab-list"
25
+ @scroll.passive="updateScrollState"
26
+ >
27
+ <div
28
+ v-if="$slots.leading"
29
+ class="s-tab-list__leading"
30
+ :role="leadingAriaLabel ? 'group' : undefined"
31
+ :aria-label="leadingAriaLabel"
32
+ >
33
+ <slot name="leading" />
34
+ </div>
35
+ <div
36
+ class="s-tab-list__tabs"
37
+ :id="tabListId"
38
+ role="tablist"
39
+ :aria-label="ariaLabel"
40
+ aria-orientation="horizontal"
41
+ >
42
+ <slot />
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ <div
48
+ v-if="$slots.controls"
49
+ ref="controlsRef"
50
+ class="s-tabs-header__controls"
51
+ role="group"
52
+ :aria-label="controlsAriaLabel"
53
+ >
54
+ <slot name="controls" />
55
+ </div>
56
+ </div>
57
+ <div
58
+ v-else
59
+ v-bind="ownedAttrs.bindings()"
60
+ ref="tabListWrapperRef"
61
+ class="s-tab-list-wrapper"
62
+ :data-placement="placement"
63
+ :data-inset="inset"
64
+ :data-divider="divider"
65
+ :class="scrollClasses"
66
+ @wheel.passive="onWheelTabList"
67
+ >
68
+ <div
69
+ ref="tabListRef"
70
+ class="s-tab-list s-tab-list--bottom"
71
+ :style="{ paddingRight: controlsPadding }"
72
+ @scroll.passive="updateScrollState"
73
+ >
74
+ <div
75
+ v-if="$slots.leading"
76
+ class="s-tab-list__leading"
77
+ :role="leadingAriaLabel ? 'group' : undefined"
78
+ :aria-label="leadingAriaLabel"
79
+ >
80
+ <slot name="leading" />
81
+ </div>
82
+ <div
83
+ class="s-tab-list__tabs"
84
+ :id="tabListId"
85
+ role="tablist"
86
+ :aria-label="ariaLabel"
87
+ aria-orientation="horizontal"
88
+ >
89
+ <slot />
90
+ </div>
91
+ </div>
92
+ <div
93
+ v-if="$slots.controls"
94
+ ref="controlsRef"
95
+ class="s-tabs-header__controls"
96
+ role="group"
97
+ :aria-label="controlsAriaLabel"
98
+ >
99
+ <slot name="controls" />
100
+ </div>
101
+ </div>
102
+ </template>
103
+
104
+ <script setup lang="ts">
105
+ import {
106
+ computed,
107
+ inject,
108
+ nextTick,
109
+ onBeforeUnmount,
110
+ onMounted,
111
+ onUpdated,
112
+ ref,
113
+ watch,
114
+ type VNodeChild,
115
+ } from 'vue'
116
+ import {
117
+ tabsContextKey,
118
+ type STabsContext,
119
+ } from '../_internal/tabsContext'
120
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs'
121
+ import { useInteractiveLeafRegistration } from '../../../internal/passiveContentContract'
122
+
123
+ defineOptions({
124
+ inheritAttrs: false,
125
+ })
126
+
127
+ /**
128
+ * Публичный layout/overflow owner для tablist и внешних action regions.
129
+ * Public layout and overflow owner for the tablist and external action regions.
130
+ */
131
+ export interface Props {
132
+ /** Размещение list относительно panels. / List placement relative to panels. */
133
+ placement?: 'top' | 'bottom'
134
+ /** Показывает разделитель header. / Shows the header divider. */
135
+ divider?: boolean
136
+ /** Добавляет внутренний inline inset для scroll row. / Adds an inline inset to the scroll row. */
137
+ inset?: 'none' | 'compact' | 'content'
138
+ /** Sticky фиксирует header в scroll owner, static оставляет его в потоке. / Sticky pins the header in its scroll owner; static keeps it in flow. */
139
+ headerPosition?: 'sticky' | 'static'
140
+ /** Accessible name role=tablist. / Accessible name for role=tablist. */
141
+ ariaLabel?: string
142
+ /** Стабильный id tablist для aria-controls внешних owners. / Stable tablist id for external aria-controls owners. */
143
+ tabListId?: string
144
+ /** Accessible name leading controls; включает role=group. / Accessible name for leading controls; enables role=group. */
145
+ leadingAriaLabel?: string
146
+ /** Accessible name header actions. / Accessible name for header actions. */
147
+ controlsAriaLabel?: string
148
+ }
149
+
150
+ const props = withDefaults(defineProps<Props>(), {
151
+ placement: 'top',
152
+ divider: true,
153
+ inset: 'none',
154
+ headerPosition: 'sticky',
155
+ ariaLabel: 'Вкладки',
156
+ tabListId: undefined,
157
+ leadingAriaLabel: undefined,
158
+ controlsAriaLabel: 'Действия вкладок',
159
+ })
160
+
161
+ watch(
162
+ () => props.tabListId,
163
+ (tabListId) => {
164
+ if (tabListId !== undefined && (!tabListId.trim() || tabListId !== tabListId.trim())) {
165
+ throw new TypeError('STabList: tabListId must be a non-empty string without surrounding whitespace')
166
+ }
167
+ },
168
+ { immediate: true },
169
+ )
170
+
171
+ defineSlots<{
172
+ /** STab compositions, зарегистрированные текущим owner. / STab compositions registered by the current owner. */
173
+ default?: () => VNodeChild
174
+ /** Controls before tablist semantics. / Controls before tablist semantics. */
175
+ leading?: () => VNodeChild
176
+ /** Header actions outside tablist semantics. / Header actions outside tablist semantics. */
177
+ controls?: () => VNodeChild
178
+ }>()
179
+ const ownedAttrs = useOwnedAttrs({ component: 'STabList', owner: 'active tab-list layout root' })
180
+ useInteractiveLeafRegistration({ owner: 'STabList' })
181
+
182
+ const maybeCtx = inject<STabsContext>(tabsContextKey)
183
+ if (!maybeCtx) {
184
+ throw new Error(
185
+ 'STabList должен использоваться внутри STabs. / STabList must be used inside STabs.',
186
+ )
187
+ }
188
+ const ctx = maybeCtx
189
+
190
+ const controlsRef = ref<HTMLElement | null>(null)
191
+ const controlsWidth = ref(0)
192
+ const tabListWrapperRef = ref<HTMLElement | null>(null)
193
+ const tabListRef = ref<HTMLElement | null>(null)
194
+ const isScrollable = ref(false)
195
+ const isScrolledToStart = ref(true)
196
+ const isScrolledToEnd = ref(false)
197
+
198
+ let resizeObserver: ResizeObserver | null = null
199
+ let mutationObserver: MutationObserver | null = null
200
+ let controlsResizeObserver: ResizeObserver | null = null
201
+ let observedControlsElement: HTMLElement | null = null
202
+ const animationFrameIds = new Set<number>()
203
+
204
+ const controlsPadding = computed(() => {
205
+ if (!controlsWidth.value) return '0px'
206
+ return `${controlsWidth.value + 8}px`
207
+ })
208
+
209
+ const scrollClasses = computed(() => ({
210
+ 'is-scrollable': isScrollable.value,
211
+ 'is-scrolled-to-start': isScrolledToStart.value,
212
+ 'is-scrolled-to-end': isScrolledToEnd.value,
213
+ }))
214
+
215
+ const getListEl = (): HTMLElement | null => tabListRef.value
216
+
217
+ const updateControlsWidth = (): void => {
218
+ controlsWidth.value = controlsRef.value?.offsetWidth ?? 0
219
+ }
220
+
221
+ const syncControlsResizeObserver = (): void => {
222
+ const nextElement = controlsRef.value
223
+ if (nextElement === observedControlsElement) return
224
+ controlsResizeObserver?.disconnect()
225
+ controlsResizeObserver = null
226
+ observedControlsElement = nextElement
227
+ if (typeof ResizeObserver !== 'undefined' && nextElement) {
228
+ controlsResizeObserver = new ResizeObserver(updateControlsWidth)
229
+ controlsResizeObserver.observe(nextElement)
230
+ }
231
+ }
232
+
233
+ const scheduleAnimationFrame = (callback: () => void): void => {
234
+ if (typeof requestAnimationFrame === 'undefined') {
235
+ callback()
236
+ return
237
+ }
238
+ let frameId = -1
239
+ let completedSynchronously = false
240
+ frameId = requestAnimationFrame(() => {
241
+ completedSynchronously = true
242
+ if (frameId >= 0) animationFrameIds.delete(frameId)
243
+ callback()
244
+ })
245
+ if (!completedSynchronously) animationFrameIds.add(frameId)
246
+ }
247
+
248
+ const updateScrollState = (): void => {
249
+ const element = getListEl()
250
+ if (!element) return
251
+
252
+ const scrollable = element.scrollWidth > element.clientWidth
253
+ isScrollable.value = scrollable
254
+ if (scrollable) {
255
+ isScrolledToStart.value = element.scrollLeft <= 1
256
+ isScrolledToEnd.value = element.scrollWidth - element.scrollLeft - element.clientWidth <= 1
257
+ return
258
+ }
259
+
260
+ isScrolledToStart.value = true
261
+ isScrolledToEnd.value = true
262
+ }
263
+
264
+ const scrollActiveIntoCenterDom = (): void => {
265
+ const listElement = getListEl()
266
+ if (!listElement) return
267
+ const activeTabElement = ctx.getActiveTabElement()
268
+ if (!activeTabElement) return
269
+ const tabCenter = activeTabElement.offsetLeft + activeTabElement.offsetWidth / 2
270
+ const targetLeft = Math.max(0, tabCenter - listElement.clientWidth / 2)
271
+ if (typeof listElement.scrollTo === 'function') {
272
+ listElement.scrollTo({ left: targetLeft, behavior: 'smooth' })
273
+ return
274
+ }
275
+ listElement.scrollLeft = targetLeft
276
+ }
277
+
278
+ const tabListRegistrationKey = ctx.registerTabList(scrollActiveIntoCenterDom)
279
+
280
+ const onWheelTabList = (event: WheelEvent): void => {
281
+ const element = getListEl()
282
+ if (!element) return
283
+ const delta = Math.abs(event.deltaY) >= Math.abs(event.deltaX) ? event.deltaY : event.deltaX
284
+ if (delta === 0) return
285
+ if (typeof element.scrollBy === 'function') {
286
+ element.scrollBy({ left: delta, behavior: 'auto' })
287
+ return
288
+ }
289
+ element.scrollLeft += delta
290
+ }
291
+
292
+ onMounted(() => {
293
+ updateControlsWidth()
294
+ syncControlsResizeObserver()
295
+ void nextTick(() => {
296
+ updateScrollState()
297
+ const listElement = getListEl()
298
+ if (!listElement) return
299
+ if (typeof ResizeObserver !== 'undefined') {
300
+ resizeObserver = new ResizeObserver(updateScrollState)
301
+ resizeObserver.observe(listElement)
302
+ }
303
+ if (typeof MutationObserver !== 'undefined') {
304
+ mutationObserver = new MutationObserver(() => {
305
+ updateScrollState()
306
+ scheduleAnimationFrame(scrollActiveIntoCenterDom)
307
+ })
308
+ mutationObserver.observe(listElement, {
309
+ characterData: true,
310
+ childList: true,
311
+ subtree: true,
312
+ })
313
+ }
314
+ scheduleAnimationFrame(scrollActiveIntoCenterDom)
315
+ })
316
+ })
317
+
318
+ onUpdated(() => {
319
+ updateControlsWidth()
320
+ syncControlsResizeObserver()
321
+ })
322
+
323
+ watch(() => ctx.activeKey.value, () => {
324
+ void nextTick(() => scheduleAnimationFrame(scrollActiveIntoCenterDom))
325
+ })
326
+
327
+ defineExpose({
328
+ scrollActiveIntoCenter: scrollActiveIntoCenterDom,
329
+ })
330
+
331
+ onBeforeUnmount(() => {
332
+ resizeObserver?.disconnect()
333
+ mutationObserver?.disconnect()
334
+ controlsResizeObserver?.disconnect()
335
+ observedControlsElement = null
336
+ if (typeof cancelAnimationFrame !== 'undefined') {
337
+ for (const frameId of animationFrameIds) cancelAnimationFrame(frameId)
338
+ }
339
+ animationFrameIds.clear()
340
+ ctx.unregisterTabList(tabListRegistrationKey)
341
+ })
342
+ </script>
343
+
344
+ <style lang="postcss" scoped>
345
+ @reference "../../../styles/reference.css";
346
+
347
+ .s-tabs-header {
348
+ @apply sticky top-0 border-b border-surface-200 dark:border-surface-700;
349
+ z-index: var(--s-layer-affordance);
350
+ height: var(--s-tab-row-height);
351
+ min-height: var(--s-tab-row-height);
352
+ max-height: var(--s-tab-row-height);
353
+ background: inherit;
354
+ }
355
+
356
+ .s-tabs-header--dividerless { border-bottom: 0; }
357
+ .s-tabs-header[data-position='static'] { position: relative; }
358
+
359
+ [data-inset='compact'] { --s-tab-list-inset-inline: 0.75rem; }
360
+ [data-inset='content'] { --s-tab-list-inset-inline: 1rem; }
361
+
362
+ @media (min-width: 1024px) {
363
+ [data-inset='content'] { --s-tab-list-inset-inline: 1.25rem; }
364
+ }
365
+
366
+ .s-tabs-header__content { @apply h-full; }
367
+
368
+ .s-tabs-header__controls {
369
+ @apply absolute inset-y-0 right-0 flex items-center gap-1 px-2;
370
+ z-index: var(--s-layer-affordance);
371
+ }
372
+
373
+ .s-tab-list-wrapper {
374
+ @apply relative h-full shrink-0 overflow-hidden;
375
+ overscroll-behavior: contain;
376
+ }
377
+
378
+ .s-tab-list-wrapper[data-placement='bottom'] {
379
+ height: var(--s-tab-row-height);
380
+ min-height: var(--s-tab-row-height);
381
+ max-height: var(--s-tab-row-height);
382
+ }
383
+
384
+ .s-tab-list-wrapper::before,
385
+ .s-tab-list-wrapper::after {
386
+ @apply pointer-events-none absolute inset-y-0 opacity-0 transition-opacity;
387
+ z-index: var(--s-layer-sticky);
388
+ transition-duration: var(--s-motion-standard);
389
+ content: '';
390
+ width: var(--s-tab-overflow-fade-width);
391
+ }
392
+
393
+ .s-tab-list-wrapper::before {
394
+ left: 0;
395
+ background: linear-gradient(to right, var(--s-tab-list-edge-surface), transparent);
396
+ }
397
+
398
+ .s-tab-list-wrapper::after {
399
+ right: 0;
400
+ background: linear-gradient(to left, var(--s-tab-list-edge-surface), transparent);
401
+ }
402
+
403
+ .s-tab-list-wrapper.is-scrollable:not(.is-scrolled-to-start)::before,
404
+ .s-tab-list-wrapper.is-scrollable:not(.is-scrolled-to-end)::after { opacity: var(--s-tab-overflow-edge-opacity); }
405
+
406
+ .s-tab-list {
407
+ @apply flex h-full min-w-0 flex-nowrap items-center overflow-x-auto overflow-y-hidden;
408
+ padding-right: var(--s-tab-list-inset-inline);
409
+ padding-left: var(--s-tab-list-inset-inline);
410
+ scroll-padding-inline: var(--s-tab-list-inset-inline);
411
+ scrollbar-width: none;
412
+ -ms-overflow-style: none;
413
+ }
414
+
415
+ .s-tab-list::-webkit-scrollbar { display: none; }
416
+ .s-tab-list--bottom { @apply justify-between px-2; }
417
+
418
+ .s-tab-list__leading,
419
+ .s-tab-list__tabs {
420
+ @apply flex h-full min-w-max flex-none items-center;
421
+ width: var(--s-tab-list-tabs-width);
422
+ }
423
+
424
+ .s-tab-list--bottom .s-tab-list__tabs {
425
+ @apply min-w-full flex-1 justify-between;
426
+ }
427
+
428
+ .is-scrollable.is-scrolled-to-start:not(.is-scrolled-to-end) .s-tab-list {
429
+ -webkit-mask-image: linear-gradient(to right, var(--s-tab-overflow-mask-opaque) calc(100% - var(--s-tab-overflow-fade-width)), transparent 100%);
430
+ mask-image: linear-gradient(to right, var(--s-tab-overflow-mask-opaque) calc(100% - var(--s-tab-overflow-fade-width)), transparent 100%);
431
+ }
432
+
433
+ .is-scrollable:not(.is-scrolled-to-start).is-scrolled-to-end .s-tab-list {
434
+ -webkit-mask-image: linear-gradient(to right, transparent 0, var(--s-tab-overflow-mask-opaque) var(--s-tab-overflow-fade-width));
435
+ mask-image: linear-gradient(to right, transparent 0, var(--s-tab-overflow-mask-opaque) var(--s-tab-overflow-fade-width));
436
+ }
437
+
438
+ .is-scrollable:not(.is-scrolled-to-start):not(.is-scrolled-to-end) .s-tab-list {
439
+ -webkit-mask-image: linear-gradient(to right, transparent 0, var(--s-tab-overflow-mask-opaque) var(--s-tab-overflow-fade-width), var(--s-tab-overflow-mask-opaque) calc(100% - var(--s-tab-overflow-fade-width)), transparent 100%);
440
+ mask-image: linear-gradient(to right, transparent 0, var(--s-tab-overflow-mask-opaque) var(--s-tab-overflow-fade-width), var(--s-tab-overflow-mask-opaque) calc(100% - var(--s-tab-overflow-fade-width)), transparent 100%);
441
+ }
442
+ </style>
@@ -0,0 +1,165 @@
1
+ <template>
2
+ <div
3
+ v-if="unmount ? state.active : true"
4
+ v-bind="ownedAttrs.bindings()"
5
+ ref="panelElement"
6
+ v-show="unmount ? true : state.active"
7
+ class="s-tab-panel"
8
+ :id="state.panelId"
9
+ :aria-labelledby="state.tabId"
10
+ :aria-hidden="state.active ? undefined : 'true'"
11
+ :data-surface="surface"
12
+ :data-content-gap="contentGap"
13
+ :data-align="align"
14
+ :data-padding="padding"
15
+ :data-overflow="overflow"
16
+ role="tabpanel"
17
+ tabindex="0"
18
+ >
19
+ <slot />
20
+ </div>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ import {
25
+ computed,
26
+ inject,
27
+ onBeforeUnmount,
28
+ onMounted,
29
+ onUpdated,
30
+ ref,
31
+ watchEffect,
32
+ type VNodeChild,
33
+ } from 'vue'
34
+ import { tabsContextKey, type STabsContext } from '../_internal/tabsContext'
35
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs'
36
+ import { useInteractiveLeafRegistration } from '../../../internal/passiveContentContract'
37
+
38
+ defineOptions({
39
+ inheritAttrs: false,
40
+ })
41
+
42
+ /**
43
+ * Публичная panel-часть STabs со стабильной ARIA-связью с одноимённой вкладкой.
44
+ * Public STabs panel part with a stable ARIA relationship to the matching tab.
45
+ */
46
+ export interface Props {
47
+ /** Точная stable identity соответствующего STab. / Exact stable identity of the matching STab. */
48
+ name: string
49
+ /** Удаляет неактивное содержимое из DOM вместо v-show. / Removes inactive content from the DOM instead of using v-show. */
50
+ unmount?: boolean
51
+ /** Владеющая layout-поверхность панели. / Layout surface owned by the panel. */
52
+ surface?: 'default' | 'workbench-right-drawer' | 'workbench-bottom-dock'
53
+ /** Семантический интервал между непосредственными участниками. / Semantic gap between direct participants. */
54
+ contentGap?: 'none' | 'control' | 'content' | 'section' | 'panel'
55
+ /** Поперечное выравнивание участников панели. / Cross-axis alignment of panel participants. */
56
+ align?: 'stretch' | 'start' | 'center' | 'end'
57
+ /** Семантический внутренний отступ panel surface. / Semantic panel-surface padding. */
58
+ padding?: 'none' | 'control' | 'content' | 'section' | 'panel'
59
+ /** Политика переполнения panel surface. / Panel-surface overflow policy. */
60
+ overflow?: 'visible' | 'hidden' | 'auto'
61
+ }
62
+
63
+ const props = withDefaults(defineProps<Props>(), {
64
+ unmount: false,
65
+ surface: 'default',
66
+ contentGap: 'none',
67
+ align: 'stretch',
68
+ padding: 'none',
69
+ overflow: 'visible',
70
+ })
71
+ const ownedAttrs = useOwnedAttrs({ component: 'STabPanel', owner: 'mounted role=tabpanel root' })
72
+ defineSlots<{
73
+ /** Содержимое панели. / Panel content. */
74
+ default?: () => VNodeChild
75
+ }>()
76
+
77
+ const maybeCtx = inject<STabsContext>(tabsContextKey)
78
+ if (!maybeCtx) {
79
+ throw new Error(
80
+ 'STabPanel должен использоваться внутри STabs. / STabPanel must be used inside STabs.',
81
+ )
82
+ }
83
+ const ctx = maybeCtx
84
+ const panelElement = ref<HTMLElement | null>(null)
85
+ const registrationKey = ctx.registerPanel({
86
+ tabKey: props.name,
87
+ element: () => panelElement.value,
88
+ })
89
+ const state = computed(() => ctx.getPanelState(registrationKey))
90
+ useInteractiveLeafRegistration({ owner: 'STabPanel', active: () => state.value.active })
91
+
92
+ watchEffect(() => {
93
+ if (props.unmount && !state.value.active) {
94
+ ownedAttrs.reject('inactive unmounted tab panel has no DOM owner')
95
+ return
96
+ }
97
+ ownedAttrs.bindings()
98
+ })
99
+
100
+ onMounted(ctx.notifyPanelOrderChanged)
101
+ onUpdated(() => {
102
+ if (props.name !== registrationKey) {
103
+ throw new Error('STabPanel: name is immutable for the lifetime of a mounted panel')
104
+ }
105
+ ctx.notifyPanelOrderChanged()
106
+ })
107
+ onBeforeUnmount(() => ctx.unregisterPanel(registrationKey))
108
+ </script>
109
+
110
+ <style lang="postcss" scoped>
111
+ @reference "../../../styles/reference.css";
112
+
113
+ .s-tab-panel {
114
+ @apply flex h-full min-h-0 min-w-0 w-full flex-col text-surface-700 focus:outline-none;
115
+ @apply dark:text-surface-300;
116
+ }
117
+
118
+ .s-tab-panel[data-align='start'] { @apply items-start; }
119
+ .s-tab-panel[data-align='center'] { @apply items-center; }
120
+ .s-tab-panel[data-align='end'] { @apply items-end; }
121
+ .s-tab-panel[data-align='stretch'] { @apply items-stretch; }
122
+
123
+ .s-tab-panel[data-content-gap='control'] { gap: var(--s-space-control-gap); }
124
+ .s-tab-panel[data-content-gap='content'] { gap: var(--s-space-content-gap); }
125
+ .s-tab-panel[data-content-gap='section'] { gap: var(--s-space-section-gap); }
126
+ .s-tab-panel[data-content-gap='panel'] { gap: var(--s-space-panel-gap); }
127
+
128
+ .s-tab-panel[data-padding='control'] { padding: var(--s-space-control-gap); }
129
+ .s-tab-panel[data-padding='content'] { padding: var(--s-space-content-gap); }
130
+ .s-tab-panel[data-padding='section'] { padding: var(--s-space-section-gap); }
131
+ .s-tab-panel[data-padding='panel'] { padding: var(--s-space-panel-gap); }
132
+
133
+ .s-tab-panel[data-overflow='visible'] { overflow: visible; }
134
+ .s-tab-panel[data-overflow='hidden'] { overflow: hidden; }
135
+ .s-tab-panel[data-overflow='auto'] {
136
+ overflow: auto;
137
+ overscroll-behavior: contain;
138
+ scrollbar-gutter: stable;
139
+ }
140
+
141
+ .s-tab-panel[data-surface='workbench-right-drawer'],
142
+ .s-tab-panel[data-surface='workbench-bottom-dock'] {
143
+ @apply flex-1 overflow-y-auto px-5;
144
+ overscroll-behavior: contain;
145
+ scrollbar-gutter: stable;
146
+ }
147
+
148
+ .s-tab-panel[data-surface='workbench-right-drawer'] {
149
+ @apply pb-7 pt-4;
150
+ scroll-padding-block-start: 1rem;
151
+ scroll-padding-block-end: calc(var(--workbench-scroll-end-padding, 1rem) + 1rem);
152
+ }
153
+
154
+ .s-tab-panel[data-surface='workbench-bottom-dock'] {
155
+ @apply pb-6 pt-5;
156
+ scroll-padding-block-start: 1.25rem;
157
+ scroll-padding-block-end: calc(var(--workbench-scroll-end-padding, 1rem) + 1rem);
158
+ }
159
+
160
+ @media (max-width: 1023px) {
161
+ .s-tab-panel[data-surface='workbench-bottom-dock'] {
162
+ scroll-padding-block-end: calc(var(--workbench-scroll-end-padding, 1rem) + 1.5rem);
163
+ }
164
+ }
165
+ </style>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <div v-bind="ownedAttrs.bindings()" class="s-tab-panels">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { inject, onBeforeUnmount, type VNodeChild } from 'vue'
9
+ import { tabsContextKey, type STabsContext } from '../_internal/tabsContext'
10
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs'
11
+
12
+ defineOptions({ inheritAttrs: false })
13
+
14
+ defineSlots<{
15
+ /** Набор STabPanel одного owner. / STabPanel collection owned by one STabs. */
16
+ default?: () => VNodeChild
17
+ }>()
18
+
19
+ const ownedAttrs = useOwnedAttrs({ component: 'STabPanels', owner: 'tab panels stack root' })
20
+ const context = inject<STabsContext>(tabsContextKey)
21
+ if (!context) throw new Error('STabPanels must be used inside STabs')
22
+ const registrationKey = context.registerPanels()
23
+ onBeforeUnmount(() => context.unregisterPanels(registrationKey))
24
+ </script>
25
+
26
+ <style lang="postcss" scoped>
27
+ @reference "../../../styles/reference.css";
28
+
29
+ .s-tab-panels {
30
+ @apply flex min-h-0 min-w-0 w-full flex-col;
31
+ flex: var(--s-tab-panels-flex);
32
+ }
33
+ </style>