@shendeguize/dsh-agent-sidecar 0.1.0 → 0.2.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/README.md +78 -11
- package/lib/client.js +4708 -2932
- package/lib/client.js.map +1 -1
- package/package.json +3 -1
- package/src/client/analysis/AnalysisPanel.tsx +19 -27
- package/src/client/analysis/analysis.module.css +36 -97
- package/src/client/board/Board.tsx +255 -48
- package/src/client/board/board.module.css +113 -92
- package/src/client/board/logic.ts +99 -21
- package/src/client/board/project-view-logic.ts +27 -34
- package/src/client/board/project-view.module.css +46 -83
- package/src/client/board/project-view.tsx +50 -8
- package/src/client/board/strings.ts +70 -85
- package/src/client/commands.ts +30 -15
- package/src/client/controller.ts +27 -7
- package/src/client/detail/SessionDetail.tsx +234 -37
- package/src/client/detail/detail.module.css +100 -153
- package/src/client/detail/logic.ts +220 -29
- package/src/client/detail/strings.ts +66 -77
- package/src/client/detail-glue.ts +33 -0
- package/src/client/detail-view.module.css +43 -35
- package/src/client/detail-view.tsx +138 -118
- package/src/client/dsh-tools/LineageTree.tsx +22 -13
- package/src/client/dsh-tools/SearchPanel.tsx +18 -11
- package/src/client/dsh-tools/dsh-tools.module.css +53 -140
- package/src/client/dsh-tools/strings.ts +42 -77
- package/src/client/index.ts +285 -124
- package/src/client/inject/InjectPanel.tsx +81 -61
- package/src/client/inject/inject.module.css +97 -197
- package/src/client/inject/logic.ts +38 -0
- package/src/client/lifecycle/handoff.ts +83 -0
- package/src/client/locales/en.ts +91 -4
- package/src/client/locales/host.ts +131 -0
- package/src/client/locales/index.ts +196 -8
- package/src/client/locales/react.ts +10 -0
- package/src/client/locales/view.ts +38 -0
- package/src/client/locales/zh.ts +200 -125
- package/src/client/mount.tsx +75 -41
- package/src/client/navigation/CenterOverlay.tsx +40 -0
- package/src/client/navigation/center-overlay.module.css +51 -0
- package/src/client/navigation/center.ts +45 -0
- package/src/client/navigation/modal-isolation.ts +152 -0
- package/src/client/navigation/modal-surface-anchor.ts +72 -0
- package/src/client/navigation/sidebar-entry.module.css +73 -0
- package/src/client/navigation/sidebar-entry.ts +309 -0
- package/src/client/primitives/StaticPill.tsx +21 -0
- package/src/client/settings-card.module.css +35 -119
- package/src/client/settings-card.tsx +31 -153
- package/src/client/settings-fields.tsx +131 -0
- package/src/client/sidebar/SidebarTab.tsx +156 -0
- package/src/client/sidebar/model.ts +65 -0
- package/src/client/sidebar/sidebar-tab.module.css +118 -0
- package/src/client/sidebar-tab.tsx +46 -320
- package/src/client/theme/agsc.module.css +31 -0
- package/src/client/theme/parts.ts +56 -0
- package/src/client/ui-integration.ts +86 -0
- package/src/client/widget.tsx +42 -16
- package/src/client/inject/overlay.module.css +0 -22
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Modal } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
2
|
+
import { useRef } from 'react'
|
|
3
|
+
import type { ReactElement, ReactNode } from 'react'
|
|
4
|
+
import { surfaceProps } from '../theme/parts.ts'
|
|
5
|
+
import css from './center-overlay.module.css'
|
|
6
|
+
import { useModalIsolation } from './modal-isolation.ts'
|
|
7
|
+
import { useModalSurfaceAnchor } from './modal-surface-anchor.ts'
|
|
8
|
+
|
|
9
|
+
export interface CenterOverlayProps {
|
|
10
|
+
open: boolean
|
|
11
|
+
onClose: () => void
|
|
12
|
+
title: string
|
|
13
|
+
closeLabel: string
|
|
14
|
+
children?: ReactNode
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Pure Agent Center presentation over the shell's frame-wide overlay seat. */
|
|
18
|
+
export function CenterOverlay(props: CenterOverlayProps): ReactElement {
|
|
19
|
+
const surfaceRef = useRef<HTMLDivElement>(null)
|
|
20
|
+
const surface = surfaceProps('overlay', css['dialog'])
|
|
21
|
+
useModalIsolation(props.open, surfaceRef)
|
|
22
|
+
useModalSurfaceAnchor(props.open, surfaceRef, surface)
|
|
23
|
+
return (
|
|
24
|
+
<Modal
|
|
25
|
+
open={props.open}
|
|
26
|
+
onClose={props.onClose}
|
|
27
|
+
title={props.title}
|
|
28
|
+
closeLabel={props.closeLabel}
|
|
29
|
+
className={surface.className}
|
|
30
|
+
contentClassName={css['content']}
|
|
31
|
+
>
|
|
32
|
+
<div
|
|
33
|
+
ref={surfaceRef}
|
|
34
|
+
className={css['surface']}
|
|
35
|
+
>
|
|
36
|
+
{props.children}
|
|
37
|
+
</div>
|
|
38
|
+
</Modal>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
.dialog {
|
|
2
|
+
gap: 0;
|
|
3
|
+
width: min(1180px, calc(100vw - 48px));
|
|
4
|
+
height: min(860px, calc(100vh - 48px));
|
|
5
|
+
max-height: calc(100vh - 48px);
|
|
6
|
+
padding: 0;
|
|
7
|
+
border-color: var(--agsc-border-strong);
|
|
8
|
+
border-radius: var(--agsc-radius-card);
|
|
9
|
+
background: var(--agsc-bg);
|
|
10
|
+
box-shadow: var(--agsc-shadow-card);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.content {
|
|
14
|
+
min-height: 0;
|
|
15
|
+
height: 100%;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.content > :last-child {
|
|
20
|
+
flex: 1;
|
|
21
|
+
min-height: 0;
|
|
22
|
+
margin-top: 0;
|
|
23
|
+
padding: 0;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.surface {
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
min-width: 0;
|
|
31
|
+
min-height: 0;
|
|
32
|
+
height: 100%;
|
|
33
|
+
color: var(--agsc-fg);
|
|
34
|
+
background: var(--agsc-bg);
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.surface > :last-child {
|
|
39
|
+
flex: 1;
|
|
40
|
+
min-height: 0;
|
|
41
|
+
height: auto;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@media (max-width: 720px) {
|
|
45
|
+
.dialog {
|
|
46
|
+
width: calc(100vw - 16px);
|
|
47
|
+
height: calc(100dvh - 16px);
|
|
48
|
+
max-height: calc(100dvh - 16px);
|
|
49
|
+
border-radius: var(--agsc-radius-control);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Callback port used by UI entry points that open Agent Center. */
|
|
2
|
+
export type CenterNavigation = () => boolean
|
|
3
|
+
|
|
4
|
+
/** Observable state port bound to the shell overlay at the composition root. */
|
|
5
|
+
export interface CenterNavigationStore {
|
|
6
|
+
readonly open: CenterNavigation
|
|
7
|
+
close(): void
|
|
8
|
+
subscribe(listener: () => void): () => void
|
|
9
|
+
getSnapshot(): boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create one DOM-free navigation source for every Agent Center entry point.
|
|
14
|
+
* Opening is always accepted; a shell overlay that mounts later observes the
|
|
15
|
+
* retained snapshot instead of losing the request.
|
|
16
|
+
*/
|
|
17
|
+
export function createCenterNavigation(): CenterNavigationStore {
|
|
18
|
+
let isOpen = false
|
|
19
|
+
const listeners = new Set<() => void>()
|
|
20
|
+
const notify = (): void => {
|
|
21
|
+
for (const listener of [...listeners]) listener()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const open: CenterNavigation = () => {
|
|
25
|
+
if (!isOpen) {
|
|
26
|
+
isOpen = true
|
|
27
|
+
notify()
|
|
28
|
+
}
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
open,
|
|
34
|
+
close: () => {
|
|
35
|
+
if (!isOpen) return
|
|
36
|
+
isOpen = false
|
|
37
|
+
notify()
|
|
38
|
+
},
|
|
39
|
+
subscribe: (listener) => {
|
|
40
|
+
listeners.add(listener)
|
|
41
|
+
return () => { listeners.delete(listener) }
|
|
42
|
+
},
|
|
43
|
+
getSnapshot: () => isOpen,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
import type { RefObject } from 'react'
|
|
3
|
+
const DIALOG_SELECTOR = '[role="dialog"][aria-modal="true"]'
|
|
4
|
+
const FOCUSABLE_SELECTOR = [
|
|
5
|
+
'a[href],area[href],button:not([disabled])',
|
|
6
|
+
'input:not([disabled]):not([type="hidden"]),select:not([disabled])',
|
|
7
|
+
'textarea:not([disabled]),iframe,[contenteditable="true"],[tabindex]',
|
|
8
|
+
].join(',')
|
|
9
|
+
function focusableElements(dialog: HTMLElement): HTMLElement[] {
|
|
10
|
+
return Array.from(dialog.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR))
|
|
11
|
+
.filter((element) =>
|
|
12
|
+
element.tabIndex >= 0 &&
|
|
13
|
+
!element.hidden &&
|
|
14
|
+
element.closest('[inert],[aria-hidden="true"]') === null &&
|
|
15
|
+
element.getClientRects().length > 0)
|
|
16
|
+
}
|
|
17
|
+
function restorableIn(element: HTMLElement | null, dialog: HTMLElement): element is HTMLElement {
|
|
18
|
+
return element?.isConnected === true &&
|
|
19
|
+
dialog.contains(element) &&
|
|
20
|
+
!element.hidden &&
|
|
21
|
+
element.closest('[inert],[aria-hidden="true"]') === null &&
|
|
22
|
+
element.getClientRects().length > 0
|
|
23
|
+
}
|
|
24
|
+
type DialogFrame = { dialog: HTMLElement; opener: HTMLElement | null; focus: HTMLElement | null }
|
|
25
|
+
/** Isolate the active official Modal, including sibling-portaled nested dialogs. */
|
|
26
|
+
export function useModalIsolation(
|
|
27
|
+
open: boolean,
|
|
28
|
+
surfaceRef: RefObject<HTMLElement>,
|
|
29
|
+
): void {
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!open || typeof document === 'undefined' || typeof window === 'undefined') return
|
|
32
|
+
const body = document.body
|
|
33
|
+
if (body === null || typeof window.MutationObserver === 'undefined') return
|
|
34
|
+
const previousFocus =
|
|
35
|
+
document.activeElement instanceof HTMLElement ? document.activeElement : null
|
|
36
|
+
const originalInert = new Map<HTMLElement, boolean>()
|
|
37
|
+
const dialogStack: DialogFrame[] = []
|
|
38
|
+
let topDialog: HTMLElement | null = null
|
|
39
|
+
let focusFrame: number | null = null
|
|
40
|
+
const getTopDialog = (): HTMLElement | null => {
|
|
41
|
+
const outer = surfaceRef.current?.closest<HTMLElement>(DIALOG_SELECTOR) ?? null
|
|
42
|
+
if (outer === null) return null
|
|
43
|
+
const dialogs = Array.from(body.querySelectorAll<HTMLElement>(DIALOG_SELECTOR))
|
|
44
|
+
const outerIndex = dialogs.indexOf(outer)
|
|
45
|
+
return outerIndex < 0 ? null : dialogs.at(-1) ?? null
|
|
46
|
+
}
|
|
47
|
+
const bodyBranch = (dialog: HTMLElement): HTMLElement | null => {
|
|
48
|
+
let branch: Node = dialog
|
|
49
|
+
while (branch.parentNode !== null && branch.parentNode !== body) {
|
|
50
|
+
branch = branch.parentNode
|
|
51
|
+
}
|
|
52
|
+
return branch instanceof HTMLElement ? branch : null
|
|
53
|
+
}
|
|
54
|
+
const isolateAround = (dialog: HTMLElement | null): void => {
|
|
55
|
+
const activeBranch = dialog === null ? null : bodyBranch(dialog)
|
|
56
|
+
const next = new Set(
|
|
57
|
+
Array.from(body.children)
|
|
58
|
+
.filter((element): element is HTMLElement =>
|
|
59
|
+
element instanceof HTMLElement && element !== activeBranch),
|
|
60
|
+
)
|
|
61
|
+
for (const [element, wasInert] of originalInert) {
|
|
62
|
+
if (next.has(element)) continue
|
|
63
|
+
element.inert = wasInert
|
|
64
|
+
originalInert.delete(element)
|
|
65
|
+
}
|
|
66
|
+
for (const element of next) {
|
|
67
|
+
if (!originalInert.has(element)) originalInert.set(element, element.inert)
|
|
68
|
+
element.inert = true
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const queueFocus = (dialog: HTMLElement, preferred: HTMLElement | null = null): void => {
|
|
72
|
+
if (focusFrame !== null) window.cancelAnimationFrame(focusFrame)
|
|
73
|
+
focusFrame = window.requestAnimationFrame(() => {
|
|
74
|
+
focusFrame = null
|
|
75
|
+
if (getTopDialog() !== dialog) return
|
|
76
|
+
const target = restorableIn(preferred, dialog)
|
|
77
|
+
? preferred
|
|
78
|
+
: focusableElements(dialog)[0]
|
|
79
|
+
target?.focus({ preventScroll: true })
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
const sync = (): void => {
|
|
83
|
+
const next = getTopDialog()
|
|
84
|
+
const changed = next !== topDialog
|
|
85
|
+
let closed: DialogFrame[] = []
|
|
86
|
+
if (changed && next !== null) {
|
|
87
|
+
const index = dialogStack.findIndex(frame => frame.dialog === next)
|
|
88
|
+
if (index < 0) {
|
|
89
|
+
const parent = dialogStack.at(-1)
|
|
90
|
+
const active =
|
|
91
|
+
document.activeElement instanceof HTMLElement ? document.activeElement : null
|
|
92
|
+
const opener = parent !== undefined && restorableIn(active, parent.dialog)
|
|
93
|
+
? active
|
|
94
|
+
: parent?.focus ?? null
|
|
95
|
+
dialogStack.push({ dialog: next, opener, focus: null })
|
|
96
|
+
} else {
|
|
97
|
+
closed = dialogStack.splice(index + 1)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
topDialog = next
|
|
101
|
+
isolateAround(next)
|
|
102
|
+
const opener = next === null
|
|
103
|
+
? null
|
|
104
|
+
: closed.reverse().find(frame => restorableIn(frame.opener, next))?.opener ?? null
|
|
105
|
+
if (
|
|
106
|
+
next !== null &&
|
|
107
|
+
(changed || !next.contains(document.activeElement))
|
|
108
|
+
) {
|
|
109
|
+
queueFocus(next, opener)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const onFocusIn = (event: FocusEvent): void => {
|
|
113
|
+
if (!(event.target instanceof HTMLElement)) return
|
|
114
|
+
const dialog = event.target.closest<HTMLElement>(DIALOG_SELECTOR)
|
|
115
|
+
const frame = dialogStack.find(item => item.dialog === dialog)
|
|
116
|
+
if (frame !== undefined) frame.focus = event.target
|
|
117
|
+
}
|
|
118
|
+
const onKeyDown = (event: KeyboardEvent): void => {
|
|
119
|
+
if (event.key !== 'Tab' || event.defaultPrevented) return
|
|
120
|
+
const dialog = getTopDialog()
|
|
121
|
+
if (dialog === null) return
|
|
122
|
+
if (dialog !== topDialog) sync()
|
|
123
|
+
|
|
124
|
+
const focusable = focusableElements(dialog)
|
|
125
|
+
if (focusable.length === 0) {
|
|
126
|
+
event.preventDefault()
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
const activeIndex = focusable.indexOf(document.activeElement as HTMLElement)
|
|
130
|
+
const wrapsBackward = event.shiftKey && activeIndex <= 0
|
|
131
|
+
const wrapsForward = !event.shiftKey && activeIndex === focusable.length - 1
|
|
132
|
+
if (activeIndex < 0 || wrapsBackward || wrapsForward) {
|
|
133
|
+
event.preventDefault()
|
|
134
|
+
const target = event.shiftKey ? focusable.at(-1) : focusable[0]
|
|
135
|
+
target?.focus({ preventScroll: true })
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const observer = new window.MutationObserver(sync)
|
|
139
|
+
observer.observe(body, { childList: true, subtree: true })
|
|
140
|
+
document.addEventListener('focusin', onFocusIn)
|
|
141
|
+
document.addEventListener('keydown', onKeyDown, true)
|
|
142
|
+
sync()
|
|
143
|
+
return () => {
|
|
144
|
+
observer.disconnect()
|
|
145
|
+
document.removeEventListener('focusin', onFocusIn)
|
|
146
|
+
document.removeEventListener('keydown', onKeyDown, true)
|
|
147
|
+
if (focusFrame !== null) window.cancelAnimationFrame(focusFrame)
|
|
148
|
+
for (const [element, wasInert] of originalInert) element.inert = wasInert
|
|
149
|
+
if (previousFocus?.isConnected === true) previousFocus.focus({ preventScroll: true })
|
|
150
|
+
}
|
|
151
|
+
}, [open, surfaceRef])
|
|
152
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect } from 'react'
|
|
2
|
+
import type { RefObject } from 'react'
|
|
3
|
+
import type { SurfaceProps } from '../theme/parts.ts'
|
|
4
|
+
|
|
5
|
+
const DIALOG_SELECTOR = '[role="dialog"][aria-modal="true"]'
|
|
6
|
+
const MODAL_SURFACE_OWNER = Symbol.for(
|
|
7
|
+
'@shendeguize/dsh-agent-sidecar/modal-surface-anchor-owner',
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
type ModalSurfaceAttributes = Pick<
|
|
11
|
+
SurfaceProps,
|
|
12
|
+
'data-dsh-plugin' | 'data-dsh-part'
|
|
13
|
+
>
|
|
14
|
+
|
|
15
|
+
export interface ModalSurfaceAnchorTarget {
|
|
16
|
+
setAttribute(name: string, value: string): void
|
|
17
|
+
removeAttribute(name: string): void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Use a pre-paint effect in the browser without warning during SSR. */
|
|
21
|
+
export function selectModalSurfaceAnchorEffect(
|
|
22
|
+
hasDocument: boolean,
|
|
23
|
+
): typeof useEffect {
|
|
24
|
+
return hasDocument ? useLayoutEffect : useEffect
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const useIsomorphicLayoutEffect = selectModalSurfaceAnchorEffect(
|
|
28
|
+
typeof document !== 'undefined',
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
function ownerSlot(target: ModalSurfaceAnchorTarget): Record<PropertyKey, unknown> {
|
|
32
|
+
return target as unknown as Record<PropertyKey, unknown>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Attach the public surface attributes with latest-owner-safe cleanup. */
|
|
36
|
+
export function attachModalSurfaceAnchor(
|
|
37
|
+
target: ModalSurfaceAnchorTarget | null,
|
|
38
|
+
attributes: ModalSurfaceAttributes,
|
|
39
|
+
): () => void {
|
|
40
|
+
if (target === null) return () => {}
|
|
41
|
+
|
|
42
|
+
const owner = Symbol('modal-surface-anchor')
|
|
43
|
+
const slot = ownerSlot(target)
|
|
44
|
+
slot[MODAL_SURFACE_OWNER] = owner
|
|
45
|
+
target.setAttribute('data-dsh-plugin', attributes['data-dsh-plugin'])
|
|
46
|
+
target.setAttribute('data-dsh-part', attributes['data-dsh-part'])
|
|
47
|
+
|
|
48
|
+
return () => {
|
|
49
|
+
if (slot[MODAL_SURFACE_OWNER] !== owner) return
|
|
50
|
+
target.removeAttribute('data-dsh-plugin')
|
|
51
|
+
target.removeAttribute('data-dsh-part')
|
|
52
|
+
delete slot[MODAL_SURFACE_OWNER]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Commit the public anchor onto the official Modal's real dialog element. */
|
|
57
|
+
export function useModalSurfaceAnchor(
|
|
58
|
+
open: boolean,
|
|
59
|
+
surfaceRef: RefObject<HTMLElement>,
|
|
60
|
+
attributes: ModalSurfaceAttributes,
|
|
61
|
+
): void {
|
|
62
|
+
useIsomorphicLayoutEffect(() => {
|
|
63
|
+
if (!open || typeof document === 'undefined') return
|
|
64
|
+
const dialog = surfaceRef.current?.closest<HTMLElement>(DIALOG_SELECTOR) ?? null
|
|
65
|
+
return attachModalSurfaceAnchor(dialog, attributes)
|
|
66
|
+
}, [
|
|
67
|
+
open,
|
|
68
|
+
surfaceRef,
|
|
69
|
+
attributes['data-dsh-plugin'],
|
|
70
|
+
attributes['data-dsh-part'],
|
|
71
|
+
])
|
|
72
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
.entry {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: 8px;
|
|
6
|
+
width: 100%;
|
|
7
|
+
height: 36px;
|
|
8
|
+
padding: 0 10px;
|
|
9
|
+
border: 0;
|
|
10
|
+
border-radius: var(--agsc-radius-control);
|
|
11
|
+
background: none;
|
|
12
|
+
color: var(--agsc-fg-secondary);
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
font: inherit;
|
|
15
|
+
font-size: 13px;
|
|
16
|
+
text-align: left;
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
transition: background-color 120ms ease, color 120ms ease, transform 120ms ease;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.entry:hover {
|
|
22
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
23
|
+
color: var(--agsc-fg);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.entry:active {
|
|
27
|
+
background: var(--dsw-alias-interactive-bg-active);
|
|
28
|
+
color: var(--agsc-fg);
|
|
29
|
+
transform: translateY(1px);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.entry:focus-visible {
|
|
33
|
+
outline: 2px solid var(--agsc-accent);
|
|
34
|
+
outline-offset: 2px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.entryIcon {
|
|
38
|
+
display: inline-flex;
|
|
39
|
+
flex: none;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: center;
|
|
42
|
+
width: 24px;
|
|
43
|
+
height: 24px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.entryIcon svg {
|
|
47
|
+
display: block;
|
|
48
|
+
width: 16px;
|
|
49
|
+
height: 16px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.entryLabel {
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
text-overflow: ellipsis;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
[data-dsh-frame][data-sidebar-collapsed] .entry {
|
|
58
|
+
justify-content: center;
|
|
59
|
+
width: 36px;
|
|
60
|
+
padding: 0;
|
|
61
|
+
margin: 0 auto 12px;
|
|
62
|
+
border-radius: 50%;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
[data-dsh-frame][data-sidebar-collapsed] .entryLabel {
|
|
66
|
+
display: none;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@media (prefers-reduced-motion: reduce) {
|
|
70
|
+
.entry {
|
|
71
|
+
transition: none;
|
|
72
|
+
}
|
|
73
|
+
}
|