@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,178 @@
1
+ @reference "../../../styles/reference.css";
2
+
3
+ .s-code-editor {
4
+ @apply min-h-0 min-w-0;
5
+ width: 100%;
6
+ max-width: 100%;
7
+ overflow: hidden;
8
+ }
9
+
10
+ .s-code-editor[data-viewport='content'] {
11
+ max-height: var(--s-code-editor-viewport-content-max);
12
+ }
13
+
14
+ .s-code-editor[data-viewport='compact'] {
15
+ height: var(--s-code-editor-viewport-compact);
16
+ }
17
+
18
+ .s-code-editor[data-viewport='standard'] {
19
+ height: var(--s-code-editor-viewport-standard);
20
+ }
21
+
22
+ .s-code-editor[data-viewport='expanded'] {
23
+ height: var(--s-code-editor-viewport-expanded);
24
+ }
25
+
26
+ .s-code-editor[data-viewport='fill'] {
27
+ height: 100%;
28
+ }
29
+
30
+ .s-code-editor :deep(.cm-editor) {
31
+ @apply min-w-0 text-sm;
32
+ width: 100%;
33
+ max-width: 100%;
34
+ height: inherit;
35
+ max-height: inherit;
36
+ }
37
+
38
+ .s-code-editor :deep(.cm-scroller) {
39
+ max-width: 100%;
40
+ overflow: auto;
41
+ }
42
+
43
+ .s-code-editor[data-line-numbers='hidden'] :deep(.cm-gutters) {
44
+ display: none;
45
+ }
46
+
47
+ .s-code-editor[data-line-numbers='hidden'] :deep(.cm-content) {
48
+ padding-left: 0;
49
+ }
50
+
51
+ .s-code-editor[data-variant='code'] :deep(.cm-scroller) {
52
+ @apply overflow-x-auto p-3;
53
+ }
54
+
55
+ .s-code-editor[data-variant='code'] :deep(.cm-lineNumbers) {
56
+ @apply px-1.5;
57
+ }
58
+
59
+ .s-code-editor[data-variant='sql'] :deep(.cm-line) {
60
+ padding-inline: 0.75rem;
61
+ }
62
+
63
+ .s-code-editor[data-variant='sql'] :deep(.cm-content) {
64
+ min-height: 12rem;
65
+ }
66
+
67
+ .s-code-editor[data-variant='sql']:not([data-viewport='content']) :deep(.cm-content) {
68
+ min-height: 100%;
69
+ }
70
+
71
+ .s-code-editor :deep(.cm-gutters) {
72
+ @apply text-surface-400 dark:text-surface-500;
73
+ }
74
+
75
+ .s-code-editor :deep(.cm-tooltip) {
76
+ @apply rounded-md border border-surface-200 bg-surface-0 text-surface-900 shadow-lg;
77
+ @apply dark:border-surface-700 dark:bg-surface-800 dark:text-surface-100;
78
+ z-index: var(--s-layer-floating);
79
+ }
80
+
81
+ .s-code-editor :deep(.cm-panels) {
82
+ position: absolute;
83
+ inset-inline: 0;
84
+ bottom: 0;
85
+ z-index: var(--s-layer-affordance);
86
+ display: flex;
87
+ justify-content: center;
88
+ padding: 0 1rem 1rem;
89
+ border: 0;
90
+ background: transparent;
91
+ pointer-events: none;
92
+ }
93
+
94
+ .s-code-editor :deep(.cm-panel[class~='s-codemirror-search-panel-host']) {
95
+ width: min(34rem, calc(100vw - 2rem));
96
+ max-width: calc(100vw - 2rem);
97
+ margin: 0;
98
+ padding: 0;
99
+ border: 0;
100
+ background: transparent;
101
+ pointer-events: auto;
102
+ }
103
+
104
+ .s-code-editor :deep(.cm-searchMatch) {
105
+ background-color: var(--s-code-editor-search-match-background);
106
+ }
107
+
108
+ .s-code-editor :deep(.cm-searchMatch.cm-searchMatch-selected) {
109
+ background-color: var(--s-code-editor-search-match-selected-background);
110
+ outline: 1px solid var(--s-code-editor-search-match-selected-outline);
111
+ }
112
+
113
+ .s-code-editor :deep(.cm-tooltip-hover),
114
+ .s-code-editor :deep(.cm-completionInfo),
115
+ .s-code-editor :deep(.cm-tooltip-markdown) {
116
+ @apply rounded-xl border border-surface-200 bg-surface-0 text-sm text-surface-900 shadow-xl;
117
+ @apply dark:border-surface-700 dark:bg-surface-900 dark:text-surface-50;
118
+ }
119
+
120
+ .s-code-editor :deep(.cm-tooltip-autocomplete) {
121
+ @apply min-w-72 rounded-md border border-surface-200 bg-surface-50 text-surface-900 shadow-xl;
122
+ @apply dark:border-surface-700 dark:bg-surface-800 dark:text-surface-50;
123
+ }
124
+
125
+ .s-code-editor :deep(.cm-tooltip-autocomplete > ul) {
126
+ @apply m-0 max-h-72 list-none overflow-y-auto p-1 font-sans text-sm;
127
+ }
128
+
129
+ .s-code-editor :deep(.cm-tooltip-autocomplete > ul > li) {
130
+ @apply flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 text-surface-700 dark:text-surface-300;
131
+ }
132
+
133
+ .s-code-editor :deep(.cm-tooltip-autocomplete > ul > li:hover) {
134
+ @apply bg-surface-200 dark:bg-surface-700;
135
+ }
136
+
137
+ .s-code-editor :deep(.cm-tooltip-autocomplete > ul > li[aria-selected]) {
138
+ @apply bg-primary-500 text-white;
139
+ }
140
+
141
+ .s-code-editor :deep(.cm-completionLabel) {
142
+ @apply truncate font-medium;
143
+ }
144
+
145
+ .s-code-editor :deep(.cm-completionDetail) {
146
+ @apply ml-auto truncate text-xs italic opacity-70;
147
+ }
148
+
149
+ .s-code-editor :deep(.cm-completionInfo),
150
+ .s-code-editor :deep(.cm-tooltip-markdown__content) {
151
+ max-height: min(26rem, calc(100vh - 3rem));
152
+ overflow: auto;
153
+ overscroll-behavior: contain;
154
+ padding: 0.75rem 0.875rem;
155
+ line-height: 1.45;
156
+ white-space: normal;
157
+ }
158
+
159
+ .s-code-editor :deep(.cm-tooltip-markdown) {
160
+ max-width: min(34rem, calc(100vw - 2rem));
161
+ max-height: min(30rem, calc(100vh - 3rem));
162
+ overflow: hidden;
163
+ line-height: 1.45;
164
+ white-space: normal;
165
+ word-break: break-word;
166
+ }
167
+
168
+ @media (max-width: 768px) {
169
+ .s-code-editor :deep(.cm-panels) {
170
+ padding-inline: 0.75rem;
171
+ padding-bottom: 0.75rem;
172
+ }
173
+
174
+ .s-code-editor :deep(.cm-panel[class~='s-codemirror-search-panel-host']) {
175
+ width: calc(100vw - 1.5rem);
176
+ max-width: calc(100vw - 1.5rem);
177
+ }
178
+ }
@@ -0,0 +1,274 @@
1
+ <template>
2
+ <Codemirror
3
+ v-bind="ownedAttrs.bindings()"
4
+ class="s-code-editor"
5
+ :data-variant="validatedVariant"
6
+ :data-viewport="validatedViewport"
7
+ :data-line-numbers="resolvedCapabilities.lineNumbers ? 'visible' : 'hidden'"
8
+ :model-value="validatedModelValue"
9
+ :autofocus="validatedAutofocus"
10
+ :indent-with-tab="resolvedCapabilities.indentation.keyBehavior === 'indent'"
11
+ :tab-size="resolvedCapabilities.indentation.tabSize"
12
+ :extensions="resolvedExtensions"
13
+ @ready="onReady"
14
+ @update:model-value="onUpdate"
15
+ />
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ import { computed, ref, watch } from 'vue'
20
+ import { Codemirror } from 'vue-codemirror'
21
+ import { oneDark } from '@codemirror/theme-one-dark'
22
+ import { EditorSelection, EditorState, type Extension } from '@codemirror/state'
23
+ import { EditorView, lineNumbers, placeholder as codeMirrorPlaceholder } from '@codemirror/view'
24
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs'
25
+ import { useInteractiveLeafRegistration } from '../../../internal/passiveContentContract'
26
+ import { useRuntimeTheme } from '../../../internal/runtimeTheme'
27
+ import {
28
+ validateBoolean,
29
+ validateBoundedInteger,
30
+ validateDomId,
31
+ validateNonEmptyString,
32
+ } from '../../../internal/runtimeContract'
33
+ import { useTrustedCodeEditorExtensions } from '../../../internal/codeEditorExtensions'
34
+ import {
35
+ CODE_EDITOR_VARIANTS,
36
+ CODE_EDITOR_VIEWPORTS,
37
+ type CodeEditorCapabilitiesContract,
38
+ type CodeEditorLanguageContract,
39
+ validateCodeEditorCapabilities,
40
+ validateCodeEditorLanguage,
41
+ validateCodeEditorVariant,
42
+ validateCodeEditorViewport,
43
+ } from '../../../internal/codeEditorContract'
44
+ import { getCodeMirrorLanguageExtension, resolveCodeLanguageKey } from '../codeLanguages'
45
+
46
+ defineOptions({ inheritAttrs: false })
47
+
48
+ export type SCodeEditorViewport = typeof CODE_EDITOR_VIEWPORTS[number]
49
+ export type SCodeEditorVariant = typeof CODE_EDITOR_VARIANTS[number]
50
+ /** Явный язык либо детектирование из обязательного filePath. / Explicit language or detection from a required filePath. */
51
+ export type SCodeEditorLanguage = CodeEditorLanguageContract
52
+ /** Явные возможности leaf-редактора. / Explicit capabilities of the leaf editor. */
53
+ export type SCodeEditorCapabilities = CodeEditorCapabilitiesContract
54
+
55
+ /** Exact-координаты документа для cursor activity. / Exact document coordinates emitted for cursor activity. */
56
+ export interface SCodeEditorCursorActivity {
57
+ pos: number
58
+ line: number
59
+ column: number
60
+ lineFrom: number
61
+ lineTo: number
62
+ lineText: string
63
+ }
64
+
65
+ /** Public readiness snapshot без раскрытия third-party EditorView. / Public readiness snapshot without exposing the third-party EditorView instance. */
66
+ export interface SCodeEditorReadyPayload {
67
+ documentLength: number
68
+ viewport: SCodeEditorViewport
69
+ variant: SCodeEditorVariant
70
+ locked: boolean
71
+ }
72
+
73
+ /** Подтверждённое применение selection и источник вызова. / Confirmed selection application and its initiating API. */
74
+ export interface SCodeEditorSelectionAppliedPayload {
75
+ position: number
76
+ source: 'controlled' | 'imperative'
77
+ }
78
+
79
+ type AccessibleName =
80
+ | { ariaLabel: string; ariaLabelledby?: never }
81
+ | { ariaLabel?: never; ariaLabelledby: string }
82
+
83
+ /** Public SCodeEditor contract; размер задаёт только semantic viewport. / Public SCodeEditor contract; sizing is expressed only by semantic viewport. */
84
+ export type Props = AccessibleName & {
85
+ modelValue: string
86
+ language: SCodeEditorLanguage
87
+ capabilities: SCodeEditorCapabilities
88
+ readOnly?: boolean
89
+ disabled?: boolean
90
+ autofocus?: boolean
91
+ placeholder?: string
92
+ viewport?: SCodeEditorViewport
93
+ variant?: SCodeEditorVariant
94
+ nextSelectionPos?: number | null
95
+ /** DOM id supplemental status/diagnostic owner. / DOM id of a supplemental status/diagnostic owner. */
96
+ ariaDescribedby?: string
97
+ /** Помечает editor content невалидным без владения presentation диагностики. / Marks the editor content as invalid without owning diagnostic presentation. */
98
+ invalid?: boolean
99
+ }
100
+
101
+ const props = withDefaults(defineProps<Props>(), {
102
+ readOnly: false,
103
+ disabled: false,
104
+ autofocus: false,
105
+ placeholder: undefined,
106
+ viewport: 'content',
107
+ variant: 'code',
108
+ nextSelectionPos: null,
109
+ ariaDescribedby: undefined,
110
+ invalid: false,
111
+ })
112
+
113
+ const emit = defineEmits<{
114
+ 'update:modelValue': [value: string]
115
+ 'cursor-activity': [payload: SCodeEditorCursorActivity]
116
+ 'selection-applied': [payload: SCodeEditorSelectionAppliedPayload]
117
+ ready: [payload: SCodeEditorReadyPayload]
118
+ }>()
119
+
120
+ const ownedAttrs = useOwnedAttrs({ component: 'SCodeEditor', owner: 'CodeMirror root adapter' })
121
+ useInteractiveLeafRegistration({ owner: 'SCodeEditor' })
122
+
123
+ const runtimeTheme = useRuntimeTheme()
124
+ const trustedChannel = useTrustedCodeEditorExtensions()
125
+ const editorView = ref<EditorView | null>(null)
126
+ const pendingControlledSelection = ref<number | null>(null)
127
+
128
+ const validatedModelValue = computed(() => {
129
+ if (typeof props.modelValue === 'string') return props.modelValue
130
+ throw new TypeError('SCodeEditor: modelValue должен быть строкой. / SCodeEditor: modelValue must be a string.')
131
+ })
132
+
133
+ const validatedViewport = computed<SCodeEditorViewport>(() => validateCodeEditorViewport('SCodeEditor', props.viewport))
134
+ const validatedVariant = computed<SCodeEditorVariant>(() => validateCodeEditorVariant('SCodeEditor', props.variant))
135
+ const validatedAutofocus = computed(() => validateBoolean('SCodeEditor', 'autofocus', props.autofocus))
136
+ const validatedReadOnly = computed(() => validateBoolean('SCodeEditor', 'readOnly', props.readOnly))
137
+ const validatedDisabled = computed(() => validateBoolean('SCodeEditor', 'disabled', props.disabled))
138
+ const validatedInvalid = computed(() => validateBoolean('SCodeEditor', 'invalid', props.invalid))
139
+ const isLocked = computed(() => validatedReadOnly.value || validatedDisabled.value)
140
+
141
+ const resolvedLanguage = computed((): SCodeEditorLanguage => {
142
+ const language = validateCodeEditorLanguage('SCodeEditor', props.language)
143
+ const extension = getCodeMirrorLanguageExtension(language)
144
+ const languageKey = resolveCodeLanguageKey(language)
145
+ if (!extension && languageKey !== 'plaintext' && languageKey !== 'text') {
146
+ const coordinate = language.mode === 'explicit'
147
+ ? `language.id ${JSON.stringify(language.id)}`
148
+ : `language.filePath ${JSON.stringify(language.filePath)}`
149
+ throw new RangeError(`SCodeEditor: ${coordinate} не поддерживается. / SCodeEditor: ${coordinate} is not supported.`)
150
+ }
151
+ return language
152
+ })
153
+
154
+ const resolvedCapabilities = computed((): SCodeEditorCapabilities =>
155
+ validateCodeEditorCapabilities('SCodeEditor', props.capabilities))
156
+
157
+ const contentAttributes = computed((): Record<string, string> => {
158
+ if (props.ariaLabel !== undefined && props.ariaLabelledby !== undefined) {
159
+ throw new TypeError('SCodeEditor: задайте только ariaLabel или ariaLabelledby. / SCodeEditor: provide only ariaLabel or ariaLabelledby.')
160
+ }
161
+ const attributes: Record<string, string> = {}
162
+ if (props.ariaLabel !== undefined) {
163
+ attributes['aria-label'] = validateNonEmptyString('SCodeEditor', 'ariaLabel', props.ariaLabel)
164
+ } else if (props.ariaLabelledby !== undefined) {
165
+ attributes['aria-labelledby'] = validateDomId('SCodeEditor', 'ariaLabelledby', props.ariaLabelledby)
166
+ } else {
167
+ throw new TypeError('SCodeEditor: требуется ariaLabel или ariaLabelledby. / SCodeEditor: ariaLabel or ariaLabelledby is required.')
168
+ }
169
+ if (props.ariaDescribedby !== undefined) {
170
+ attributes['aria-describedby'] = validateDomId('SCodeEditor', 'ariaDescribedby', props.ariaDescribedby)
171
+ }
172
+ if (validatedInvalid.value) attributes['aria-invalid'] = 'true'
173
+ if (validatedReadOnly.value) attributes['aria-readonly'] = 'true'
174
+ if (validatedDisabled.value) attributes['aria-disabled'] = 'true'
175
+ return attributes
176
+ })
177
+
178
+ const resolvedExtensions = computed<Extension[]>(() => {
179
+ const capabilities = resolvedCapabilities.value
180
+ const language = resolvedLanguage.value
181
+ const result: Extension[] = [...(trustedChannel?.extensions.value ?? [])]
182
+ const languageExtension = getCodeMirrorLanguageExtension(language)
183
+ if (languageExtension) result.push(languageExtension)
184
+ if (runtimeTheme.value.mode === 'dark') result.push(oneDark)
185
+ if (capabilities.lineNumbers) result.push(lineNumbers())
186
+ if (props.placeholder !== undefined) result.push(codeMirrorPlaceholder(validateNonEmptyString('SCodeEditor', 'placeholder', props.placeholder)))
187
+ result.push(EditorState.readOnly.of(isLocked.value))
188
+ result.push(EditorView.editable.of(!isLocked.value))
189
+ result.push(EditorView.contentAttributes.of(contentAttributes.value))
190
+ if (capabilities.keyInterceptor && !isLocked.value) {
191
+ result.push(EditorView.domEventHandlers({ keydown: (event) => {
192
+ const handled = capabilities.keyInterceptor?.(event)
193
+ if (!validateBoolean('SCodeEditor', 'capabilities.keyInterceptor return value', handled)) return false
194
+ event.preventDefault()
195
+ return true
196
+ } }))
197
+ }
198
+ result.push(EditorView.updateListener.of((update) => {
199
+ editorView.value = update.view
200
+ if ((isLocked.value && capabilities.cursorActivity !== 'always') || (!update.selectionSet && !update.docChanged)) return
201
+ const pos = update.state.selection.main.head
202
+ const line = update.state.doc.lineAt(pos)
203
+ emit('cursor-activity', {
204
+ pos,
205
+ line: line.number,
206
+ column: pos - line.from + 1,
207
+ lineFrom: line.from,
208
+ lineTo: line.to,
209
+ lineText: update.state.doc.sliceString(line.from, line.to),
210
+ })
211
+ }))
212
+ return result
213
+ })
214
+
215
+ const validateSelectionPosition = (position: unknown, documentLength: number, coordinate: string): number =>
216
+ validateBoundedInteger('SCodeEditor', coordinate, position, { minimum: 0, maximum: documentLength })
217
+
218
+ const dispatchSelection = (position: number, source: SCodeEditorSelectionAppliedPayload['source']): void => {
219
+ const view = editorView.value
220
+ if (!view) throw new Error('SCodeEditor: editor не готов; дождитесь ready. / SCodeEditor: editor is not ready; wait for ready.')
221
+ const validated = validateSelectionPosition(position, view.state.doc.length, `${source} selection position`)
222
+ view.dispatch({ selection: EditorSelection.single(validated), scrollIntoView: true })
223
+ view.focus()
224
+ emit('selection-applied', { position: validated, source })
225
+ }
226
+
227
+ const onUpdate = (value: string): void => {
228
+ if (typeof value !== 'string') {
229
+ throw new TypeError('SCodeEditor: CodeMirror update payload должен быть строкой. / SCodeEditor: CodeMirror update payload must be a string.')
230
+ }
231
+ if (!isLocked.value) emit('update:modelValue', value)
232
+ }
233
+
234
+ const onReady = (payload: unknown): void => {
235
+ if (typeof payload !== 'object' || payload === null || !('view' in payload) || !((payload as { view?: unknown }).view instanceof EditorView)) {
236
+ throw new TypeError('SCodeEditor: CodeMirror ready payload должен содержать EditorView. / SCodeEditor: CodeMirror ready payload must contain EditorView.')
237
+ }
238
+ editorView.value = (payload as { view: EditorView }).view
239
+ const readyPayload: SCodeEditorReadyPayload = {
240
+ documentLength: editorView.value.state.doc.length,
241
+ viewport: validatedViewport.value,
242
+ variant: validatedVariant.value,
243
+ locked: isLocked.value,
244
+ }
245
+ emit('ready', readyPayload)
246
+ if (pendingControlledSelection.value !== null) {
247
+ const position = pendingControlledSelection.value
248
+ pendingControlledSelection.value = null
249
+ dispatchSelection(position, 'controlled')
250
+ }
251
+ }
252
+
253
+ watch(() => props.nextSelectionPos, (position) => {
254
+ if (position === null || position === undefined) return
255
+ if (!Number.isInteger(position)) {
256
+ throw new TypeError(`SCodeEditor: nextSelectionPos должен быть целым числом; получено ${String(position)}. / SCodeEditor: nextSelectionPos must be an integer; received ${String(position)}.`)
257
+ }
258
+ if (!editorView.value) {
259
+ pendingControlledSelection.value = position
260
+ return
261
+ }
262
+ dispatchSelection(position, 'controlled')
263
+ }, { immediate: true })
264
+
265
+ defineExpose({
266
+ applySelection: (position: number) => dispatchSelection(position, 'imperative'),
267
+ focus: () => {
268
+ if (!editorView.value) throw new Error('SCodeEditor: editor не готов; дождитесь ready. / SCodeEditor: editor is not ready; wait for ready.')
269
+ editorView.value.focus()
270
+ },
271
+ })
272
+ </script>
273
+
274
+ <style lang="postcss" src="./SCodeEditor.css" scoped></style>