@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.
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/docs/accessibility.md +27 -0
- package/docs/getting-started.md +80 -0
- package/docs/licensing.md +30 -0
- package/docs/public-api.md +39 -0
- package/docs/security.md +21 -0
- package/docs/theming.md +35 -0
- package/docs/versioning.md +23 -0
- package/index.ts +3 -0
- package/package.json +592 -0
- package/src/components/icons/_internal/generatedSputnigUiIconMarkup.ts +971 -0
- package/src/components/icons/sputnigUiIcons.ts +530 -0
- package/src/components/layout/SAppShell.vue +232 -0
- package/src/components/layout/SDockRegion.vue +200 -0
- package/src/components/layout/SResizeHandle.vue +251 -0
- package/src/components/layout/SSideMenuButton.vue +104 -0
- package/src/components/layout/SSideMenuRail.vue +47 -0
- package/src/components/layout/SStack.css +25 -0
- package/src/components/layout/SStack.vue +57 -0
- package/src/components/layout/SStatusBarShell.css +15 -0
- package/src/components/layout/SStatusBarShell.vue +26 -0
- package/src/components/layout/SWorkbenchLayout.vue +53 -0
- package/src/components/layout/SWorkbenchPanelShell.css +161 -0
- package/src/components/layout/SWorkbenchPanelShell.vue +286 -0
- package/src/components/layout/_internal/SAppBrand.css +45 -0
- package/src/components/layout/_internal/SAppBrand.vue +37 -0
- package/src/components/layout/_internal/SAppBrandContent.vue +30 -0
- package/src/components/layout/types.ts +28 -0
- package/src/components/shared/_internal/SDialogSurface.css +127 -0
- package/src/components/shared/_internal/SDialogSurface.vue +117 -0
- package/src/components/shared/_internal/SDisclosureSummary.css +92 -0
- package/src/components/shared/_internal/SDisclosureSummary.vue +121 -0
- package/src/components/shared/_internal/SListboxGroup.css +18 -0
- package/src/components/shared/_internal/SListboxGroup.vue +24 -0
- package/src/components/shared/_internal/SMenuItem.css +112 -0
- package/src/components/shared/_internal/SMenuItem.vue +213 -0
- package/src/components/shared/_internal/SMenuLayer.css +21 -0
- package/src/components/shared/_internal/SMenuLayer.vue +48 -0
- package/src/components/shared/_internal/SMenuSeparator.css +5 -0
- package/src/components/shared/_internal/SMenuSeparator.vue +13 -0
- package/src/components/shared/_internal/SToastItem.css +65 -0
- package/src/components/shared/_internal/SToastItem.vue +58 -0
- package/src/components/shared/_internal/STreeNode.vue +761 -0
- package/src/components/shared/_internal/dialogSurface.ts +105 -0
- package/src/components/shared/_internal/focusTrap.ts +40 -0
- package/src/components/shared/_internal/listboxContext.ts +16 -0
- package/src/components/shared/_internal/markerContract.ts +10 -0
- package/src/components/shared/_internal/menuContext.ts +23 -0
- package/src/components/shared/_internal/selectionContract.ts +239 -0
- package/src/components/shared/_internal/sidebarGroupContext.ts +31 -0
- package/src/components/shared/_internal/tabsContext.ts +57 -0
- package/src/components/shared/_internal/treeRuntime.ts +203 -0
- package/src/components/shared/_internal/useSelectionRoving.ts +162 -0
- package/src/components/shared/codeLanguages.ts +168 -0
- package/src/components/shared/complex/SKanbanCard.css +8 -0
- package/src/components/shared/complex/SKanbanCard.vue +76 -0
- package/src/components/shared/complex/SKanbanLane.vue +82 -0
- package/src/components/shared/complex/STree.css +24 -0
- package/src/components/shared/complex/STree.vue +288 -0
- package/src/components/shared/complex/treeAdapter.ts +210 -0
- package/src/components/shared/complex/types.ts +102 -0
- package/src/components/shared/containers/SCloudChrome.css +103 -0
- package/src/components/shared/containers/SCloudChrome.vue +87 -0
- package/src/components/shared/containers/SCloudChromeEmpty.css +10 -0
- package/src/components/shared/containers/SCloudChromeEmpty.vue +18 -0
- package/src/components/shared/containers/SCloudChromeRow.css +78 -0
- package/src/components/shared/containers/SCloudChromeRow.vue +108 -0
- package/src/components/shared/containers/SCollapsiblePanel.css +57 -0
- package/src/components/shared/containers/SCollapsiblePanel.vue +360 -0
- package/src/components/shared/containers/SDrawer.css +60 -0
- package/src/components/shared/containers/SDrawer.vue +86 -0
- package/src/components/shared/containers/SFormGrid.css +26 -0
- package/src/components/shared/containers/SFormGrid.vue +46 -0
- package/src/components/shared/containers/SFormGridItem.css +18 -0
- package/src/components/shared/containers/SFormGridItem.vue +29 -0
- package/src/components/shared/containers/SLeftSidebar.vue +74 -0
- package/src/components/shared/containers/SModal.css +27 -0
- package/src/components/shared/containers/SModal.vue +105 -0
- package/src/components/shared/containers/SPageHeader.css +66 -0
- package/src/components/shared/containers/SPageHeader.vue +45 -0
- package/src/components/shared/containers/SPanel.css +134 -0
- package/src/components/shared/containers/SPanel.vue +169 -0
- package/src/components/shared/containers/SPopover.css +35 -0
- package/src/components/shared/containers/SPopover.vue +551 -0
- package/src/components/shared/containers/SSidebarGroup.css +12 -0
- package/src/components/shared/containers/SSidebarGroup.vue +443 -0
- package/src/components/shared/containers/SSidebarSection.css +41 -0
- package/src/components/shared/containers/SSidebarSection.vue +268 -0
- package/src/components/shared/containers/disclosureSummary.ts +48 -0
- package/src/components/shared/containers/popover.ts +72 -0
- package/src/components/shared/containers/sidebar.ts +206 -0
- package/src/components/shared/controls/SButton.css +274 -0
- package/src/components/shared/controls/SButton.vue +211 -0
- package/src/components/shared/controls/SCheckbox.css +65 -0
- package/src/components/shared/controls/SCheckbox.vue +130 -0
- package/src/components/shared/controls/SCheckboxGroup.vue +134 -0
- package/src/components/shared/controls/SChoiceCards.css +29 -0
- package/src/components/shared/controls/SChoiceCards.vue +145 -0
- package/src/components/shared/controls/SColorSelect.vue +146 -0
- package/src/components/shared/controls/SComboboxTrigger.css +35 -0
- package/src/components/shared/controls/SComboboxTrigger.vue +122 -0
- package/src/components/shared/controls/SContextMenu.css +11 -0
- package/src/components/shared/controls/SContextMenu.vue +186 -0
- package/src/components/shared/controls/SContextToggleButton.vue +64 -0
- package/src/components/shared/controls/SDragHandle.vue +100 -0
- package/src/components/shared/controls/SDropdownMenu.css +5 -0
- package/src/components/shared/controls/SDropdownMenu.vue +188 -0
- package/src/components/shared/controls/SField.css +79 -0
- package/src/components/shared/controls/SField.vue +89 -0
- package/src/components/shared/controls/SFieldGroup.css +58 -0
- package/src/components/shared/controls/SFieldGroup.vue +117 -0
- package/src/components/shared/controls/SFieldLabel.css +12 -0
- package/src/components/shared/controls/SFieldLabel.vue +51 -0
- package/src/components/shared/controls/SInlineTokenEditor.css +61 -0
- package/src/components/shared/controls/SInlineTokenEditor.vue +1081 -0
- package/src/components/shared/controls/SInputText.css +66 -0
- package/src/components/shared/controls/SInputText.vue +321 -0
- package/src/components/shared/controls/SInteractiveSurface.css +72 -0
- package/src/components/shared/controls/SInteractiveSurface.vue +326 -0
- package/src/components/shared/controls/SLink.css +114 -0
- package/src/components/shared/controls/SLink.ts +85 -0
- package/src/components/shared/controls/SLink.vue +114 -0
- package/src/components/shared/controls/SListbox.css +31 -0
- package/src/components/shared/controls/SListbox.vue +388 -0
- package/src/components/shared/controls/SListboxOption.css +63 -0
- package/src/components/shared/controls/SListboxOption.vue +85 -0
- package/src/components/shared/controls/SMarker.css +19 -0
- package/src/components/shared/controls/SMarker.vue +33 -0
- package/src/components/shared/controls/SMenuSurface.css +20 -0
- package/src/components/shared/controls/SMenuSurface.vue +333 -0
- package/src/components/shared/controls/SSegmentedControl.css +38 -0
- package/src/components/shared/controls/SSegmentedControl.vue +134 -0
- package/src/components/shared/controls/SSelect.css +12 -0
- package/src/components/shared/controls/SSelect.vue +338 -0
- package/src/components/shared/controls/STextarea.css +31 -0
- package/src/components/shared/controls/STextarea.vue +316 -0
- package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +224 -0
- package/src/components/shared/controls/menu.ts +178 -0
- package/src/components/shared/data-display/SActionCard.css +6 -0
- package/src/components/shared/data-display/SActionCard.vue +49 -0
- package/src/components/shared/data-display/SActionList.css +15 -0
- package/src/components/shared/data-display/SActionList.vue +34 -0
- package/src/components/shared/data-display/SActionListItem.css +114 -0
- package/src/components/shared/data-display/SActionListItem.vue +230 -0
- package/src/components/shared/data-display/SBadge.css +73 -0
- package/src/components/shared/data-display/SBadge.vue +52 -0
- package/src/components/shared/data-display/SCodeBlock.css +56 -0
- package/src/components/shared/data-display/SCodeBlock.vue +188 -0
- package/src/components/shared/data-display/SCodeEditor.css +178 -0
- package/src/components/shared/data-display/SCodeEditor.vue +274 -0
- package/src/components/shared/data-display/SCodeSearchPanel.vue +327 -0
- package/src/components/shared/data-display/SCopyField.vue +77 -0
- package/src/components/shared/data-display/SDocBlock.css +136 -0
- package/src/components/shared/data-display/SDocBlock.vue +358 -0
- package/src/components/shared/data-display/SEmptyState.css +42 -0
- package/src/components/shared/data-display/SEmptyState.vue +79 -0
- package/src/components/shared/data-display/SExpandableText.vue +314 -0
- package/src/components/shared/data-display/SJsonTree.css +6 -0
- package/src/components/shared/data-display/SJsonTree.vue +111 -0
- package/src/components/shared/data-display/SKeyValueGrid.css +37 -0
- package/src/components/shared/data-display/SKeyValueGrid.vue +277 -0
- package/src/components/shared/data-display/SLinkedSystemsList.vue +270 -0
- package/src/components/shared/data-display/SMessage.css +88 -0
- package/src/components/shared/data-display/SMessage.vue +152 -0
- package/src/components/shared/data-display/SMetricCard.css +32 -0
- package/src/components/shared/data-display/SMetricCard.vue +117 -0
- package/src/components/shared/data-display/SProgressBar.css +84 -0
- package/src/components/shared/data-display/SProgressBar.vue +114 -0
- package/src/components/shared/data-display/SProgressIndicator.css +59 -0
- package/src/components/shared/data-display/SProgressIndicator.vue +104 -0
- package/src/components/shared/data-display/SSectionHeader.css +84 -0
- package/src/components/shared/data-display/SSectionHeader.vue +58 -0
- package/src/components/shared/data-display/SStatus.css +90 -0
- package/src/components/shared/data-display/SStatus.vue +79 -0
- package/src/components/shared/data-display/STable.css +83 -0
- package/src/components/shared/data-display/STable.vue +339 -0
- package/src/components/shared/data-display/SToastContainer.css +53 -0
- package/src/components/shared/data-display/SToastContainer.vue +31 -0
- package/src/components/shared/data-display/STooltip.css +114 -0
- package/src/components/shared/data-display/STooltip.vue +550 -0
- package/src/components/shared/data-display/STooltipAnchor.css +21 -0
- package/src/components/shared/data-display/STooltipAnchor.vue +119 -0
- package/src/components/shared/data-display/SVirtualList.css +24 -0
- package/src/components/shared/data-display/SVirtualList.ts +19 -0
- package/src/components/shared/data-display/SVirtualList.vue +361 -0
- package/src/components/shared/data-display/json.ts +136 -0
- package/src/components/shared/data-display/table.ts +76 -0
- package/src/components/shared/database/SDataGrid.css +3 -0
- package/src/components/shared/database/SDataGrid.vue +464 -0
- package/src/components/shared/database/SSqlEditor.vue +363 -0
- package/src/components/shared/database/dataGrid.ts +61 -0
- package/src/components/shared/feedback/SAsyncState.css +26 -0
- package/src/components/shared/feedback/SAsyncState.vue +190 -0
- package/src/components/shared/graph/SGraphViewport.ts +1 -0
- package/src/components/shared/graph/SGraphViewport.vue +858 -0
- package/src/components/shared/graph/_internal/graphViewportContract.ts +362 -0
- package/src/components/shared/markdown.ts +169 -0
- package/src/components/shared/navigation/SBottomNav.vue +692 -0
- package/src/components/shared/navigation/SBreadcrumbs.css +26 -0
- package/src/components/shared/navigation/SBreadcrumbs.vue +155 -0
- package/src/components/shared/navigation/SCatalogNavigator.css +100 -0
- package/src/components/shared/navigation/SCatalogNavigator.vue +308 -0
- package/src/components/shared/navigation/STab.vue +363 -0
- package/src/components/shared/navigation/STabList.vue +442 -0
- package/src/components/shared/navigation/STabPanel.vue +165 -0
- package/src/components/shared/navigation/STabPanels.vue +33 -0
- package/src/components/shared/navigation/STabs.vue +617 -0
- package/src/components/shared/navigation/SWizardSteps.css +69 -0
- package/src/components/shared/navigation/SWizardSteps.vue +171 -0
- package/src/components/shared/navigation/_internal/SCatalogNavigatorGroup.vue +63 -0
- package/src/components/shared/persona/SPersonaProjectPicker.css +36 -0
- package/src/components/shared/persona/SPersonaProjectPicker.vue +361 -0
- package/src/components/shared/persona/SPersonaRunWorkbench.css +109 -0
- package/src/components/shared/persona/SPersonaRunWorkbench.vue +549 -0
- package/src/components/shared/persona/_internal/SPersonaRunConsole.css +40 -0
- package/src/components/shared/persona/_internal/SPersonaRunConsole.vue +298 -0
- package/src/components/shared/persona/_internal/inventory.ts +110 -0
- package/src/components/shared/persona/_internal/labels.ts +79 -0
- package/src/components/shared/persona/presentation.ts +371 -0
- package/src/components/shared/persona/types.ts +256 -0
- package/src/composables/uiPreferencesReset.ts +52 -0
- package/src/composables/useClipboard.ts +37 -0
- package/src/composables/useDialogOverlay.ts +85 -0
- package/src/composables/useDisclosure.ts +40 -0
- package/src/composables/useFloatingPosition.ts +245 -0
- package/src/composables/useNotifier.ts +26 -0
- package/src/composables/useSidebarPanelState.ts +610 -0
- package/src/internal/codeEditorContract.ts +110 -0
- package/src/internal/codeEditorExtensions.ts +27 -0
- package/src/internal/feedbackPresentation.ts +136 -0
- package/src/internal/fieldSurface.ts +59 -0
- package/src/internal/focusNavigation.ts +69 -0
- package/src/internal/inlineTokenEditorContract.ts +136 -0
- package/src/internal/interactiveElement.ts +33 -0
- package/src/internal/layerStack.ts +202 -0
- package/src/internal/linkTarget.ts +256 -0
- package/src/internal/ownedAttrs.ts +464 -0
- package/src/internal/passiveContentContract.ts +129 -0
- package/src/internal/pointerInteractionLease.ts +81 -0
- package/src/internal/runtimeContract.ts +194 -0
- package/src/internal/runtimeTheme.ts +159 -0
- package/src/internal/semanticSizing.ts +60 -0
- package/src/internal/useMenuLayer.ts +129 -0
- package/src/stores/useNotifierStore.ts +69 -0
- package/src/styles/reference.css +4 -0
- package/src/styles/style.css +151 -0
- package/src/styles/tokens.css +836 -0
- package/src/theme.ts +449 -0
- package/src/types/highlight-js-lib.d.ts +76 -0
- package/src/variants.ts +15 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { inject, onBeforeUnmount, onMounted } from 'vue'
|
|
2
|
+
import type { ComputedRef, InjectionKey, Ref } from 'vue'
|
|
3
|
+
import type {
|
|
4
|
+
STreeNodeContentScope,
|
|
5
|
+
TreeActionsLayout,
|
|
6
|
+
TreeActivationOrigin,
|
|
7
|
+
TreeDensity,
|
|
8
|
+
TreeExpandControl,
|
|
9
|
+
SelectionKey,
|
|
10
|
+
TreeModel,
|
|
11
|
+
TreeRowVariant,
|
|
12
|
+
TreeSelection,
|
|
13
|
+
TreeSelectionRail,
|
|
14
|
+
} from '../complex/types'
|
|
15
|
+
|
|
16
|
+
export const TREE_RUNTIME_KEY = 's-tree-runtime'
|
|
17
|
+
export const TREE_ACTION_CONTEXT_KEY: InjectionKey<TreeActionContext> = Symbol('s-tree-action-context')
|
|
18
|
+
|
|
19
|
+
export interface RegisteredTreeItem<T extends object, K extends SelectionKey> {
|
|
20
|
+
key: K
|
|
21
|
+
parentKey?: K
|
|
22
|
+
node: T
|
|
23
|
+
element: HTMLElement
|
|
24
|
+
activate(origin: TreeActivationOrigin): void
|
|
25
|
+
select(): void
|
|
26
|
+
toggle(): void
|
|
27
|
+
expanded(): boolean
|
|
28
|
+
hasChildren(): boolean
|
|
29
|
+
disabled(): boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type TreeActionKey = symbol
|
|
33
|
+
|
|
34
|
+
export interface RegisteredTreeAction {
|
|
35
|
+
readonly key: TreeActionKey
|
|
36
|
+
readonly element: HTMLElement
|
|
37
|
+
focus(options?: FocusOptions): void
|
|
38
|
+
disabled(): boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TreeActionContext {
|
|
42
|
+
register(action: RegisteredTreeAction): void
|
|
43
|
+
unregister(action: RegisteredTreeAction): void
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function compareRegisteredElements(
|
|
47
|
+
owner: string,
|
|
48
|
+
left: { readonly element: HTMLElement },
|
|
49
|
+
right: { readonly element: HTMLElement },
|
|
50
|
+
): number {
|
|
51
|
+
if (left.element === right.element) return 0
|
|
52
|
+
const position = left.element.compareDocumentPosition(right.element)
|
|
53
|
+
const nodeConstructor = left.element.ownerDocument.defaultView?.Node
|
|
54
|
+
if (!nodeConstructor) {
|
|
55
|
+
throw new Error(`${owner}: registered elements require an active DOM document`)
|
|
56
|
+
}
|
|
57
|
+
if ((position & nodeConstructor.DOCUMENT_POSITION_DISCONNECTED) !== 0) {
|
|
58
|
+
throw new Error(`${owner}: registered elements must belong to the same DOM tree`)
|
|
59
|
+
}
|
|
60
|
+
if ((position & nodeConstructor.DOCUMENT_POSITION_FOLLOWING) !== 0) return -1
|
|
61
|
+
if ((position & nodeConstructor.DOCUMENT_POSITION_PRECEDING) !== 0) return 1
|
|
62
|
+
throw new Error(`${owner}: registered elements have no deterministic DOM order`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function orderedRegistrations<R extends { readonly element: HTMLElement }>(
|
|
66
|
+
owner: string,
|
|
67
|
+
root: HTMLElement | null,
|
|
68
|
+
registrations: Iterable<R>,
|
|
69
|
+
): R[] {
|
|
70
|
+
if (!root) return []
|
|
71
|
+
return [...registrations]
|
|
72
|
+
.filter((registration) => registration.element.isConnected && root.contains(registration.element))
|
|
73
|
+
.sort((left, right) => compareRegisteredElements(owner, left, right))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class TreeItemRegistrationStore<T extends object, K extends SelectionKey> {
|
|
77
|
+
readonly #items = new Map<K, RegisteredTreeItem<T, K>>()
|
|
78
|
+
readonly #root: () => HTMLElement | null
|
|
79
|
+
|
|
80
|
+
constructor(root: () => HTMLElement | null) {
|
|
81
|
+
this.#root = root
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
register(item: RegisteredTreeItem<T, K>): void {
|
|
85
|
+
const current = this.#items.get(item.key)
|
|
86
|
+
if (current && current.element !== item.element) {
|
|
87
|
+
throw new Error(`STree: runtime registration collision for ${typeof item.key}:${String(item.key)}`)
|
|
88
|
+
}
|
|
89
|
+
this.#items.set(item.key, item)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
unregister(key: K): void {
|
|
93
|
+
this.#items.delete(key)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get(key: K): RegisteredTreeItem<T, K> | undefined {
|
|
97
|
+
return this.#items.get(key)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
ordered(): RegisteredTreeItem<T, K>[] {
|
|
101
|
+
return orderedRegistrations('STree item registry', this.#root(), this.#items.values())
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export class TreeActionRegistrationStore implements TreeActionContext {
|
|
106
|
+
readonly #actions = new Map<TreeActionKey, RegisteredTreeAction>()
|
|
107
|
+
readonly #root: () => HTMLElement | null
|
|
108
|
+
|
|
109
|
+
constructor(root: () => HTMLElement | null) {
|
|
110
|
+
this.#root = root
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
register(action: RegisteredTreeAction): void {
|
|
114
|
+
const current = this.#actions.get(action.key)
|
|
115
|
+
if (current && current !== action) {
|
|
116
|
+
throw new Error(`STree: action registration collision for ${String(action.key)}`)
|
|
117
|
+
}
|
|
118
|
+
const elementOwner = [...this.#actions.values()].find((candidate) =>
|
|
119
|
+
candidate.key !== action.key && candidate.element === action.element,
|
|
120
|
+
)
|
|
121
|
+
if (elementOwner) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`STree: action element is already registered by ${String(elementOwner.key)}`,
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
this.#actions.set(action.key, action)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
unregister(action: RegisteredTreeAction): void {
|
|
130
|
+
const current = this.#actions.get(action.key)
|
|
131
|
+
if (!current) return
|
|
132
|
+
if (current !== action) {
|
|
133
|
+
throw new Error(`STree: stale action unregistration for ${String(action.key)}`)
|
|
134
|
+
}
|
|
135
|
+
this.#actions.delete(action.key)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
orderedEnabled(): RegisteredTreeAction[] {
|
|
139
|
+
return orderedRegistrations(
|
|
140
|
+
'STree action registry',
|
|
141
|
+
this.#root(),
|
|
142
|
+
this.#actions.values(),
|
|
143
|
+
).filter((action) => !action.disabled())
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
actionForTarget(target: EventTarget | null): RegisteredTreeAction | undefined {
|
|
147
|
+
if (!(target instanceof Node)) return undefined
|
|
148
|
+
return this.orderedEnabled().find((action) =>
|
|
149
|
+
action.element === target || action.element.contains(target),
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function useTreeActionRegistration(options: Readonly<{
|
|
155
|
+
owner: string
|
|
156
|
+
element: Readonly<Ref<HTMLElement | null>>
|
|
157
|
+
disabled: () => boolean
|
|
158
|
+
}>): boolean {
|
|
159
|
+
const context = inject(TREE_ACTION_CONTEXT_KEY, null)
|
|
160
|
+
if (!context) return false
|
|
161
|
+
|
|
162
|
+
const key = Symbol(options.owner)
|
|
163
|
+
let registration: RegisteredTreeAction | undefined
|
|
164
|
+
onMounted(() => {
|
|
165
|
+
const element = options.element.value
|
|
166
|
+
if (!element) {
|
|
167
|
+
throw new Error(`STree: ${options.owner} action registration requires a mounted element`)
|
|
168
|
+
}
|
|
169
|
+
registration = {
|
|
170
|
+
key,
|
|
171
|
+
element,
|
|
172
|
+
focus: (focusOptions) => element.focus(focusOptions),
|
|
173
|
+
disabled: options.disabled,
|
|
174
|
+
}
|
|
175
|
+
context.register(registration)
|
|
176
|
+
})
|
|
177
|
+
onBeforeUnmount(() => {
|
|
178
|
+
if (registration) context.unregister(registration)
|
|
179
|
+
})
|
|
180
|
+
return true
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface TreeRuntime<T extends object, K extends SelectionKey> {
|
|
184
|
+
model: TreeModel<T, K>
|
|
185
|
+
selection: ComputedRef<TreeSelection<K>>
|
|
186
|
+
expanded: ComputedRef<ReadonlySet<K>>
|
|
187
|
+
density: ComputedRef<TreeDensity>
|
|
188
|
+
actionsLayout: ComputedRef<TreeActionsLayout>
|
|
189
|
+
expandControl: ComputedRef<TreeExpandControl>
|
|
190
|
+
selectionRail: ComputedRef<TreeSelectionRail>
|
|
191
|
+
rowVariant: ComputedRef<TreeRowVariant>
|
|
192
|
+
activeKey: Ref<K | undefined>
|
|
193
|
+
activateNode(node: T, origin: TreeActivationOrigin): void
|
|
194
|
+
selectNode(node: T): void
|
|
195
|
+
toggleNode(node: T): void
|
|
196
|
+
checkNode(node: T): void
|
|
197
|
+
multipleKeys(node: T): readonly K[]
|
|
198
|
+
register(item: RegisteredTreeItem<T, K>): void
|
|
199
|
+
unregister(key: K): void
|
|
200
|
+
handleItemKeydown(item: RegisteredTreeItem<T, K>, event: KeyboardEvent): void
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export type TreeNodeScope<T extends object, K extends SelectionKey> = STreeNodeContentScope<T, K>
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { computed } from 'vue'
|
|
2
|
+
import type { ComputedRef, Ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
/** Стабильный ключ, принимаемый владельцами выбора. / Stable key accepted by selection owners. */
|
|
5
|
+
export type SelectionKey = string | number
|
|
6
|
+
|
|
7
|
+
/** Ключ и точная координата его объявления для диагностики. / A key and its exact declaration coordinate for diagnostics. */
|
|
8
|
+
export interface SelectionIdentity<T extends SelectionKey = SelectionKey> {
|
|
9
|
+
key: T
|
|
10
|
+
coordinate: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Опция общего roving-focus с состоянием доступности. / A shared roving-focus option with availability state. */
|
|
14
|
+
export interface SelectionRovingOption<T extends SelectionKey> {
|
|
15
|
+
value: T
|
|
16
|
+
disabled?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Публичный keyboard/focus-контракт владельца выбора. / Public keyboard/focus contract for a selection owner. */
|
|
20
|
+
export interface SelectionRovingApi<T extends SelectionKey> {
|
|
21
|
+
focusableValue: ComputedRef<T | undefined>
|
|
22
|
+
register: (value: T, element: Element | null) => void
|
|
23
|
+
handleKeydown: (event: KeyboardEvent, value: T) => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function invalidKeyMessage(component: string, coordinate: string): string {
|
|
27
|
+
return `${component}: ${coordinate} must be a non-empty string or a finite number`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Проверяет runtime-контракт identity в точной координате вызова. / Asserts the runtime identity contract at the exact caller coordinate. */
|
|
31
|
+
export function assertSelectionKey(
|
|
32
|
+
component: string,
|
|
33
|
+
key: unknown,
|
|
34
|
+
coordinate: string,
|
|
35
|
+
): asserts key is SelectionKey {
|
|
36
|
+
if (typeof key === 'string') {
|
|
37
|
+
if (key.trim().length === 0) throw new TypeError(invalidKeyMessage(component, coordinate))
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
if (typeof key === 'number' && Number.isFinite(key)) return
|
|
41
|
+
throw new TypeError(invalidKeyMessage(component, coordinate))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Возвращает type-aware token для DOM id, Vue key и lookup map. / Returns a type-aware token for DOM ids, Vue keys, and lookup maps. */
|
|
45
|
+
export function selectionKeyToken(
|
|
46
|
+
component: string,
|
|
47
|
+
key: SelectionKey,
|
|
48
|
+
coordinate: string,
|
|
49
|
+
): string {
|
|
50
|
+
assertSelectionKey(component, key, coordinate)
|
|
51
|
+
return typeof key === 'string'
|
|
52
|
+
? `string:${encodeURIComponent(key)}`
|
|
53
|
+
: `number:${Object.is(key, -0) ? '0' : String(key)}`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Проверяет полный inventory identity и возвращает канонический индекс. / Validates a complete identity inventory and returns its canonical index. */
|
|
57
|
+
export function validateSelectionIdentities<T extends SelectionKey>(
|
|
58
|
+
component: string,
|
|
59
|
+
identities: readonly SelectionIdentity<T>[],
|
|
60
|
+
): ReadonlyMap<string, T> {
|
|
61
|
+
const keysByToken = new Map<string, T>()
|
|
62
|
+
const coordinatesByToken = new Map<string, string>()
|
|
63
|
+
|
|
64
|
+
for (const identity of identities) {
|
|
65
|
+
const token = selectionKeyToken(component, identity.key, identity.coordinate)
|
|
66
|
+
const previousCoordinate = coordinatesByToken.get(token)
|
|
67
|
+
if (previousCoordinate !== undefined) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`${component}: duplicate selection key at ${identity.coordinate}; first declared at ${previousCoordinate}`,
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
coordinatesByToken.set(token, identity.coordinate)
|
|
73
|
+
keysByToken.set(token, identity.key)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return keysByToken
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function validateRovingOptions<T extends SelectionKey>(
|
|
80
|
+
component: string,
|
|
81
|
+
options: readonly SelectionRovingOption<T>[],
|
|
82
|
+
): ReadonlyMap<string, T> {
|
|
83
|
+
return validateSelectionIdentities(component, options.map((option, index) => ({
|
|
84
|
+
key: option.value,
|
|
85
|
+
coordinate: `options[${index}].value`,
|
|
86
|
+
})))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function validateSelectedValue<T extends SelectionKey>(
|
|
90
|
+
component: string,
|
|
91
|
+
selectedValue: T | null | undefined,
|
|
92
|
+
optionsByToken: ReadonlyMap<string, T>,
|
|
93
|
+
): void {
|
|
94
|
+
if (selectedValue === null || selectedValue === undefined) return
|
|
95
|
+
const token = selectionKeyToken(component, selectedValue, 'selectedValue')
|
|
96
|
+
if (!optionsByToken.has(token)) {
|
|
97
|
+
throw new Error(`${component}: selectedValue does not match any option`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Управляет общим roving-focus без потери доменного типа ключа.
|
|
103
|
+
* Manages shared roving focus without losing the domain key type.
|
|
104
|
+
*/
|
|
105
|
+
export function useSelectionRoving<T extends SelectionKey>(config: {
|
|
106
|
+
options: ComputedRef<readonly SelectionRovingOption<T>[]>
|
|
107
|
+
selectedValue: Ref<T | null | undefined>
|
|
108
|
+
orientation?: 'horizontal' | 'vertical' | 'both'
|
|
109
|
+
select: (value: T) => void
|
|
110
|
+
component?: string
|
|
111
|
+
}): SelectionRovingApi<T> {
|
|
112
|
+
const elements = new Map<T, HTMLElement>()
|
|
113
|
+
const orientation = config.orientation ?? 'both'
|
|
114
|
+
const component = config.component ?? 'useSelectionRoving'
|
|
115
|
+
|
|
116
|
+
const validatedOptions = computed(() => {
|
|
117
|
+
const options = config.options.value
|
|
118
|
+
const optionsByToken = validateRovingOptions(component, options)
|
|
119
|
+
validateSelectedValue(component, config.selectedValue.value, optionsByToken)
|
|
120
|
+
return options
|
|
121
|
+
})
|
|
122
|
+
const enabledOptions = computed(() => validatedOptions.value.filter((option) => !option.disabled))
|
|
123
|
+
const focusableValue = computed(() => {
|
|
124
|
+
const selectedValue = config.selectedValue.value
|
|
125
|
+
return enabledOptions.value.find((option) => option.value === selectedValue)?.value
|
|
126
|
+
?? enabledOptions.value[0]?.value
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const register = (value: T, element: Element | null): void => {
|
|
130
|
+
selectionKeyToken(component, value, 'registered value')
|
|
131
|
+
if (element instanceof HTMLElement) elements.set(value, element)
|
|
132
|
+
else elements.delete(value)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const handleKeydown = (event: KeyboardEvent, value: T): void => {
|
|
136
|
+
const direction = (
|
|
137
|
+
orientation !== 'vertical' && event.key === 'ArrowLeft' ? -1
|
|
138
|
+
: orientation !== 'vertical' && event.key === 'ArrowRight' ? 1
|
|
139
|
+
: orientation !== 'horizontal' && event.key === 'ArrowUp' ? -1
|
|
140
|
+
: orientation !== 'horizontal' && event.key === 'ArrowDown' ? 1
|
|
141
|
+
: null
|
|
142
|
+
)
|
|
143
|
+
const enabled = enabledOptions.value
|
|
144
|
+
if (enabled.length === 0) return
|
|
145
|
+
|
|
146
|
+
const currentIndex = enabled.findIndex((option) => option.value === value)
|
|
147
|
+
const target = event.key === 'Home'
|
|
148
|
+
? enabled[0]
|
|
149
|
+
: event.key === 'End'
|
|
150
|
+
? enabled[enabled.length - 1]
|
|
151
|
+
: direction === null
|
|
152
|
+
? undefined
|
|
153
|
+
: enabled[(Math.max(currentIndex, 0) + direction + enabled.length) % enabled.length]
|
|
154
|
+
if (!target) return
|
|
155
|
+
|
|
156
|
+
event.preventDefault()
|
|
157
|
+
config.select(target.value)
|
|
158
|
+
elements.get(target.value)?.focus()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return { focusableValue, register, handleKeydown }
|
|
162
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { StreamLanguage } from '@codemirror/language'
|
|
2
|
+
import type { Extension } from '@codemirror/state'
|
|
3
|
+
import { langNames, loadLanguage } from '@uiw/codemirror-extensions-langs'
|
|
4
|
+
import { makefile } from 'codemirror-lang-makefile'
|
|
5
|
+
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile'
|
|
6
|
+
import { nginx } from '@codemirror/legacy-modes/mode/nginx'
|
|
7
|
+
import { shell } from '@codemirror/legacy-modes/mode/shell'
|
|
8
|
+
import {
|
|
9
|
+
type CodeEditorLanguageContract,
|
|
10
|
+
validateCodeEditorLanguage,
|
|
11
|
+
} from '../../internal/codeEditorContract'
|
|
12
|
+
|
|
13
|
+
type NullableText = string | null | undefined
|
|
14
|
+
|
|
15
|
+
const SPECIAL_FILENAMES: Record<string, string> = {
|
|
16
|
+
'cmakelists.txt': 'cmake',
|
|
17
|
+
'dockerfile': 'dockerfile',
|
|
18
|
+
'docker-compose.yml': 'yaml',
|
|
19
|
+
'docker-compose.yaml': 'yaml',
|
|
20
|
+
'compose.yml': 'yaml',
|
|
21
|
+
'compose.yaml': 'yaml',
|
|
22
|
+
'.env': 'properties',
|
|
23
|
+
'.env.example': 'properties',
|
|
24
|
+
'.env.local': 'properties',
|
|
25
|
+
'.env.development': 'properties',
|
|
26
|
+
'.env.production': 'properties',
|
|
27
|
+
'makefile': 'makefile',
|
|
28
|
+
'nginx.conf': 'nginx',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const LANGUAGE_ALIASES: Record<string, string> = {
|
|
32
|
+
bash: 'shell',
|
|
33
|
+
'c#': 'cs',
|
|
34
|
+
csharp: 'cs',
|
|
35
|
+
env: 'properties',
|
|
36
|
+
fish: 'shell',
|
|
37
|
+
handlebars: 'handlebars',
|
|
38
|
+
htm: 'html',
|
|
39
|
+
js: 'javascript',
|
|
40
|
+
jsonc: 'json',
|
|
41
|
+
json5: 'json',
|
|
42
|
+
log: 'text',
|
|
43
|
+
make: 'makefile',
|
|
44
|
+
md: 'markdown',
|
|
45
|
+
mk: 'makefile',
|
|
46
|
+
plaintext: 'text',
|
|
47
|
+
proto3: 'proto',
|
|
48
|
+
ps1: 'powershell',
|
|
49
|
+
psm1: 'powershell',
|
|
50
|
+
psd1: 'powershell',
|
|
51
|
+
py: 'python',
|
|
52
|
+
pyw: 'python',
|
|
53
|
+
pyi: 'python',
|
|
54
|
+
rbw: 'ruby',
|
|
55
|
+
sh: 'shell',
|
|
56
|
+
shellsession: 'shell',
|
|
57
|
+
ts: 'typescript',
|
|
58
|
+
text: 'text',
|
|
59
|
+
txt: 'text',
|
|
60
|
+
yaml: 'yaml',
|
|
61
|
+
yml: 'yaml',
|
|
62
|
+
zsh: 'shell',
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const LEGACY_CODEMIRROR_LANGUAGES: Record<string, Extension> = {
|
|
66
|
+
dockerfile: StreamLanguage.define(dockerFile),
|
|
67
|
+
makefile: makefile(),
|
|
68
|
+
nginx: StreamLanguage.define(nginx),
|
|
69
|
+
shell: StreamLanguage.define(shell),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const CODEMIRROR_LANGUAGE_ALIASES: Record<string, string> = {
|
|
73
|
+
javascript: 'js',
|
|
74
|
+
powershell: 'ps1',
|
|
75
|
+
python: 'py',
|
|
76
|
+
ruby: 'rb',
|
|
77
|
+
rust: 'rs',
|
|
78
|
+
shell: 'bash',
|
|
79
|
+
typescript: 'ts',
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const HIGHLIGHT_LANGUAGE_ALIASES: Record<string, string> = {
|
|
83
|
+
shell: 'bash',
|
|
84
|
+
text: 'plaintext',
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type SupportedCodeMirrorLanguage = Parameters<typeof loadLanguage>[0]
|
|
88
|
+
|
|
89
|
+
const SUPPORTED_CODEMIRROR_LANGUAGES = new Set<SupportedCodeMirrorLanguage>(langNames)
|
|
90
|
+
|
|
91
|
+
function normalizeDetectedToken(value: NullableText): string | null {
|
|
92
|
+
if (!value) {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const normalized = value.trim().toLowerCase().replace(/^\./, '')
|
|
97
|
+
if (!normalized) {
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return LANGUAGE_ALIASES[normalized] ?? normalized
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveLanguageFromFilePath(filePath: NullableText): string | null {
|
|
105
|
+
if (!filePath) {
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const normalizedPath = filePath.trim().toLowerCase()
|
|
110
|
+
if (!normalizedPath) {
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const basename = normalizedPath.split('/').pop() ?? normalizedPath
|
|
115
|
+
if (SPECIAL_FILENAMES[basename]) {
|
|
116
|
+
return SPECIAL_FILENAMES[basename]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (basename.startsWith('dockerfile.')) {
|
|
120
|
+
return 'dockerfile'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!basename.includes('.')) {
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const extension = basename.split('.').pop()
|
|
128
|
+
return normalizeDetectedToken(extension)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isSupportedCodeMirrorLanguage(value: string): value is SupportedCodeMirrorLanguage {
|
|
132
|
+
return SUPPORTED_CODEMIRROR_LANGUAGES.has(value as SupportedCodeMirrorLanguage)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function resolveCodeLanguageKey(input: CodeEditorLanguageContract): string {
|
|
136
|
+
const language = validateCodeEditorLanguage('codeLanguages', input)
|
|
137
|
+
if (language.mode === 'explicit') {
|
|
138
|
+
return LANGUAGE_ALIASES[language.id] ?? language.id
|
|
139
|
+
}
|
|
140
|
+
const detected = resolveLanguageFromFilePath(language.filePath)
|
|
141
|
+
if (!detected) {
|
|
142
|
+
throw new RangeError(
|
|
143
|
+
`codeLanguages: невозможно определить язык по filePath ${JSON.stringify(language.filePath)}. `
|
|
144
|
+
+ `/ codeLanguages: cannot detect a language from filePath ${JSON.stringify(language.filePath)}.`,
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
return detected
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function getCodeMirrorLanguageExtension(input: CodeEditorLanguageContract): Extension | null {
|
|
151
|
+
const languageKey = resolveCodeLanguageKey(input)
|
|
152
|
+
|
|
153
|
+
const codeMirrorLanguageKey = CODEMIRROR_LANGUAGE_ALIASES[languageKey] ?? languageKey
|
|
154
|
+
|
|
155
|
+
if (isSupportedCodeMirrorLanguage(codeMirrorLanguageKey)) {
|
|
156
|
+
const loadedLanguage = loadLanguage(codeMirrorLanguageKey)
|
|
157
|
+
if (loadedLanguage) {
|
|
158
|
+
return loadedLanguage
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return LEGACY_CODEMIRROR_LANGUAGES[codeMirrorLanguageKey] ?? LEGACY_CODEMIRROR_LANGUAGES[languageKey] ?? null
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function resolveHighlightLanguageKey(input: CodeEditorLanguageContract): string {
|
|
166
|
+
const languageKey = resolveCodeLanguageKey(input)
|
|
167
|
+
return HIGHLIGHT_LANGUAGE_ALIASES[languageKey] ?? languageKey
|
|
168
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
@reference "../../../styles/reference.css";
|
|
2
|
+
|
|
3
|
+
.s-kanban-card__content { @apply flex w-full min-w-0 flex-col; }
|
|
4
|
+
.s-kanban-card__copy { @apply min-w-0; }
|
|
5
|
+
.s-kanban-card__title { @apply block truncate text-sm font-medium text-surface-800 dark:text-surface-100; }
|
|
6
|
+
.s-kanban-card__subtitle { @apply mt-0.5 block truncate text-xs text-surface-500 dark:text-surface-400; }
|
|
7
|
+
.s-kanban-card__body { @apply mt-2 min-w-0; }
|
|
8
|
+
.s-kanban-card__meta { @apply mt-2 flex min-w-0 flex-wrap items-center gap-2 text-xs text-surface-500 dark:text-surface-400; }
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type {
|
|
4
|
+
InteractiveSurfaceContract,
|
|
5
|
+
InteractiveSurfaceDensity,
|
|
6
|
+
InteractiveSurfacePadding,
|
|
7
|
+
InteractiveSurfaceRadius,
|
|
8
|
+
InteractiveSurfaceTone,
|
|
9
|
+
} from '../controls/SInteractiveSurface.vue'
|
|
10
|
+
import SActionCard from '../data-display/SActionCard.vue'
|
|
11
|
+
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
12
|
+
|
|
13
|
+
defineOptions({ inheritAttrs: false })
|
|
14
|
+
|
|
15
|
+
export interface Props {
|
|
16
|
+
title: string
|
|
17
|
+
subtitle?: string
|
|
18
|
+
surface?: InteractiveSurfaceContract
|
|
19
|
+
selected?: boolean
|
|
20
|
+
tone?: InteractiveSurfaceTone
|
|
21
|
+
density?: InteractiveSurfaceDensity
|
|
22
|
+
padding?: InteractiveSurfacePadding
|
|
23
|
+
radius?: InteractiveSurfaceRadius
|
|
24
|
+
fill?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
28
|
+
subtitle: undefined,
|
|
29
|
+
surface: undefined,
|
|
30
|
+
selected: false,
|
|
31
|
+
tone: 'default',
|
|
32
|
+
density: 'comfortable',
|
|
33
|
+
padding: 'sm',
|
|
34
|
+
radius: 'md',
|
|
35
|
+
fill: false,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const emit = defineEmits<{
|
|
39
|
+
activate: [event: MouseEvent]
|
|
40
|
+
}>()
|
|
41
|
+
|
|
42
|
+
const resolvedSurface = computed<InteractiveSurfaceContract>(() => props.surface ?? { mode: 'static' })
|
|
43
|
+
const ownedAttrs = useOwnedAttrs({ component: 'SKanbanCard', owner: 'canonical action-card surface' })
|
|
44
|
+
|
|
45
|
+
function activate(event: MouseEvent): void {
|
|
46
|
+
emit('activate', event)
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<SActionCard
|
|
52
|
+
data-testid="s-kanban-card"
|
|
53
|
+
v-bind="ownedAttrs.bindings()"
|
|
54
|
+
:surface="resolvedSurface"
|
|
55
|
+
:selected="selected"
|
|
56
|
+
:tone="tone"
|
|
57
|
+
:density="density"
|
|
58
|
+
:padding="padding"
|
|
59
|
+
:radius="radius"
|
|
60
|
+
:fill="fill ? 'container' : 'content'"
|
|
61
|
+
@activate="activate"
|
|
62
|
+
>
|
|
63
|
+
<div class="s-kanban-card__content">
|
|
64
|
+
<span class="s-kanban-card__copy">
|
|
65
|
+
<span class="s-kanban-card__title">{{ title }}</span>
|
|
66
|
+
<span v-if="subtitle" class="s-kanban-card__subtitle">{{ subtitle }}</span>
|
|
67
|
+
</span>
|
|
68
|
+
|
|
69
|
+
<span v-if="$slots.default" class="s-kanban-card__body"><slot /></span>
|
|
70
|
+
<span v-if="$slots.meta" class="s-kanban-card__meta"><slot name="meta" /></span>
|
|
71
|
+
</div>
|
|
72
|
+
<template v-if="$slots.actions" #actions><slot name="actions" /></template>
|
|
73
|
+
</SActionCard>
|
|
74
|
+
</template>
|
|
75
|
+
|
|
76
|
+
<style lang="postcss" src="./SKanbanCard.css" scoped></style>
|