@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,363 @@
1
+ <template>
2
+ <SPanel
3
+ v-bind="ownedAttrs.bindings()"
4
+ :title="validatedTitle || undefined"
5
+ :title-id="validatedTitle ? generatedTitleId : undefined"
6
+ :footer-id="hasStatus ? generatedStatusId : undefined"
7
+ :heading-level="3"
8
+ :state="panelState"
9
+ no-content-padding
10
+ content-layout="block"
11
+ scroll-behavior="child"
12
+ surface-radius="md"
13
+ surface-overflow="clip"
14
+ >
15
+ <template v-if="$slots.actions" #actions><slot name="actions" /></template>
16
+
17
+ <SCodeEditor
18
+ ref="codeEditor"
19
+ :model-value="modelValue"
20
+ :language="sqlLanguage"
21
+ :capabilities="editorCapabilities"
22
+ variant="sql"
23
+ :viewport="validatedViewport"
24
+ :autofocus="validatedAutofocus"
25
+ :placeholder="validatedPlaceholder"
26
+ :read-only="validatedReadOnly"
27
+ :disabled="validatedDisabled"
28
+ :aria-label="resolvedAccessibleName.ariaLabel"
29
+ :aria-labelledby="resolvedAccessibleName.ariaLabelledby"
30
+ :aria-describedby="hasStatus ? generatedStatusId : undefined"
31
+ :invalid="statusHasError"
32
+ :next-selection-pos="nextSelectionPos"
33
+ @update:model-value="onUpdate"
34
+ @cursor-activity="emitCursorActivity"
35
+ @selection-applied="emit('selection-applied', $event)"
36
+ @ready="emit('ready', $event)"
37
+ />
38
+
39
+ <template v-if="hasStatus" #footer>
40
+ <slot name="status" :diagnostics="validatedDiagnostics" :error-message="validatedErrorMessage">
41
+ <SStatus
42
+ :label="statusLabel"
43
+ :tone="statusTone"
44
+ size="sm"
45
+ :live="validatedErrorMessage ? 'assertive' : 'polite'"
46
+ />
47
+ </slot>
48
+ </template>
49
+ </SPanel>
50
+ </template>
51
+
52
+ <script setup lang="ts">
53
+ import { computed, ref, useId, useSlots } from 'vue'
54
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs'
55
+ import { provideTrustedCodeEditorExtensions } from '../../../internal/codeEditorExtensions'
56
+ import { validateCodeEditorViewport } from '../../../internal/codeEditorContract'
57
+ import {
58
+ validateBoolean,
59
+ validateBoundedInteger,
60
+ validateDomId,
61
+ validateExactString,
62
+ validateNonEmptyString,
63
+ } from '../../../internal/runtimeContract'
64
+ import { autocompletion, type Completion, type CompletionContext } from '@codemirror/autocomplete'
65
+ import { linter, type Diagnostic as CodeMirrorDiagnostic } from '@codemirror/lint'
66
+ import { EditorState, type Extension } from '@codemirror/state'
67
+ import SCodeEditor, {
68
+ type SCodeEditorCapabilities,
69
+ type SCodeEditorCursorActivity,
70
+ type SCodeEditorLanguage,
71
+ type SCodeEditorReadyPayload,
72
+ type SCodeEditorSelectionAppliedPayload,
73
+ type SCodeEditorViewport,
74
+ } from '../data-display/SCodeEditor.vue'
75
+ import SPanel from '../containers/SPanel.vue'
76
+ import SStatus, { type StatusTone } from '../data-display/SStatus.vue'
77
+
78
+ defineOptions({ inheritAttrs: false })
79
+
80
+ interface SSqlEditorDiagnosticBase {
81
+ severity: 'error' | 'warning' | 'info'
82
+ message: string
83
+ }
84
+
85
+ /** Exact SQL diagnostic range через offsets либо line/column. / Exact SQL diagnostic range expressed either as offsets or line/column. */
86
+ export type SSqlEditorDiagnostic = SSqlEditorDiagnosticBase & (
87
+ | { from: number; to?: number; line?: never; column?: never }
88
+ | { line: number; column: number; from?: never; to?: never }
89
+ )
90
+
91
+ /** Typed completion item для private SQL extension adapter. / Typed completion item transformed by the private SQL extension adapter. */
92
+ export interface SSqlEditorCompletionItem {
93
+ label: string
94
+ apply?: string
95
+ detail?: string
96
+ kind?: 'keyword' | 'schema' | 'table' | 'view' | 'column' | 'function'
97
+ }
98
+
99
+ /** Public SQL-editor adapter contract; CodeMirror extensions остаются private. / Public SQL-editor adapter contract; CodeMirror extensions remain private. */
100
+ export interface Props {
101
+ modelValue: string
102
+ title?: string
103
+ placeholder?: string
104
+ readOnly?: boolean
105
+ disabled?: boolean
106
+ loading?: boolean
107
+ errorMessage?: string
108
+ diagnostics?: readonly SSqlEditorDiagnostic[]
109
+ completionItems?: readonly SSqlEditorCompletionItem[]
110
+ showLineNumbers?: boolean
111
+ autofocus?: boolean
112
+ viewport?: SCodeEditorViewport
113
+ tabSize?: number
114
+ ariaLabel?: string
115
+ ariaLabelledby?: string
116
+ nextSelectionPos?: number | null
117
+ }
118
+
119
+ const props = withDefaults(defineProps<Props>(), {
120
+ title: undefined,
121
+ placeholder: 'select * from public.table_name;',
122
+ readOnly: false,
123
+ disabled: false,
124
+ loading: false,
125
+ errorMessage: '',
126
+ diagnostics: () => [],
127
+ completionItems: () => [],
128
+ showLineNumbers: true,
129
+ autofocus: false,
130
+ viewport: 'standard',
131
+ tabSize: 2,
132
+ ariaLabel: undefined,
133
+ ariaLabelledby: undefined,
134
+ nextSelectionPos: null,
135
+ })
136
+
137
+ const ownedAttrs = useOwnedAttrs({ component: 'SSqlEditor', owner: 'SQL editor adapter root' })
138
+ const slots = useSlots()
139
+
140
+ const emit = defineEmits<{
141
+ (e: 'update:modelValue', value: string): void
142
+ (e: 'cursor-activity', payload: { pos: number; line: number; column: number }): void
143
+ (e: 'selection-applied', payload: SCodeEditorSelectionAppliedPayload): void
144
+ (e: 'ready', payload: SCodeEditorReadyPayload): void
145
+ }>()
146
+
147
+ const codeEditor = ref<InstanceType<typeof SCodeEditor> | null>(null)
148
+
149
+ const validatedReadOnly = computed(() => validateBoolean('SSqlEditor', 'readOnly', props.readOnly))
150
+ const validatedDisabled = computed(() => validateBoolean('SSqlEditor', 'disabled', props.disabled))
151
+ const validatedLoading = computed(() => validateBoolean('SSqlEditor', 'loading', props.loading))
152
+ const validatedAutofocus = computed(() => validateBoolean('SSqlEditor', 'autofocus', props.autofocus))
153
+ const validatedShowLineNumbers = computed(() => validateBoolean('SSqlEditor', 'showLineNumbers', props.showLineNumbers))
154
+ const validatedViewport = computed(() => validateCodeEditorViewport('SSqlEditor', props.viewport))
155
+ const validatedTitle = computed(() => props.title === undefined ? '' : validateNonEmptyString('SSqlEditor', 'title', props.title))
156
+ const validatedPlaceholder = computed(() => validateNonEmptyString('SSqlEditor', 'placeholder', props.placeholder))
157
+ const validatedErrorMessage = computed(() => {
158
+ if (typeof props.errorMessage !== 'string') {
159
+ throw new TypeError('SSqlEditor: errorMessage должен быть строкой. / SSqlEditor: errorMessage must be a string.')
160
+ }
161
+ return props.errorMessage === '' ? '' : validateNonEmptyString('SSqlEditor', 'errorMessage', props.errorMessage)
162
+ })
163
+ const resolvedAccessibleName = computed(() => {
164
+ if (validatedTitle.value !== '') {
165
+ if (props.ariaLabel !== undefined || props.ariaLabelledby !== undefined) {
166
+ throw new TypeError(
167
+ 'SSqlEditor: title уже задаёт accessible name; не передавайте ariaLabel/ariaLabelledby. '
168
+ + '/ SSqlEditor: title already provides the accessible name; do not provide ariaLabel/ariaLabelledby.',
169
+ )
170
+ }
171
+ return { ariaLabel: undefined, ariaLabelledby: generatedTitleId }
172
+ }
173
+ if (props.ariaLabel !== undefined && props.ariaLabelledby !== undefined) {
174
+ throw new TypeError('SSqlEditor: задайте только ariaLabel или ariaLabelledby. / SSqlEditor: provide only ariaLabel or ariaLabelledby.')
175
+ }
176
+ if (props.ariaLabelledby !== undefined) {
177
+ return { ariaLabel: undefined, ariaLabelledby: validateDomId('SSqlEditor', 'ariaLabelledby', props.ariaLabelledby) }
178
+ }
179
+ return {
180
+ ariaLabel: props.ariaLabel === undefined ? 'SQL editor' : validateNonEmptyString('SSqlEditor', 'ariaLabel', props.ariaLabel),
181
+ ariaLabelledby: undefined,
182
+ }
183
+ })
184
+ const generatedTitleId = `s-sql-editor-title-${useId()}`
185
+ const generatedStatusId = `s-sql-editor-status-${useId()}`
186
+ const panelState = computed(() => validatedLoading.value
187
+ ? { status: 'loading' as const, message: 'Загрузка SQL editor...' }
188
+ : { status: 'ready' as const })
189
+
190
+ const onUpdate = (value: string) => {
191
+ emit('update:modelValue', value)
192
+ }
193
+
194
+ const completionSource = (context: CompletionContext) => {
195
+ const word = context.matchBefore(/[A-Za-zА-Яа-я0-9_.$"]*/)
196
+ if (!word || (word.from === word.to && !context.explicit)) {
197
+ return null
198
+ }
199
+ const options: Completion[] = validatedCompletionItems.value.map((item) => ({
200
+ label: item.label,
201
+ apply: item.apply ?? item.label,
202
+ detail: item.detail,
203
+ type: completionType(item.kind),
204
+ }))
205
+ return {
206
+ from: word.from,
207
+ options,
208
+ }
209
+ }
210
+
211
+ const completionType = (kind: SSqlEditorCompletionItem['kind']) => {
212
+ switch (kind) {
213
+ case 'schema':
214
+ return 'namespace'
215
+ case 'table':
216
+ case 'view':
217
+ return 'class'
218
+ case 'column':
219
+ return 'property'
220
+ case 'function':
221
+ return 'function'
222
+ case 'keyword':
223
+ default:
224
+ return 'keyword'
225
+ }
226
+ }
227
+
228
+ const diagnosticsExtension = computed(() =>
229
+ linter((view) =>
230
+ validatedDiagnostics.value.map((diagnostic): CodeMirrorDiagnostic => {
231
+ const { from, to } = resolveDiagnosticRange(view.state.doc, diagnostic)
232
+ return {
233
+ from,
234
+ to,
235
+ severity: diagnosticSeverity(diagnostic.severity),
236
+ message: diagnostic.message,
237
+ }
238
+ }),
239
+ ),
240
+ )
241
+
242
+ const resolveDiagnosticRange = (
243
+ doc: EditorState['doc'],
244
+ diagnostic: SSqlEditorDiagnostic,
245
+ ) : { from: number; to: number } => {
246
+ if ('from' in diagnostic) {
247
+ const from = validateBoundedInteger('SSqlEditor', 'diagnostics[].from', diagnostic.from, { minimum: 0, maximum: doc.length })
248
+ const to = diagnostic.to === undefined
249
+ ? Math.min(from + 1, doc.length)
250
+ : validateBoundedInteger('SSqlEditor', 'diagnostics[].to', diagnostic.to, { minimum: from, maximum: doc.length })
251
+ return { from, to }
252
+ }
253
+ const lineNumber = validateBoundedInteger('SSqlEditor', 'diagnostics[].line', diagnostic.line, { minimum: 1, maximum: doc.lines })
254
+ const line = doc.line(lineNumber)
255
+ const column = validateBoundedInteger('SSqlEditor', 'diagnostics[].column', diagnostic.column, { minimum: 1, maximum: line.length + 1 })
256
+ const from = line.from + column - 1
257
+ return { from, to: Math.min(from + 1, doc.length) }
258
+ }
259
+
260
+ const diagnosticSeverity = (severity: SSqlEditorDiagnostic['severity']): CodeMirrorDiagnostic['severity'] => {
261
+ if (severity === 'info') return 'info'
262
+ if (severity === 'warning') return 'warning'
263
+ return 'error'
264
+ }
265
+
266
+ function assertPlainExactRecord(owner: string, value: unknown, allowed: readonly string[]): Record<string, unknown> {
267
+ if (typeof value !== 'object' || value === null || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype) {
268
+ throw new TypeError(`${owner}: значение должно быть plain object. / ${owner}: value must be a plain object.`)
269
+ }
270
+ const record = value as Record<string, unknown>
271
+ const unexpected = Object.keys(record).filter((key) => !allowed.includes(key))
272
+ if (unexpected.length > 0) throw new TypeError(`${owner}: неизвестные поля ${unexpected.join(', ')}. / ${owner}: unknown fields ${unexpected.join(', ')}.`)
273
+ return record
274
+ }
275
+
276
+ const validatedCompletionItems = computed<SSqlEditorCompletionItem[]>(() => {
277
+ if (!Array.isArray(props.completionItems)) throw new TypeError('SSqlEditor: completionItems должен быть массивом. / SSqlEditor: completionItems must be an array.')
278
+ return props.completionItems.map((candidate, index) => {
279
+ const record = assertPlainExactRecord(`SSqlEditor.completionItems[${index}]`, candidate, ['label', 'apply', 'detail', 'kind'])
280
+ return {
281
+ label: validateNonEmptyString('SSqlEditor', `completionItems[${index}].label`, record.label),
282
+ ...(record.apply === undefined ? {} : { apply: validateNonEmptyString('SSqlEditor', `completionItems[${index}].apply`, record.apply) }),
283
+ ...(record.detail === undefined ? {} : { detail: validateNonEmptyString('SSqlEditor', `completionItems[${index}].detail`, record.detail) }),
284
+ ...(record.kind === undefined ? {} : { kind: validateExactString('SSqlEditor', `completionItems[${index}].kind`, record.kind, ['keyword', 'schema', 'table', 'view', 'column', 'function'] as const) }),
285
+ }
286
+ })
287
+ })
288
+
289
+ const validatedDiagnostics = computed<SSqlEditorDiagnostic[]>(() => {
290
+ if (!Array.isArray(props.diagnostics)) throw new TypeError('SSqlEditor: diagnostics должен быть массивом. / SSqlEditor: diagnostics must be an array.')
291
+ return props.diagnostics.map((candidate, index) => {
292
+ const runtimeCandidate: unknown = candidate
293
+ const record = assertPlainExactRecord(`SSqlEditor.diagnostics[${index}]`, runtimeCandidate, ['severity', 'message', 'from', 'to', 'line', 'column'])
294
+ const hasOffset = Object.hasOwn(record, 'from')
295
+ const hasLine = Object.hasOwn(record, 'line') || Object.hasOwn(record, 'column')
296
+ if (hasOffset === hasLine) throw new TypeError(`SSqlEditor: diagnostics[${index}] должен задавать ровно один range: from/to или line/column. / SSqlEditor: diagnostics[${index}] must provide exactly one range: from/to or line/column.`)
297
+ const allowedRangeKeys = hasOffset
298
+ ? ['severity', 'message', 'from', 'to']
299
+ : ['severity', 'message', 'line', 'column']
300
+ const unexpectedRangeKeys = Object.keys(record).filter((key) => !allowedRangeKeys.includes(key))
301
+ if (unexpectedRangeKeys.length > 0) throw new TypeError(`SSqlEditor.diagnostics[${index}]: неизвестные поля ${unexpectedRangeKeys.join(', ')}. / SSqlEditor.diagnostics[${index}]: unknown fields ${unexpectedRangeKeys.join(', ')}.`)
302
+ const common = {
303
+ severity: validateExactString('SSqlEditor', `diagnostics[${index}].severity`, record.severity, ['error', 'warning', 'info'] as const),
304
+ message: validateNonEmptyString('SSqlEditor', `diagnostics[${index}].message`, record.message),
305
+ }
306
+ if (hasOffset) {
307
+ if (!Number.isInteger(record.from) || (record.to !== undefined && !Number.isInteger(record.to))) throw new TypeError(`SSqlEditor: diagnostics[${index}] offsets должны быть целыми числами. / SSqlEditor: diagnostics[${index}] offsets must be integers.`)
308
+ return { ...common, from: record.from as number, ...(record.to === undefined ? {} : { to: record.to as number }) }
309
+ }
310
+ if (!Number.isInteger(record.line) || !Number.isInteger(record.column)) throw new TypeError(`SSqlEditor: diagnostics[${index}] line/column должны быть целыми числами. / SSqlEditor: diagnostics[${index}] line/column must be integers.`)
311
+ return { ...common, line: record.line as number, column: record.column as number }
312
+ })
313
+ })
314
+ const statusLabel = computed(() => validatedErrorMessage.value || `${validatedDiagnostics.value.length} диагностик`)
315
+ const hasStatus = computed(() => (
316
+ validatedErrorMessage.value !== ''
317
+ || validatedDiagnostics.value.length > 0
318
+ || Boolean(slots.status)
319
+ ))
320
+ const statusHasError = computed(() => (
321
+ validatedErrorMessage.value !== ''
322
+ || validatedDiagnostics.value.some((diagnostic) => diagnostic.severity === 'error')
323
+ ))
324
+ const statusTone = computed<StatusTone>(() => {
325
+ if (statusHasError.value) return 'danger'
326
+ if (validatedDiagnostics.value.some((diagnostic) => diagnostic.severity === 'warning')) return 'warn'
327
+ return 'info'
328
+ })
329
+
330
+ const sqlLanguage: SCodeEditorLanguage = { mode: 'explicit', id: 'sql' }
331
+ const editorCapabilities = computed<SCodeEditorCapabilities>(() => ({
332
+ lineNumbers: validatedShowLineNumbers.value,
333
+ indentation: { tabSize: props.tabSize, keyBehavior: 'indent' },
334
+ cursorActivity: 'always',
335
+ }))
336
+
337
+ const extensions = computed<readonly Extension[]>(() => {
338
+ const result: Extension[] = []
339
+ result.push(autocompletion({ override: [completionSource] }))
340
+ result.push(diagnosticsExtension.value)
341
+ return result
342
+ })
343
+
344
+ provideTrustedCodeEditorExtensions('SSqlEditor', extensions)
345
+
346
+ const emitCursorActivity = (payload: SCodeEditorCursorActivity): void => {
347
+ emit('cursor-activity', { pos: payload.pos, line: payload.line, column: payload.column })
348
+ }
349
+
350
+ const applySelection = (pos: number): void => {
351
+ const editor = codeEditor.value
352
+ if (!editor) throw new Error('SSqlEditor: editor не готов; дождитесь ready. / SSqlEditor: editor is not ready; wait for ready.')
353
+ editor.applySelection(pos)
354
+ }
355
+
356
+ const focus = (): void => {
357
+ const editor = codeEditor.value
358
+ if (!editor) throw new Error('SSqlEditor: editor не готов; дождитесь ready. / SSqlEditor: editor is not ready; wait for ready.')
359
+ editor.focus()
360
+ }
361
+
362
+ defineExpose({ applySelection, focus })
363
+ </script>
@@ -0,0 +1,61 @@
1
+ import type {
2
+ ScrollableViewportMaxBlockSize,
3
+ TableColumnInlineSize,
4
+ TableColumnMinInlineSize,
5
+ } from '../../../internal/semanticSizing'
6
+ import type { STableAlignment } from '../data-display/table'
7
+
8
+ export type SDataGridColumnKey = string
9
+ export type SDataGridCellKind = 'text' | 'number' | 'boolean' | 'json' | 'datetime' | 'binary'
10
+ export type SDataGridRowState = 'clean' | 'inserted' | 'updated' | 'deleted'
11
+
12
+ export interface SDataGridColumn<Key extends SDataGridColumnKey = SDataGridColumnKey> {
13
+ key: Key
14
+ label: string
15
+ type?: SDataGridCellKind
16
+ width?: TableColumnInlineSize
17
+ minWidth?: TableColumnMinInlineSize
18
+ editable?: boolean
19
+ align?: STableAlignment
20
+ }
21
+
22
+ export interface SDataGridCell {
23
+ value: unknown
24
+ display: string
25
+ kind?: SDataGridCellKind
26
+ pending?: boolean
27
+ readonly?: boolean
28
+ }
29
+
30
+ export interface SDataGridRow<Key extends SDataGridColumnKey = SDataGridColumnKey> {
31
+ id: string
32
+ cells: Record<Key, SDataGridCell>
33
+ state?: SDataGridRowState
34
+ }
35
+
36
+ export interface SDataGridEditPayload<Key extends SDataGridColumnKey = SDataGridColumnKey> {
37
+ rowId: string
38
+ columnKey: Key
39
+ value: unknown
40
+ }
41
+
42
+ export type SDataGridCommitInput =
43
+ | { source: 'draft' }
44
+ | { source: 'value'; value: unknown }
45
+
46
+ export interface SDataGridProps<Key extends SDataGridColumnKey = SDataGridColumnKey> {
47
+ columns: readonly SDataGridColumn<Key>[]
48
+ rows: readonly SDataGridRow<Key>[]
49
+ title?: string
50
+ loading?: boolean
51
+ disabled?: boolean
52
+ readOnly?: boolean
53
+ errorMessage?: string
54
+ emptyMessage?: string
55
+ selectedRowId?: string
56
+ maxHeight?: ScrollableViewportMaxBlockSize
57
+ /** Accessible name без visible title. / Accessible name when there is no visible title. */
58
+ ariaLabel?: string
59
+ /** DOM id external visible title. / DOM id of an external visible title. */
60
+ ariaLabelledby?: string
61
+ }
@@ -0,0 +1,26 @@
1
+ @reference "../../../styles/reference.css";
2
+
3
+ .s-async-state,
4
+ .s-async-state__surface {
5
+ @apply min-w-0;
6
+ }
7
+
8
+ .s-async-state--block,
9
+ .s-async-state__surface {
10
+ @apply w-full;
11
+ }
12
+
13
+ .s-async-state--overlay {
14
+ @apply absolute inset-0 flex items-center justify-center bg-surface-0/90 p-4 backdrop-blur-sm dark:bg-surface-900/90;
15
+ z-index: var(--s-layer-sticky);
16
+ }
17
+
18
+ .s-async-state__cell {
19
+ @apply p-0;
20
+ }
21
+
22
+ .s-async-state__loading {
23
+ @apply flex min-h-24 min-w-0 w-full items-center justify-center whitespace-normal p-4 text-center text-sm text-surface-500 dark:text-surface-400;
24
+ overflow-wrap: anywhere;
25
+ gap: var(--s-space-control-gap);
26
+ }
@@ -0,0 +1,190 @@
1
+ <template>
2
+ <slot v-if="validatedState.status === 'ready' && assertReadyAttrs()" />
3
+
4
+ <tr
5
+ v-else-if="validatedPresentation === 'table-row'"
6
+ class="s-async-state s-async-state--table-row"
7
+ :data-state="validatedState.status"
8
+ >
9
+ <td :colspan="validatedColspan" class="s-async-state__cell">
10
+ <div
11
+ v-bind="ownedAttrs.bindings()"
12
+ class="s-async-state__surface"
13
+ :data-state="validatedState.status"
14
+ :role="surfaceLiveAttributes.role"
15
+ :aria-live="surfaceLiveAttributes.ariaLive"
16
+ :aria-atomic="surfaceLiveAttributes.ariaAtomic"
17
+ :aria-busy="validatedState.status === 'loading' || undefined"
18
+ >
19
+ <AsyncStateBody />
20
+ </div>
21
+ </td>
22
+ </tr>
23
+
24
+ <div
25
+ v-else
26
+ v-bind="ownedAttrs.bindings()"
27
+ class="s-async-state"
28
+ :class="`s-async-state--${validatedPresentation}`"
29
+ :data-state="validatedState.status"
30
+ :role="surfaceLiveAttributes.role"
31
+ :aria-live="surfaceLiveAttributes.ariaLive"
32
+ :aria-atomic="surfaceLiveAttributes.ariaAtomic"
33
+ :aria-busy="validatedState.status === 'loading' || undefined"
34
+ >
35
+ <AsyncStateBody />
36
+ </div>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ import { Fragment, computed, h, type Slots, type VNodeChild } from 'vue'
41
+
42
+ import { resolveAsyncFeedbackPresentation } from '../../../internal/feedbackPresentation'
43
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs'
44
+ import SEmptyState from '../data-display/SEmptyState.vue'
45
+ import SMessage from '../data-display/SMessage.vue'
46
+ import SProgressIndicator from '../data-display/SProgressIndicator.vue'
47
+
48
+ defineOptions({ inheritAttrs: false })
49
+
50
+ /**
51
+ * Единственное состояние асинхронной поверхности; error всегда содержит диагностику, endpoint допустим только для error.
52
+ * The sole async-surface state; error always carries diagnostics, and endpoint is valid only for error.
53
+ */
54
+ export type SAsyncStateContract =
55
+ | { status: 'ready' }
56
+ | { status: 'loading'; message?: string }
57
+ | { status: 'empty'; message?: string }
58
+ | { status: 'error'; message: string; endpoint?: string }
59
+ export type SAsyncStatePresentation = 'block' | 'table-row' | 'overlay'
60
+
61
+ interface Props {
62
+ state: SAsyncStateContract
63
+ presentation?: SAsyncStatePresentation
64
+ /** Обязателен и допустим только для table-row. / Required and valid only for table-row. */
65
+ colspan?: number
66
+ }
67
+
68
+ const props = withDefaults(defineProps<Props>(), {
69
+ presentation: 'block',
70
+ colspan: undefined,
71
+ })
72
+ const slots = defineSlots<{
73
+ default?: () => VNodeChild
74
+ loading?: (props: { message: string; state: Extract<SAsyncStateContract, { status: 'loading' }> }) => VNodeChild
75
+ empty?: (props: { message: string; state: Extract<SAsyncStateContract, { status: 'empty' }> }) => VNodeChild
76
+ error?: (props: { message: string; state: Extract<SAsyncStateContract, { status: 'error' }> }) => VNodeChild
77
+ }>()
78
+
79
+ const ownedAttrs = useOwnedAttrs({
80
+ component: 'SAsyncState',
81
+ owner: 'rendered async-state surface',
82
+ })
83
+
84
+ const stateAllowedKeys: Readonly<Record<SAsyncStateContract['status'], readonly string[]>> = {
85
+ ready: ['status'],
86
+ loading: ['status', 'message'],
87
+ empty: ['status', 'message'],
88
+ error: ['status', 'message', 'endpoint'],
89
+ }
90
+
91
+ const assertNonEmptyOptionalString = (record: Record<string, unknown>, key: 'message' | 'endpoint') => {
92
+ if (!(key in record)) return
93
+ if (typeof record[key] !== 'string' || record[key].trim().length === 0) {
94
+ throw new TypeError(`SAsyncState: state.${key} должен быть непустой строкой при наличии.`)
95
+ }
96
+ }
97
+
98
+ const validateState = (state: unknown): SAsyncStateContract => {
99
+ if (typeof state !== 'object' || state === null || Array.isArray(state)) {
100
+ throw new TypeError('SAsyncState: state должен быть объектом discriminated union.')
101
+ }
102
+ const record = state as Record<string, unknown>
103
+ const status = record.status
104
+ if (!['ready', 'loading', 'empty', 'error'].includes(String(status))) {
105
+ throw new TypeError(`SAsyncState: неизвестный state.status "${String(status)}".`)
106
+ }
107
+ const typedStatus = status as SAsyncStateContract['status']
108
+ const unexpected = Object.keys(record).filter((key) => !stateAllowedKeys[typedStatus].includes(key))
109
+ if (unexpected.length > 0) {
110
+ throw new TypeError(`SAsyncState: state.status="${typedStatus}" не допускает поля ${unexpected.join(', ')}.`)
111
+ }
112
+ if (typedStatus === 'error') {
113
+ if (typeof record.message !== 'string' || record.message.trim().length === 0) {
114
+ throw new TypeError('SAsyncState: state.message должен быть непустой строкой для status="error".')
115
+ }
116
+ assertNonEmptyOptionalString(record, 'endpoint')
117
+ } else if (typedStatus === 'loading' || typedStatus === 'empty') {
118
+ assertNonEmptyOptionalString(record, 'message')
119
+ }
120
+ return state as SAsyncStateContract
121
+ }
122
+
123
+ const validatedState = computed(() => validateState(props.state))
124
+ const validatedPresentation = computed<SAsyncStatePresentation>(() => {
125
+ const colspan = props.colspan
126
+ if (!['block', 'table-row', 'overlay'].includes(String(props.presentation))) {
127
+ throw new TypeError(`SAsyncState: неизвестный presentation "${String(props.presentation)}".`)
128
+ }
129
+ if (props.presentation !== 'table-row' && colspan !== undefined) {
130
+ throw new TypeError('SAsyncState: colspan допустим только для presentation="table-row".')
131
+ }
132
+ if (props.presentation === 'table-row' && colspan === undefined) {
133
+ throw new TypeError('SAsyncState: presentation="table-row" требует colspan.')
134
+ }
135
+ if (props.presentation === 'table-row'
136
+ && (typeof colspan !== 'number' || !Number.isSafeInteger(colspan) || colspan <= 0)) {
137
+ throw new TypeError('SAsyncState: colspan должен быть целым числом больше 0.')
138
+ }
139
+ return props.presentation
140
+ })
141
+ const validatedColspan = computed(() => {
142
+ return validatedPresentation.value === 'table-row' ? props.colspan : undefined
143
+ })
144
+
145
+ const assertReadyAttrs = (): boolean => {
146
+ ownedAttrs.reject('ready state отдаёт consumer content без UI-kit root / ready state renders consumer content without a UI-kit root')
147
+ return true
148
+ }
149
+ const statePresentation = computed(() => {
150
+ const state = validatedState.value
151
+ return state.status === 'ready' ? undefined : resolveAsyncFeedbackPresentation(state.status)
152
+ })
153
+ const resolvedMessage = computed(() => {
154
+ const state = validatedState.value
155
+ if (state.status === 'ready') return ''
156
+ return state.message ?? statePresentation.value?.fallbackMessage ?? ''
157
+ })
158
+ const customStateSlot = computed(() => {
159
+ const state = validatedState.value
160
+ return state.status === 'ready' ? undefined : slots[state.status] as Slots[string] | undefined
161
+ })
162
+ const surfaceLiveAttributes = computed(() => {
163
+ const state = validatedState.value
164
+ if (state.status === 'ready') return {}
165
+ return state.status === 'loading' || customStateSlot.value
166
+ ? statePresentation.value?.live ?? {}
167
+ : {}
168
+ })
169
+
170
+ const AsyncStateBody = () => {
171
+ const state = validatedState.value
172
+ if (state.status === 'ready') return null
173
+ const stateSlot = customStateSlot.value
174
+ if (stateSlot) {
175
+ return h(Fragment, stateSlot({ message: resolvedMessage.value, state }))
176
+ }
177
+ if (state.status === 'error') {
178
+ return h(SMessage, { severity: 'danger', live: 'auto', size: 'sm' }, () => resolvedMessage.value)
179
+ }
180
+ if (state.status === 'empty') {
181
+ return h(SEmptyState, { title: resolvedMessage.value, size: 'sm' })
182
+ }
183
+ return h('div', { class: 's-async-state__loading' }, [
184
+ h(SProgressIndicator, { accessibility: { mode: 'decorative' }, size: 'sm', tone: 'info' }),
185
+ h('span', resolvedMessage.value),
186
+ ])
187
+ }
188
+ </script>
189
+
190
+ <style lang="postcss" src="./SAsyncState.css" scoped></style>
@@ -0,0 +1 @@
1
+ export * from './_internal/graphViewportContract'