@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,72 @@
1
+ @reference "../../../../styles/reference.css";
2
+
3
+ .s-code-token-line {
4
+ display: block;
5
+ min-width: max-content;
6
+ color: var(--color-text);
7
+ font-family: var(--font-mono);
8
+ font-size: 0.8125rem;
9
+ line-height: var(--s-diff-row-block-size);
10
+ tab-size: 2;
11
+ white-space: pre;
12
+ }
13
+
14
+ .s-code-token-line[data-wrap='true'] {
15
+ min-width: 0;
16
+ overflow-wrap: anywhere;
17
+ white-space: pre-wrap;
18
+ }
19
+
20
+ .s-code-token-line[data-variant='code-block'] {
21
+ font-size: 0.875rem;
22
+ line-height: 1.5;
23
+ }
24
+
25
+ .hljs-comment,
26
+ .hljs-quote {
27
+ color: var(--s-code-token-comment);
28
+ font-style: italic;
29
+ }
30
+
31
+ .hljs-keyword,
32
+ .hljs-selector-tag,
33
+ .hljs-subst,
34
+ .hljs-built_in,
35
+ .hljs-type {
36
+ color: var(--s-code-token-keyword);
37
+ }
38
+
39
+ .hljs-title,
40
+ .hljs-section,
41
+ .hljs-selector-id,
42
+ .hljs-selector-class {
43
+ color: var(--s-code-token-title);
44
+ font-weight: 600;
45
+ }
46
+
47
+ .hljs-number,
48
+ .hljs-literal,
49
+ .hljs-variable,
50
+ .hljs-template-variable,
51
+ .hljs-attribute {
52
+ color: var(--s-code-token-literal);
53
+ }
54
+
55
+ .hljs-string,
56
+ .hljs-regexp,
57
+ .hljs-symbol,
58
+ .hljs-bullet,
59
+ .hljs-addition {
60
+ color: var(--s-code-token-string);
61
+ }
62
+
63
+ .hljs-meta,
64
+ .hljs-link,
65
+ .hljs-name,
66
+ .hljs-tag,
67
+ .hljs-deletion {
68
+ color: var(--s-code-token-meta);
69
+ }
70
+
71
+ .hljs-emphasis { font-style: italic; }
72
+ .hljs-strong { font-weight: 700; }
@@ -0,0 +1,79 @@
1
+ <template>
2
+ <code
3
+ class="s-code-token-line"
4
+ :data-highlight-state="highlightState"
5
+ :data-wrap="validatedWrap"
6
+ :data-variant="validatedVariant"
7
+ ><TokenContent /></code>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import { Fragment, computed, h, onBeforeUnmount, ref, watch, type VNodeChild } from 'vue'
12
+
13
+ import { validateBoolean } from '../../../../internal/runtimeContract'
14
+ import type { SDiffLanguage } from '../diff'
15
+
16
+ defineOptions({ inheritAttrs: false })
17
+
18
+ interface Props {
19
+ text: string
20
+ language?: SDiffLanguage
21
+ wrap?: boolean
22
+ variant?: 'diff' | 'code-block'
23
+ }
24
+
25
+ type RenderToken = string | {
26
+ readonly className: string | null
27
+ readonly children: readonly RenderToken[]
28
+ }
29
+
30
+ const props = withDefaults(defineProps<Props>(), { language: undefined, wrap: false, variant: 'diff' })
31
+ const tokens = ref<readonly RenderToken[]>([props.text])
32
+ const highlightState = ref<'plain' | 'loading' | 'highlighted'>('plain')
33
+ const validatedWrap = computed(() => validateBoolean('SCodeTokenLine', 'wrap', props.wrap))
34
+ const validatedVariant = computed(() => {
35
+ if (props.variant === 'diff' || props.variant === 'code-block') return props.variant
36
+ throw new TypeError('SCodeTokenLine: variant must be diff or code-block.')
37
+ })
38
+ let generation = 0
39
+
40
+ function renderToken(token: RenderToken, index: number): VNodeChild {
41
+ if (typeof token === 'string') return token
42
+ return h('span', { key: index, class: token.className }, token.children.map(renderToken))
43
+ }
44
+
45
+ const TokenContent = () => h(Fragment, tokens.value.map(renderToken))
46
+
47
+ watch(
48
+ () => [props.text, props.language] as const,
49
+ async ([text, language]) => {
50
+ const currentGeneration = generation += 1
51
+ if (language === undefined) {
52
+ tokens.value = [text]
53
+ highlightState.value = 'plain'
54
+ return
55
+ }
56
+ highlightState.value = 'loading'
57
+ const highlighter = await import('../../../../internal/codeHighlight')
58
+ const toRenderToken = (token: Awaited<ReturnType<typeof highlighter.highlightCodeToTokens>>[number]): RenderToken => (
59
+ typeof token === 'string'
60
+ ? token
61
+ : Object.freeze({
62
+ className: token.scope === null ? null : highlighter.highlightScopeClass(token.scope),
63
+ children: Object.freeze(token.children.map(toRenderToken)),
64
+ })
65
+ )
66
+ const nextTokens = highlighter.highlightCodeToTokens(text, language).map(toRenderToken)
67
+ if (currentGeneration !== generation) return
68
+ tokens.value = Object.freeze(nextTokens)
69
+ highlightState.value = 'highlighted'
70
+ },
71
+ { immediate: true },
72
+ )
73
+
74
+ onBeforeUnmount(() => {
75
+ generation += 1
76
+ })
77
+ </script>
78
+
79
+ <style lang="postcss" src="./SCodeTokenLine.css" scoped></style>
@@ -0,0 +1,85 @@
1
+ @reference "../../../../styles/reference.css";
2
+
3
+ .s-rich-document__paragraph {
4
+ min-width: 0;
5
+ margin: 0;
6
+ }
7
+
8
+ .s-rich-document__heading {
9
+ @apply font-semibold text-surface-950 dark:text-surface-50;
10
+ min-width: 0;
11
+ margin: 0;
12
+ line-height: 1.25;
13
+ }
14
+
15
+ h1.s-rich-document__heading { @apply text-3xl; }
16
+ h2.s-rich-document__heading { @apply text-2xl; }
17
+ h3.s-rich-document__heading { @apply text-xl; }
18
+ h4.s-rich-document__heading { @apply text-lg; }
19
+ h5.s-rich-document__heading,
20
+ h6.s-rich-document__heading { @apply text-base; }
21
+
22
+ .s-rich-document__list {
23
+ display: grid;
24
+ gap: var(--s-space-content-gap);
25
+ margin: 0;
26
+ padding-inline-start: 1.5rem;
27
+ }
28
+
29
+ ul.s-rich-document__list { list-style: disc; }
30
+ ol.s-rich-document__list { list-style: decimal; }
31
+
32
+ .s-rich-document__quote {
33
+ @apply border-l-4 border-primary-300 text-surface-600 dark:border-primary-700 dark:text-surface-300;
34
+ display: grid;
35
+ gap: var(--s-space-content-gap);
36
+ margin: 0;
37
+ padding-inline-start: var(--s-space-section-gap);
38
+ }
39
+
40
+ .s-rich-document__divider {
41
+ @apply border-0 border-t border-surface-200 dark:border-surface-700;
42
+ width: 100%;
43
+ margin: 0;
44
+ }
45
+
46
+ .s-rich-document__figure {
47
+ display: grid;
48
+ gap: var(--s-space-content-gap);
49
+ min-width: 0;
50
+ margin: 0;
51
+ }
52
+
53
+ .s-rich-document__image {
54
+ @apply rounded-lg border border-surface-200 bg-surface-50 dark:border-surface-700 dark:bg-surface-900;
55
+ display: block;
56
+ width: auto;
57
+ max-width: 100%;
58
+ height: auto;
59
+ margin-inline: auto;
60
+ }
61
+
62
+ .s-rich-document__caption,
63
+ .s-rich-document__resource-description {
64
+ @apply text-sm text-surface-500 dark:text-surface-400;
65
+ }
66
+
67
+ .s-rich-document__caption {
68
+ text-align: center;
69
+ }
70
+
71
+ .s-rich-document__resource {
72
+ @apply flex min-w-0 flex-wrap items-center;
73
+ gap: var(--s-space-content-gap);
74
+ }
75
+
76
+ .s-rich-document__callout-content {
77
+ display: grid;
78
+ gap: var(--s-space-content-gap);
79
+ min-width: 0;
80
+ }
81
+
82
+ .s-rich-document__actions {
83
+ @apply flex min-w-0 flex-wrap items-center;
84
+ gap: var(--s-space-content-gap);
85
+ }
@@ -0,0 +1,282 @@
1
+ <template>
2
+ <p v-if="node.type === 'paragraph'" class="s-rich-document__paragraph">
3
+ <SRichInlineNode
4
+ v-for="(child, index) in node.children"
5
+ :key="index"
6
+ :node="child"
7
+ :document-id="documentId"
8
+ :path="`${path}.children[${index}]`"
9
+ @link-activate="emit('link-activate', $event)"
10
+ />
11
+ </p>
12
+ <component :is="`h${node.level}`" v-else-if="node.type === 'heading'" class="s-rich-document__heading">
13
+ <SRichInlineNode
14
+ v-for="(child, index) in node.children"
15
+ :key="index"
16
+ :node="child"
17
+ :document-id="documentId"
18
+ :path="`${path}.children[${index}]`"
19
+ @link-activate="emit('link-activate', $event)"
20
+ />
21
+ </component>
22
+ <component
23
+ :is="node.ordered ? 'ol' : 'ul'"
24
+ v-else-if="node.type === 'list'"
25
+ class="s-rich-document__list"
26
+ :start="node.ordered ? node.start : undefined"
27
+ >
28
+ <li v-for="(item, itemIndex) in node.children" :key="itemIndex">
29
+ <SRichBlockNode
30
+ v-for="(child, childIndex) in item.children"
31
+ :key="childIndex"
32
+ :node="child"
33
+ :resources="resources"
34
+ :document-id="documentId"
35
+ :path="`${path}.children[${itemIndex}].children[${childIndex}]`"
36
+ @link-activate="emit('link-activate', $event)"
37
+ @resource-activate="emit('resource-activate', $event)"
38
+ @action="emit('action', $event)"
39
+ />
40
+ </li>
41
+ </component>
42
+ <blockquote v-else-if="node.type === 'quote'" class="s-rich-document__quote">
43
+ <SRichBlockNode
44
+ v-for="(child, index) in node.children"
45
+ :key="index"
46
+ :node="child"
47
+ :resources="resources"
48
+ :document-id="documentId"
49
+ :path="`${path}.children[${index}]`"
50
+ @link-activate="emit('link-activate', $event)"
51
+ @resource-activate="emit('resource-activate', $event)"
52
+ @action="emit('action', $event)"
53
+ />
54
+ </blockquote>
55
+ <hr v-else-if="node.type === 'divider'" class="s-rich-document__divider" />
56
+ <figure v-else-if="node.type === 'image'" class="s-rich-document__figure">
57
+ <img
58
+ class="s-rich-document__image"
59
+ :src="imageResource(node.resourceId).url"
60
+ :alt="imageResource(node.resourceId).alt"
61
+ :title="imageResource(node.resourceId).title"
62
+ loading="lazy"
63
+ decoding="async"
64
+ />
65
+ <figcaption v-if="node.caption" class="s-rich-document__caption">
66
+ <SRichInlineNode
67
+ v-for="(child, index) in node.caption"
68
+ :key="index"
69
+ :node="child"
70
+ :document-id="documentId"
71
+ :path="`${path}.caption[${index}]`"
72
+ @link-activate="emit('link-activate', $event)"
73
+ />
74
+ </figcaption>
75
+ </figure>
76
+ <div v-else-if="node.type === 'resource'" class="s-rich-document__resource">
77
+ <SButton
78
+ :label="node.label"
79
+ severity="secondary"
80
+ appearance="soft"
81
+ size="sm"
82
+ @click="activateResource(node.resourceId)"
83
+ />
84
+ <span v-if="node.description" class="s-rich-document__resource-description">{{ node.description }}</span>
85
+ </div>
86
+ <AsyncSCodeBlock
87
+ v-else-if="node.type === 'codeBlock'"
88
+ :code="node.code"
89
+ :language="{ mode: 'explicit', id: node.language ?? 'text' }"
90
+ presentation="highlighted"
91
+ :show-header="node.language !== undefined || node.label !== undefined"
92
+ :show-line-numbers="node.showLineNumbers ?? true"
93
+ read-only
94
+ viewport="document"
95
+ :aria-label="node.label ?? t('editor.codeBlock')"
96
+ />
97
+ <AsyncSRichMath
98
+ v-else-if="node.type === 'mathBlock'"
99
+ :source="node.source"
100
+ :label="node.label"
101
+ display="block"
102
+ />
103
+ <STable
104
+ v-else-if="node.type === 'table'"
105
+ :columns="tableColumns"
106
+ :data="node.rows"
107
+ :get-row-key="rowKey"
108
+ :aria-label="node.label"
109
+ surface="plain"
110
+ density="compact"
111
+ min-width="content"
112
+ >
113
+ <template #cell="{ row, column }">
114
+ <SRichInlineNode
115
+ v-for="(child, index) in tableCell(row, column.field).children"
116
+ :key="index"
117
+ :node="child"
118
+ :document-id="documentId"
119
+ :path="`${path}.rows[${tableRowIndex(row)}].cells[${tableColumnIndex(column.field)}].children[${index}]`"
120
+ @link-activate="emit('link-activate', $event)"
121
+ />
122
+ </template>
123
+ </STable>
124
+ <SPanel
125
+ v-else-if="node.type === 'callout' && node.tone === 'neutral'"
126
+ :title="node.title"
127
+ :heading-level="3"
128
+ surface-tone="subtle"
129
+ content-layout="stack"
130
+ content-gap="content"
131
+ >
132
+ <SRichBlockNode
133
+ v-for="(child, index) in node.children"
134
+ :key="index"
135
+ :node="child"
136
+ :resources="resources"
137
+ :document-id="documentId"
138
+ :path="`${path}.children[${index}]`"
139
+ @link-activate="emit('link-activate', $event)"
140
+ @resource-activate="emit('resource-activate', $event)"
141
+ @action="emit('action', $event)"
142
+ />
143
+ </SPanel>
144
+ <SMessage
145
+ v-else-if="node.type === 'callout'"
146
+ :severity="calloutSeverity(node.tone)"
147
+ :title="node.title"
148
+ live="off"
149
+ >
150
+ <div class="s-rich-document__callout-content">
151
+ <SRichBlockNode
152
+ v-for="(child, index) in node.children"
153
+ :key="index"
154
+ :node="child"
155
+ :resources="resources"
156
+ :document-id="documentId"
157
+ :path="`${path}.children[${index}]`"
158
+ @link-activate="emit('link-activate', $event)"
159
+ @resource-activate="emit('resource-activate', $event)"
160
+ @action="emit('action', $event)"
161
+ />
162
+ </div>
163
+ </SMessage>
164
+ <div v-else-if="node.type === 'actionGroup'" class="s-rich-document__actions" role="group" :aria-label="node.label">
165
+ <SButton
166
+ v-for="action in node.actions"
167
+ :key="action.id"
168
+ :label="action.label"
169
+ :severity="actionSeverity(action.appearance)"
170
+ :appearance="action.appearance === 'primary' ? 'solid' : 'soft'"
171
+ :disabled="action.disabled"
172
+ size="sm"
173
+ @click="activateAction(action.id)"
174
+ />
175
+ </div>
176
+ </template>
177
+
178
+ <script setup lang="ts">
179
+ import { computed, defineAsyncComponent } from 'vue'
180
+
181
+ import { useUiKitLocale } from '../../../../composables/useUiKitLocale'
182
+ import SPanel from '../../containers/SPanel.vue'
183
+ import SButton, { type SButtonSeverity } from '../../controls/SButton.vue'
184
+ import SMessage, { type Severity as SMessageSeverity } from '../SMessage.vue'
185
+ import STable from '../STable.vue'
186
+ import type { STableColumn } from '../table'
187
+ import type {
188
+ SRichActionAppearance,
189
+ SRichBlockNode,
190
+ SRichCalloutTone,
191
+ SRichDocumentActionIntent,
192
+ SRichDocumentImageResource,
193
+ SRichDocumentLinkActivate,
194
+ SRichDocumentResource,
195
+ SRichDocumentResourceActivate,
196
+ SRichTableCell,
197
+ SRichTableRow,
198
+ } from '../richDocument'
199
+ import SRichInlineNode from './SRichInlineNode.vue'
200
+
201
+ defineOptions({ inheritAttrs: false })
202
+
203
+ const props = defineProps<{
204
+ node: SRichBlockNode
205
+ resources: readonly SRichDocumentResource[]
206
+ documentId: string
207
+ path: string
208
+ }>()
209
+
210
+ const emit = defineEmits<{
211
+ 'link-activate': [payload: SRichDocumentLinkActivate]
212
+ 'resource-activate': [payload: SRichDocumentResourceActivate]
213
+ action: [payload: SRichDocumentActionIntent]
214
+ }>()
215
+
216
+ const { t } = useUiKitLocale()
217
+ const AsyncSCodeBlock = defineAsyncComponent(() => import('../SCodeBlock.vue'))
218
+ const AsyncSRichMath = defineAsyncComponent(() => import('./SRichMath.vue'))
219
+ const tableColumns = computed<readonly STableColumn<SRichTableRow, string>[]>(() => {
220
+ if (props.node.type !== 'table') return []
221
+ return Object.freeze(props.node.columns.map((column, index) => Object.freeze({
222
+ field: column.id,
223
+ header: column.header,
224
+ headerPresentation: column.headerPresentation,
225
+ align: column.align,
226
+ content: 'value' as const,
227
+ value: (row: SRichTableRow) => row.cells[index],
228
+ })))
229
+ })
230
+
231
+ function rowKey(row: SRichTableRow): string {
232
+ return row.id
233
+ }
234
+
235
+ function tableColumnIndex(field: string): number {
236
+ if (props.node.type !== 'table') throw new Error('SRichBlockNode: table column outside table node.')
237
+ const index = props.node.columns.findIndex((column) => column.id === field)
238
+ if (index < 0) throw new Error(`SRichBlockNode: unknown table column ${JSON.stringify(field)}.`)
239
+ return index
240
+ }
241
+
242
+ function tableRowIndex(row: SRichTableRow): number {
243
+ if (props.node.type !== 'table') throw new Error('SRichBlockNode: table row outside table node.')
244
+ const index = props.node.rows.findIndex((candidate) => candidate.id === row.id)
245
+ if (index < 0) throw new Error(`SRichBlockNode: unknown table row ${JSON.stringify(row.id)}.`)
246
+ return index
247
+ }
248
+
249
+ function tableCell(row: SRichTableRow, field: string): SRichTableCell {
250
+ const cell = row.cells[tableColumnIndex(field)]
251
+ if (!cell) throw new Error(`SRichBlockNode: missing table cell for ${JSON.stringify(field)}.`)
252
+ return cell
253
+ }
254
+
255
+ function imageResource(id: string): SRichDocumentImageResource {
256
+ const resource = props.resources.find((candidate) => candidate.id === id)
257
+ if (resource?.kind !== 'image') throw new Error(`SRichBlockNode: image resource ${JSON.stringify(id)} is unavailable.`)
258
+ return resource
259
+ }
260
+
261
+ function activateResource(resourceId: string): void {
262
+ emit('resource-activate', Object.freeze({ documentId: props.documentId, path: props.path, resourceId }))
263
+ }
264
+
265
+ function activateAction(actionId: string): void {
266
+ emit('action', Object.freeze({ documentId: props.documentId, path: props.path, actionId }))
267
+ }
268
+
269
+ function calloutSeverity(tone: SRichCalloutTone): SMessageSeverity {
270
+ if (tone === 'information' || tone === 'neutral') return 'info'
271
+ if (tone === 'warning') return 'warn'
272
+ return tone
273
+ }
274
+
275
+ function actionSeverity(appearance: SRichActionAppearance | undefined): SButtonSeverity {
276
+ if (appearance === 'danger') return 'danger'
277
+ if (appearance === 'secondary') return 'secondary'
278
+ return 'primary'
279
+ }
280
+ </script>
281
+
282
+ <style lang="postcss" src="./SRichBlockNode.css" scoped></style>
@@ -0,0 +1,6 @@
1
+ @reference "../../../../styles/reference.css";
2
+
3
+ .s-rich-document__inline-code {
4
+ @apply rounded bg-surface-100 px-1 py-0.5 font-mono text-sm text-surface-800;
5
+ @apply dark:bg-surface-800 dark:text-surface-100;
6
+ }
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <template v-if="node.type === 'text'">{{ node.value }}</template>
3
+ <strong v-else-if="node.type === 'strong'">
4
+ <SRichInlineNode
5
+ v-for="(child, index) in node.children"
6
+ :key="index"
7
+ :node="child"
8
+ :document-id="documentId"
9
+ :path="`${path}.children[${index}]`"
10
+ @link-activate="emit('link-activate', $event)"
11
+ />
12
+ </strong>
13
+ <em v-else-if="node.type === 'emphasis'">
14
+ <SRichInlineNode
15
+ v-for="(child, index) in node.children"
16
+ :key="index"
17
+ :node="child"
18
+ :document-id="documentId"
19
+ :path="`${path}.children[${index}]`"
20
+ @link-activate="emit('link-activate', $event)"
21
+ />
22
+ </em>
23
+ <code v-else-if="node.type === 'inlineCode'" class="s-rich-document__inline-code">{{ node.code }}</code>
24
+ <SLink
25
+ v-else-if="node.type === 'link'"
26
+ :href="node.href"
27
+ :title="node.title"
28
+ :active="node.current"
29
+ variant="document"
30
+ wrap="anywhere"
31
+ @click.prevent="activateLink"
32
+ >
33
+ <SRichInlineNode
34
+ v-for="(child, index) in node.children"
35
+ :key="index"
36
+ :node="child"
37
+ :document-id="documentId"
38
+ :path="`${path}.children[${index}]`"
39
+ @link-activate="emit('link-activate', $event)"
40
+ />
41
+ </SLink>
42
+ <AsyncSRichMath v-else-if="node.type === 'mathInline'" :source="node.source" display="inline" />
43
+ </template>
44
+
45
+ <script setup lang="ts">
46
+ import { defineAsyncComponent } from 'vue'
47
+
48
+ import SLink from '../../controls/SLink.vue'
49
+ import type { SRichDocumentLinkActivate, SRichInlineNode } from '../richDocument'
50
+
51
+ defineOptions({ inheritAttrs: false })
52
+
53
+ const props = defineProps<{
54
+ node: SRichInlineNode
55
+ documentId: string
56
+ path: string
57
+ }>()
58
+
59
+ const emit = defineEmits<{
60
+ 'link-activate': [payload: SRichDocumentLinkActivate]
61
+ }>()
62
+
63
+ const AsyncSRichMath = defineAsyncComponent(() => import('./SRichMath.vue'))
64
+
65
+ function activateLink(): void {
66
+ if (props.node.type !== 'link') return
67
+ emit('link-activate', Object.freeze({
68
+ documentId: props.documentId,
69
+ path: props.path,
70
+ href: props.node.href,
71
+ ...(props.node.title === undefined ? {} : { title: props.node.title }),
72
+ }))
73
+ }
74
+ </script>
75
+
76
+ <style lang="postcss" src="./SRichInlineNode.css" scoped></style>
@@ -0,0 +1,42 @@
1
+ @reference "../../../../styles/reference.css";
2
+
3
+ .s-rich-math {
4
+ min-width: 0;
5
+ }
6
+
7
+ .s-rich-math[data-display='block'] {
8
+ max-width: 100%;
9
+ overflow-x: auto;
10
+ padding-block: var(--s-space-content-gap);
11
+ text-align: center;
12
+ }
13
+
14
+ .s-rich-math__surface:empty {
15
+ display: none;
16
+ }
17
+
18
+ .s-rich-math__fallback,
19
+ .s-rich-math__error code {
20
+ @apply rounded bg-surface-100 px-1 py-0.5 font-mono text-sm text-surface-700;
21
+ @apply dark:bg-surface-800 dark:text-surface-200;
22
+ }
23
+
24
+ .s-rich-math__error {
25
+ @apply inline-flex min-w-0 flex-wrap items-baseline gap-2 text-danger-700 dark:text-danger-300;
26
+ }
27
+
28
+ .s-rich-math__assistive {
29
+ position: absolute;
30
+ width: 1px;
31
+ height: 1px;
32
+ padding: 0;
33
+ margin: -1px;
34
+ overflow: hidden;
35
+ clip: rect(0, 0, 0, 0);
36
+ white-space: nowrap;
37
+ border: 0;
38
+ }
39
+
40
+ .s-rich-math .katex-display {
41
+ margin: 0;
42
+ }