@pgcorp/ui-kit 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +188 -9
  2. package/docs/getting-started.md +135 -0
  3. package/docs/public-api.md +53 -4
  4. package/docs/rich-document.md +216 -0
  5. package/docs/scroll-viewport.md +167 -0
  6. package/package.json +103 -50
  7. package/src/components/layout/SAppShell.vue +3 -1
  8. package/src/components/layout/SDockRegion.vue +5 -3
  9. package/src/components/layout/SWorkbenchPanelShell.vue +4 -2
  10. package/src/components/shared/_internal/SDialogSurface.css +5 -0
  11. package/src/components/shared/_internal/SDialogSurface.vue +3 -1
  12. package/src/components/shared/_internal/SToastItem.css +4 -0
  13. package/src/components/shared/_internal/SToastItem.vue +44 -8
  14. package/src/components/shared/_internal/STreeNode.vue +24 -11
  15. package/src/components/shared/complex/SKanbanLane.vue +4 -2
  16. package/src/components/shared/containers/SCloudChromeRow.vue +3 -2
  17. package/src/components/shared/containers/SDrawer.vue +5 -2
  18. package/src/components/shared/containers/SModal.vue +5 -2
  19. package/src/components/shared/containers/SPanel.vue +4 -2
  20. package/src/components/shared/containers/SPopover.vue +7 -3
  21. package/src/components/shared/containers/SUiKitLocaleProvider.vue +57 -0
  22. package/src/components/shared/controls/SButton.css +14 -10
  23. package/src/components/shared/controls/SCombobox.vue +8 -6
  24. package/src/components/shared/controls/SComboboxTrigger.css +7 -4
  25. package/src/components/shared/controls/SComboboxTrigger.vue +5 -2
  26. package/src/components/shared/controls/SContextToggleButton.vue +6 -4
  27. package/src/components/shared/controls/SDragHandle.vue +8 -4
  28. package/src/components/shared/controls/SDropdownMenu.vue +7 -4
  29. package/src/components/shared/controls/SInputText.css +5 -4
  30. package/src/components/shared/controls/SListbox.vue +4 -2
  31. package/src/components/shared/controls/SSelect.vue +4 -2
  32. package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +4 -2
  33. package/src/components/shared/data-display/SChart.vue +7 -5
  34. package/src/components/shared/data-display/SChip.vue +3 -1
  35. package/src/components/shared/data-display/SCodeBlock.css +4 -0
  36. package/src/components/shared/data-display/SCodeBlock.vue +42 -20
  37. package/src/components/shared/data-display/SCodeEditor.css +17 -0
  38. package/src/components/shared/data-display/SCodeEditor.vue +173 -8
  39. package/src/components/shared/data-display/SCodeSearchPanel.vue +48 -33
  40. package/src/components/shared/data-display/SCodeWorkspaceEditor.css +13 -0
  41. package/src/components/shared/data-display/SCodeWorkspaceEditor.vue +355 -0
  42. package/src/components/shared/data-display/SControlledToastViewport.css +89 -0
  43. package/src/components/shared/data-display/SControlledToastViewport.vue +205 -0
  44. package/src/components/shared/data-display/SCopyField.vue +12 -10
  45. package/src/components/shared/data-display/SDiffViewer.css +122 -0
  46. package/src/components/shared/data-display/SDiffViewer.vue +499 -0
  47. package/src/components/shared/data-display/SDocBlock.vue +99 -71
  48. package/src/components/shared/data-display/SExpandableText.vue +10 -8
  49. package/src/components/shared/data-display/SJsonTree.vue +4 -2
  50. package/src/components/shared/data-display/SLinkedSystemsList.vue +4 -2
  51. package/src/components/shared/data-display/SMessage.vue +6 -6
  52. package/src/components/shared/data-display/SRichDocument.css +8 -0
  53. package/src/components/shared/data-display/SRichDocument.vue +65 -0
  54. package/src/components/shared/data-display/SScrollViewport.css +53 -0
  55. package/src/components/shared/data-display/SScrollViewport.vue +470 -0
  56. package/src/components/shared/data-display/STable.vue +38 -19
  57. package/src/components/shared/data-display/SToastContainer.vue +22 -17
  58. package/src/components/shared/data-display/_internal/SCodeTokenLine.css +72 -0
  59. package/src/components/shared/data-display/_internal/SCodeTokenLine.vue +79 -0
  60. package/src/components/shared/data-display/_internal/SRichBlockNode.css +85 -0
  61. package/src/components/shared/data-display/_internal/SRichBlockNode.vue +282 -0
  62. package/src/components/shared/data-display/_internal/SRichInlineNode.css +6 -0
  63. package/src/components/shared/data-display/_internal/SRichInlineNode.vue +76 -0
  64. package/src/components/shared/data-display/_internal/SRichMath.css +42 -0
  65. package/src/components/shared/data-display/_internal/SRichMath.vue +102 -0
  66. package/src/components/shared/data-display/codeEditor.ts +200 -0
  67. package/src/components/shared/data-display/codeWorkspace.ts +233 -0
  68. package/src/components/shared/data-display/diff.ts +541 -0
  69. package/src/components/shared/data-display/richDocument.ts +876 -0
  70. package/src/components/shared/data-display/scrollViewport.ts +47 -0
  71. package/src/components/shared/data-display/toast.ts +194 -0
  72. package/src/components/shared/database/SDataGrid.vue +7 -4
  73. package/src/components/shared/database/SSqlEditor.vue +39 -39
  74. package/src/components/shared/feedback/SAsyncState.vue +3 -1
  75. package/src/components/shared/graph/SGraphViewport.vue +11 -9
  76. package/src/components/shared/navigation/SBreadcrumbs.vue +8 -5
  77. package/src/components/shared/navigation/SCatalogNavigator.vue +26 -20
  78. package/src/components/shared/navigation/STabList.vue +10 -6
  79. package/src/components/shared/navigation/STabs.vue +15 -11
  80. package/src/components/shared/navigation/SWizardSteps.vue +4 -2
  81. package/src/components/shared/persona/SPersonaProjectPicker.vue +31 -27
  82. package/src/components/shared/persona/SPersonaRunWorkbench.vue +39 -33
  83. package/src/components/shared/persona/_internal/SPersonaRunConsole.vue +8 -6
  84. package/src/components/shared/persona/_internal/labels.ts +66 -57
  85. package/src/components/shared/persona/presentation.ts +140 -110
  86. package/src/composables/useFloatingPosition.ts +39 -2
  87. package/src/composables/useUiKitLocale.ts +17 -0
  88. package/src/internal/codeHighlight.ts +110 -0
  89. package/src/internal/diffEngine.ts +464 -0
  90. package/src/internal/diffRuntime.ts +189 -0
  91. package/src/internal/feedbackPresentation.ts +33 -14
  92. package/src/internal/localeContext.ts +22 -0
  93. package/src/internal/ownedAttrs.ts +8 -0
  94. package/src/internal/runtimeContract.ts +11 -9
  95. package/src/internal/semanticSizing.ts +13 -0
  96. package/src/locales/catalog.ts +634 -0
  97. package/src/locales/locale.ts +472 -0
  98. package/src/locales/messages.generated.ts +1826 -0
  99. package/src/locales/messages.ts +150 -0
  100. package/src/stores/useNotifierStore.ts +61 -44
  101. package/src/styles/tokens.css +20 -2
  102. package/src/theme.ts +59 -32
  103. package/src/workers/diff.worker.ts +73 -0
  104. package/src/workers/diffProtocol.ts +155 -0
  105. package/src/components/shared/data-display/SToastContainer.css +0 -53
@@ -0,0 +1,205 @@
1
+ <template>
2
+ <div
3
+ v-bind="ownedAttrs.bindings()"
4
+ class="s-toast-container s-controlled-toast-viewport"
5
+ :data-position="validatedPosition"
6
+ role="region"
7
+ :aria-label="validatedAccessibleLabel"
8
+ >
9
+ <TransitionGroup name="notification-transition" tag="div" class="s-toast-transition-wrapper">
10
+ <SToastItem
11
+ v-for="notification in visibleNotifications"
12
+ :key="notification.id"
13
+ :notification="notification"
14
+ @dismiss="onDismiss"
15
+ @action="emit('action', $event)"
16
+ @lifetime-pause="pauseLifetime"
17
+ @lifetime-resume="resumeLifetime"
18
+ />
19
+ </TransitionGroup>
20
+ </div>
21
+ </template>
22
+
23
+ <script
24
+ setup
25
+ lang="ts"
26
+ generic="Id extends SToastId = string, ActionId extends SToastActionId = string"
27
+ >
28
+ import { computed, onBeforeUnmount, watch } from 'vue'
29
+
30
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs'
31
+ import {
32
+ validateBoundedInteger,
33
+ validateNonEmptyString,
34
+ } from '../../../internal/runtimeContract'
35
+ import SToastItem from '../_internal/SToastItem.vue'
36
+ import {
37
+ type SToastActionId,
38
+ type SToastActionIntent,
39
+ type SToastDismissIntent,
40
+ type SToastId,
41
+ type SToastLifetimeExpiredIntent,
42
+ type SToastLifetimeOwner,
43
+ type SToastPosition,
44
+ type SToastPresentation,
45
+ validateToastLifetimeOwner,
46
+ validateToastPosition,
47
+ validateToastPresentations,
48
+ } from './toast'
49
+
50
+ defineOptions({ inheritAttrs: false })
51
+
52
+ const props = withDefaults(defineProps<{
53
+ notifications: readonly SToastPresentation<Id, ActionId>[]
54
+ accessibleLabel: string
55
+ lifetimeOwner?: SToastLifetimeOwner
56
+ position?: SToastPosition
57
+ maxVisible?: number
58
+ }>(), {
59
+ lifetimeOwner: 'consumer',
60
+ position: 'top-end',
61
+ maxVisible: 5,
62
+ })
63
+
64
+ const emit = defineEmits<{
65
+ dismiss: [intent: SToastDismissIntent<Id>]
66
+ action: [intent: SToastActionIntent<Id, ActionId>]
67
+ 'lifetime-expired': [intent: SToastLifetimeExpiredIntent<Id>]
68
+ }>()
69
+
70
+ type PauseReason = 'pointer' | 'focus'
71
+
72
+ interface LifetimeState {
73
+ configuredDurationMs: number
74
+ remainingMs: number
75
+ startedAt: number
76
+ timer: ReturnType<typeof setTimeout> | null
77
+ pausedBy: Set<PauseReason>
78
+ expired: boolean
79
+ }
80
+
81
+ const ownedAttrs = useOwnedAttrs({
82
+ component: 'SControlledToastViewport',
83
+ owner: 'controlled toast viewport root',
84
+ })
85
+ const lifetimeStates = new Map<Id, LifetimeState>()
86
+ const validatedNotifications = computed(() =>
87
+ validateToastPresentations('SControlledToastViewport', props.notifications))
88
+ const validatedAccessibleLabel = computed(() =>
89
+ validateNonEmptyString('SControlledToastViewport', 'accessibleLabel', props.accessibleLabel))
90
+ const validatedPosition = computed(() => validateToastPosition(props.position))
91
+ const validatedLifetimeOwner = computed(() => validateToastLifetimeOwner(props.lifetimeOwner))
92
+ const validatedMaxVisible = computed(() => validateBoundedInteger(
93
+ 'SControlledToastViewport',
94
+ 'maxVisible',
95
+ props.maxVisible,
96
+ { minimum: 1, maximum: Number.MAX_SAFE_INTEGER },
97
+ ))
98
+ const visibleNotifications = computed(() =>
99
+ validatedNotifications.value.slice(0, validatedMaxVisible.value))
100
+
101
+ function clearTimer(state: LifetimeState): void {
102
+ if (state.timer === null) return
103
+ clearTimeout(state.timer)
104
+ state.timer = null
105
+ }
106
+
107
+ function removeLifetime(id: Id): void {
108
+ const state = lifetimeStates.get(id)
109
+ if (!state) return
110
+ clearTimer(state)
111
+ lifetimeStates.delete(id)
112
+ }
113
+
114
+ function clearLifetimes(): void {
115
+ for (const state of lifetimeStates.values()) clearTimer(state)
116
+ lifetimeStates.clear()
117
+ }
118
+
119
+ function startLifetime(id: Id, state: LifetimeState): void {
120
+ if (
121
+ typeof window === 'undefined'
122
+ || state.expired
123
+ || state.remainingMs <= 0
124
+ || state.pausedBy.size > 0
125
+ ) return
126
+ state.startedAt = Date.now()
127
+ state.timer = setTimeout(() => {
128
+ state.timer = null
129
+ state.remainingMs = 0
130
+ state.expired = true
131
+ emit('lifetime-expired', { id })
132
+ }, state.remainingMs)
133
+ }
134
+
135
+ function createLifetime(id: Id, durationMs: number): LifetimeState {
136
+ const state: LifetimeState = {
137
+ configuredDurationMs: durationMs,
138
+ remainingMs: durationMs,
139
+ startedAt: 0,
140
+ timer: null,
141
+ pausedBy: new Set(),
142
+ expired: false,
143
+ }
144
+ lifetimeStates.set(id, state)
145
+ startLifetime(id, state)
146
+ return state
147
+ }
148
+
149
+ function syncLifetimes(): void {
150
+ if (validatedLifetimeOwner.value !== 'viewport' || typeof window === 'undefined') {
151
+ clearLifetimes()
152
+ return
153
+ }
154
+ const visibleIds = new Set<Id>()
155
+ for (const notification of visibleNotifications.value) {
156
+ visibleIds.add(notification.id)
157
+ const durationMs = notification.durationMs ?? 0
158
+ const current = lifetimeStates.get(notification.id)
159
+ if (durationMs === 0) {
160
+ removeLifetime(notification.id)
161
+ continue
162
+ }
163
+ if (!current) {
164
+ createLifetime(notification.id, durationMs)
165
+ continue
166
+ }
167
+ if (current.configuredDurationMs !== durationMs) {
168
+ removeLifetime(notification.id)
169
+ createLifetime(notification.id, durationMs)
170
+ }
171
+ }
172
+ for (const id of lifetimeStates.keys()) {
173
+ if (!visibleIds.has(id)) removeLifetime(id)
174
+ }
175
+ }
176
+
177
+ function pauseLifetime(id: Id, reason: PauseReason): void {
178
+ const state = lifetimeStates.get(id)
179
+ if (!state || state.expired || state.pausedBy.has(reason)) return
180
+ state.pausedBy.add(reason)
181
+ if (state.timer === null) return
182
+ const elapsedMs = Math.max(0, Date.now() - state.startedAt)
183
+ state.remainingMs = Math.max(0, state.remainingMs - elapsedMs)
184
+ clearTimer(state)
185
+ }
186
+
187
+ function resumeLifetime(id: Id, reason: PauseReason): void {
188
+ const state = lifetimeStates.get(id)
189
+ if (!state || !state.pausedBy.delete(reason) || state.pausedBy.size > 0) return
190
+ startLifetime(id, state)
191
+ }
192
+
193
+ function onDismiss(id: Id): void {
194
+ emit('dismiss', { id, reason: 'close' })
195
+ }
196
+
197
+ watch(
198
+ [visibleNotifications, validatedLifetimeOwner],
199
+ syncLifetimes,
200
+ { immediate: true },
201
+ )
202
+ onBeforeUnmount(clearLifetimes)
203
+ </script>
204
+
205
+ <style lang="postcss" src="./SControlledToastViewport.css" scoped></style>
@@ -3,6 +3,7 @@ import { Copy } from '../../icons/sputnigUiIcons'
3
3
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
4
4
  import { useNotifier } from '../../../composables/useNotifier'
5
5
  import { useClipboard } from '../../../composables/useClipboard'
6
+ import { useUiKitLocale } from '../../../composables/useUiKitLocale'
6
7
  import SInputText from '../controls/SInputText.vue'
7
8
 
8
9
  defineOptions({ inheritAttrs: false })
@@ -24,13 +25,14 @@ const props = withDefaults(
24
25
  placeholder: '',
25
26
  disabled: false,
26
27
  monospace: false,
27
- successMessage: 'Значение скопировано',
28
- unavailableMessage: 'Буфер обмена недоступен в этом браузере.',
29
- errorMessage: 'Не удалось скопировать значение.',
28
+ successMessage: undefined,
29
+ unavailableMessage: undefined,
30
+ errorMessage: undefined,
30
31
  },
31
32
  )
32
33
 
33
34
  const notifier = useNotifier()
35
+ const { t } = useUiKitLocale()
34
36
  const { copyText } = useClipboard()
35
37
  const ownedAttrs = useOwnedAttrs({ component: 'SCopyField', owner: 'copy field input owner' })
36
38
 
@@ -41,17 +43,17 @@ async function copyValue() {
41
43
 
42
44
  const result = await copyText(props.value)
43
45
  if (result.status === 'copied') {
44
- notifier.success({ message: props.successMessage })
46
+ notifier.success({ message: props.successMessage ?? t('clipboard.valueCopied') })
45
47
  return
46
48
  }
47
49
 
48
50
  notifier.danger({
49
- title: 'Ошибка копирования',
51
+ title: t('clipboard.copyErrorTitle'),
50
52
  message: result.status === 'unavailable'
51
- ? props.unavailableMessage
53
+ ? props.unavailableMessage ?? t('clipboard.clipboardUnavailable')
52
54
  : result.status === 'empty'
53
- ? 'Нет значения для копирования.'
54
- : `${props.errorMessage} ${result.error.message}`,
55
+ ? t('clipboard.emptyValue')
56
+ : `${props.errorMessage ?? t('clipboard.valueCopyFailed')} ${result.error.message}`,
55
57
  })
56
58
  }
57
59
  </script>
@@ -66,8 +68,8 @@ async function copyValue() {
66
68
  :disabled="disabled"
67
69
  :monospace="monospace"
68
70
  :trailing-action="{
69
- label: 'Скопировать',
70
- title: 'Скопировать',
71
+ label: t('clipboard.copy'),
72
+ title: t('clipboard.copy'),
71
73
  icon: Copy,
72
74
  disabled: disabled || !value,
73
75
  }"
@@ -0,0 +1,122 @@
1
+ @reference "../../../styles/reference.css";
2
+
3
+ .s-diff-viewer {
4
+ @apply flex min-h-0 min-w-0 flex-col overflow-hidden rounded-md border border-surface-200 text-surface-900 dark:border-surface-700 dark:text-surface-100;
5
+ width: 100%;
6
+ background: var(--s-diff-surface);
7
+ }
8
+
9
+ .s-diff-viewer[data-viewport='content'] {
10
+ block-size: min(
11
+ calc((var(--s-diff-content-row-count) * var(--s-diff-row-block-size)) + 4.5rem),
12
+ var(--s-code-editor-viewport-content-max)
13
+ );
14
+ }
15
+
16
+ .s-diff-viewer[data-viewport='compact'] { block-size: var(--s-code-editor-viewport-compact); }
17
+ .s-diff-viewer[data-viewport='standard'] { block-size: var(--s-code-editor-viewport-standard); }
18
+ .s-diff-viewer[data-viewport='expanded'] { block-size: var(--s-code-editor-viewport-expanded); }
19
+ .s-diff-viewer[data-viewport='fill'] { block-size: 100%; }
20
+
21
+ .s-diff-viewer__header,
22
+ .s-diff-viewer__split-header {
23
+ @apply flex shrink-0 items-center justify-between gap-3 border-b border-surface-200 px-3 text-xs dark:border-surface-700;
24
+ min-block-size: 2.25rem;
25
+ }
26
+
27
+ .s-diff-viewer__sources { @apply flex min-w-0 items-center gap-2; }
28
+ .s-diff-viewer__source { @apply min-w-0 truncate font-semibold; }
29
+ .s-diff-viewer__direction,
30
+ .s-diff-viewer__summary { @apply shrink-0 text-surface-500 dark:text-surface-400; }
31
+
32
+ .s-diff-viewer__split-header {
33
+ display: grid;
34
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
35
+ padding-inline: 0;
36
+ }
37
+
38
+ .s-diff-viewer__split-header > span { @apply truncate px-3 font-semibold; }
39
+ .s-diff-viewer__split-header > span + span { @apply border-l border-surface-200 dark:border-surface-700; }
40
+
41
+ .s-diff-viewer__viewport {
42
+ @apply min-h-0 min-w-0 flex-1;
43
+ overflow: hidden;
44
+ }
45
+
46
+ .s-diff-viewer__error {
47
+ @apply flex min-h-0 flex-col items-start gap-3 p-3;
48
+ }
49
+
50
+ .s-diff-viewer__gap-row {
51
+ @apply flex min-w-0 items-center border-y border-surface-200 px-2 dark:border-surface-700;
52
+ min-block-size: var(--s-diff-row-block-size);
53
+ background: var(--s-diff-context-surface);
54
+ }
55
+
56
+ .s-diff-viewer__unified-row,
57
+ .s-diff-viewer__split-row,
58
+ .s-diff-viewer__split-cell {
59
+ min-width: 0;
60
+ min-block-size: var(--s-diff-row-block-size);
61
+ }
62
+
63
+ .s-diff-viewer__unified-row {
64
+ display: grid;
65
+ grid-template-columns: 2rem 3rem 3rem minmax(var(--s-diff-unified-code-inline-size), 1fr);
66
+ width: 100%;
67
+ min-width: calc(9rem + var(--s-diff-unified-code-inline-size));
68
+ }
69
+
70
+ .s-diff-viewer__split-row {
71
+ display: grid;
72
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
73
+ width: 100%;
74
+ min-width: 100%;
75
+ }
76
+
77
+ .s-diff-viewer__split-cell {
78
+ display: grid;
79
+ grid-template-columns: 3rem minmax(0, 1fr);
80
+ overflow: hidden;
81
+ }
82
+
83
+ .s-diff-viewer__split-cell + .s-diff-viewer__split-cell {
84
+ @apply border-l border-surface-200 dark:border-surface-700;
85
+ }
86
+
87
+ .s-diff-viewer__marker,
88
+ .s-diff-viewer__line-number {
89
+ @apply select-none text-right text-xs text-surface-500 dark:text-surface-400;
90
+ min-block-size: var(--s-diff-row-block-size);
91
+ padding-inline: 0.5rem;
92
+ background: var(--s-diff-gutter-surface);
93
+ font-family: var(--font-mono);
94
+ line-height: var(--s-diff-row-block-size);
95
+ }
96
+
97
+ .s-diff-viewer__marker { @apply text-center font-semibold; }
98
+
99
+ .s-diff-viewer__code-cell {
100
+ @apply relative overflow-hidden px-2;
101
+ min-width: 0;
102
+ min-block-size: var(--s-diff-row-block-size);
103
+ }
104
+
105
+ .s-diff-viewer__unified-row[data-kind='added'],
106
+ .s-diff-viewer__split-cell[data-kind='added'] {
107
+ background: var(--s-diff-added-surface);
108
+ box-shadow: inset 2px 0 var(--s-diff-added-accent);
109
+ }
110
+
111
+ .s-diff-viewer__unified-row[data-kind='removed'],
112
+ .s-diff-viewer__split-cell[data-kind='removed'] {
113
+ background: var(--s-diff-removed-surface);
114
+ box-shadow: inset 2px 0 var(--s-diff-removed-accent);
115
+ }
116
+
117
+ .s-diff-viewer__split-cell[data-kind='empty'] { background: var(--s-diff-gutter-surface); }
118
+
119
+ .s-diff-viewer__missing-break {
120
+ @apply absolute right-1 top-0 text-xs font-semibold text-surface-500 dark:text-surface-400;
121
+ line-height: var(--s-diff-row-block-size);
122
+ }