@linxin666/dsh-client-ui-task-board 0.3.11 → 0.3.13
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/lib/client.js +68 -43
- package/lib/client.js.map +1 -1
- package/lib/types/client/board-mount.d.ts +10 -0
- package/lib/types/client/panel-mount-core.d.ts +56 -0
- package/package.json +19 -19
- package/src/client/board-mount.tsx +22 -104
- package/src/client/panel-mount-core.ts +147 -0
- package/src/client/settings-card.module.css +14 -8
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Board view mounting (dsh-task-board wrapper).
|
|
3
|
+
*
|
|
4
|
+
* The single-occupant takeover lifecycle — container injection into the
|
|
5
|
+
* center column, sibling eviction, remount resilience, sidebar click-out —
|
|
6
|
+
* lives exactly once in shared/client/panel-mount-core.ts (synced copy); this
|
|
7
|
+
* wrapper supplies the board tree, view dataset key, CSS module, and the html
|
|
8
|
+
* attribute names. Those names are pinned by board.module.css, skins, and the
|
|
9
|
+
* semantic attributes contract.
|
|
10
|
+
*/
|
|
1
11
|
import type { BoardController } from '../core/controller.ts';
|
|
2
12
|
import type { LocaleRefreshSource } from './sidebar-entry.ts';
|
|
3
13
|
/** The injected board container (kept in the DOM, hidden when inactive). */
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Center-column panel takeover lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* The `conversation` slot is single-occupant (ui-conversation) and external
|
|
5
|
+
* plugins cannot declare slots, so a family panel takes over the center
|
|
6
|
+
* column at the DOM level: a container is appended inside the center column
|
|
7
|
+
* (`[class*="centerCol"]`, the 0.1.0-rc.6+ AppFrame layout; previously
|
|
8
|
+
* `[data-pane="conversation"]` on older shells — the mount selector keeps
|
|
9
|
+
* both, ssh #243 / task-board #107) as an extra trailing child React never
|
|
10
|
+
* manages, and a stylesheet rule hides the conversation content while the
|
|
11
|
+
* panel is active. Toggling is a data attribute on <html> — no React
|
|
12
|
+
* involvement, so the conversation subtree underneath stays mounted and
|
|
13
|
+
* stateful.
|
|
14
|
+
*
|
|
15
|
+
* Consuming plugins keep a thin wrapper that supplies the panel tree,
|
|
16
|
+
* container attribute names, and stylesheet class; those names are pinned by
|
|
17
|
+
* each package's CSS, skins, and the semantic-attributes contract. The
|
|
18
|
+
* sidebar row toggling the panel shares its core the same way
|
|
19
|
+
* (shared/client/sidebar-entry-core.ts, synced copy).
|
|
20
|
+
*/
|
|
21
|
+
import { type Root } from 'react-dom/client';
|
|
22
|
+
/** Options for mountCenterPanel; dsh-ssh mount.tsx and dsh-task-board board-mount.tsx are the canonical consumers. */
|
|
23
|
+
export interface CenterPanelMountOptions {
|
|
24
|
+
/** Render the panel React tree into a root (initial mount, remount, locale refresh). */
|
|
25
|
+
render: (root: Root) => void;
|
|
26
|
+
/** dataset key of the injected container's view attribute, e.g. `dshSshView` for `data-dsh-ssh-view`. */
|
|
27
|
+
viewDatasetKey: string;
|
|
28
|
+
/** value of the container's L2 `data-dsh-plugin` semantic attribute. */
|
|
29
|
+
pluginName: string;
|
|
30
|
+
/** stylesheet class applied to the injected container. */
|
|
31
|
+
viewClassName: string;
|
|
32
|
+
/** <html> attribute set while this panel is active. */
|
|
33
|
+
activeAttribute: string;
|
|
34
|
+
/** the sibling panel's active attribute, removed from <html> when this panel opens. */
|
|
35
|
+
siblingActiveAttribute: string;
|
|
36
|
+
/** detail value this panel broadcasts on the cross-plugin activation event. */
|
|
37
|
+
panelName: string;
|
|
38
|
+
/** sibling detail value whose activation closes this panel. */
|
|
39
|
+
siblingPanelName: string;
|
|
40
|
+
/** open flag of the owning controller. */
|
|
41
|
+
isOpen: () => boolean;
|
|
42
|
+
/** close the panel, handing the center column back to the conversation. */
|
|
43
|
+
close: () => void;
|
|
44
|
+
/** subscribe to the owning controller's open-state changes; returns an unsubscriber. */
|
|
45
|
+
subscribe: (listener: () => void) => () => void;
|
|
46
|
+
/** locale-change source; when given, re-renders an open panel on a Language switch. */
|
|
47
|
+
locale?: {
|
|
48
|
+
subscribe(listener: () => void): () => void;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Mount a family panel into the center column and bind its visibility to the
|
|
53
|
+
* owning controller's open state.
|
|
54
|
+
* @returns disposer unmounting the tree and restoring the column.
|
|
55
|
+
*/
|
|
56
|
+
export declare function mountCenterPanel(options: CenterPanelMountOptions): () => void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-client-ui-task-board",
|
|
3
3
|
"description": "Host-authoritative task board for the DSH Web GUI with real session execution, Host cron scheduling, and optional cross-platform idle-sleep protection; mounted without DSH source changes.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.13",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dsh": {
|
|
25
25
|
"engines": {
|
|
26
|
-
"dsh": ">=0.1.2-alpha.
|
|
26
|
+
"dsh": ">=0.1.2-alpha.4"
|
|
27
27
|
},
|
|
28
28
|
"bundle": {
|
|
29
29
|
"patch": "./cordis.patch.yml"
|
|
@@ -48,23 +48,23 @@
|
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
51
|
-
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.
|
|
52
|
-
"@deepseek-ai/dsh-agent-presets": "^0.1.2-alpha.
|
|
53
|
-
"@deepseek-ai/dsh-api-gateway": "^0.1.2-alpha.
|
|
54
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.2-alpha.
|
|
55
|
-
"@deepseek-ai/dsh-workspace": "^0.1.2-alpha.
|
|
56
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.2-alpha.
|
|
57
|
-
"@deepseek-ai/dsh-api-workspace-controller": "^0.1.2-alpha.
|
|
58
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.
|
|
59
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.
|
|
60
|
-
"@deepseek-ai/dsh-client-store": "^0.1.2-alpha.
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.
|
|
64
|
-
"@deepseek-ai/dsh-commands": "^0.1.2-alpha.
|
|
65
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.
|
|
66
|
-
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.
|
|
67
|
-
"@deepseek-ai/dsh-system-prompt": "^0.1.2-alpha.
|
|
51
|
+
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.4",
|
|
52
|
+
"@deepseek-ai/dsh-agent-presets": "^0.1.2-alpha.4",
|
|
53
|
+
"@deepseek-ai/dsh-api-gateway": "^0.1.2-alpha.4",
|
|
54
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.2-alpha.4",
|
|
55
|
+
"@deepseek-ai/dsh-workspace": "^0.1.2-alpha.4",
|
|
56
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.2-alpha.4",
|
|
57
|
+
"@deepseek-ai/dsh-api-workspace-controller": "^0.1.2-alpha.4",
|
|
58
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.4",
|
|
59
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.4",
|
|
60
|
+
"@deepseek-ai/dsh-client-store": "^0.1.2-alpha.4",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.4",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.4",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.4",
|
|
64
|
+
"@deepseek-ai/dsh-commands": "^0.1.2-alpha.4",
|
|
65
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.4",
|
|
66
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.4",
|
|
67
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.2-alpha.4",
|
|
68
68
|
"@types/node": "^22.20.0",
|
|
69
69
|
"@types/react": "~18.3.1",
|
|
70
70
|
"@types/react-dom": "^18.3.5",
|
|
@@ -1,39 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Board view mounting.
|
|
2
|
+
* Board view mounting (dsh-task-board wrapper).
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* React never manages, and a stylesheet
|
|
11
|
-
* rule hides the conversation content while the board is active. Toggling is
|
|
12
|
-
* a data attribute on <html> — no React involvement, so the conversation
|
|
13
|
-
* subtree underneath stays mounted and stateful.
|
|
4
|
+
* The single-occupant takeover lifecycle — container injection into the
|
|
5
|
+
* center column, sibling eviction, remount resilience, sidebar click-out —
|
|
6
|
+
* lives exactly once in shared/client/panel-mount-core.ts (synced copy); this
|
|
7
|
+
* wrapper supplies the board tree, view dataset key, CSS module, and the html
|
|
8
|
+
* attribute names. Those names are pinned by board.module.css, skins, and the
|
|
9
|
+
* semantic attributes contract.
|
|
14
10
|
*/
|
|
15
|
-
import { createRoot, type Root } from 'react-dom/client'
|
|
16
11
|
import type { BoardController } from '../core/controller.ts'
|
|
17
12
|
import { TaskBoard } from './board/TaskBoard.tsx'
|
|
13
|
+
import { mountCenterPanel } from './panel-mount-core.ts'
|
|
18
14
|
import type { LocaleRefreshSource } from './sidebar-entry.ts'
|
|
19
15
|
import css from './board.module.css'
|
|
20
16
|
|
|
21
17
|
/** The injected board container (kept in the DOM, hidden when inactive). */
|
|
22
18
|
export const BOARD_VIEW_SELECTOR = '[data-dsh-taskboard-view]'
|
|
23
19
|
|
|
24
|
-
const CONVERSATION_COLUMN_SELECTOR = '[data-pane="conversation"], [class*="centerCol"]'
|
|
25
|
-
const ACTIVE_ATTR = 'data-dsh-taskboard-active'
|
|
26
|
-
/** The sibling panel's activation attribute (ssh), removed when this panel opens. */
|
|
27
|
-
const OTHER_ACTIVE_ATTR = 'data-dsh-ssh-active'
|
|
28
|
-
/** Cross-plugin activation event; detail is the activating panel name. */
|
|
29
|
-
const ACTIVATE_EVENT = 'dsh-panel-activate'
|
|
30
|
-
const PANEL_NAME = 'taskboard'
|
|
31
|
-
|
|
32
|
-
/** Find the center column, or undefined while the frame is not mounted. */
|
|
33
|
-
function conversationColumn(): HTMLElement | undefined {
|
|
34
|
-
return document.querySelector<HTMLElement>(CONVERSATION_COLUMN_SELECTOR) ?? undefined
|
|
35
|
-
}
|
|
36
|
-
|
|
37
20
|
/**
|
|
38
21
|
* Mount the board React tree into the center column and bind its visibility
|
|
39
22
|
* to the controller's boardOpen state.
|
|
@@ -43,83 +26,18 @@ function conversationColumn(): HTMLElement | undefined {
|
|
|
43
26
|
* @returns disposer unmounting the tree and restoring the column.
|
|
44
27
|
*/
|
|
45
28
|
export function mountBoard(controller: BoardController, locale?: LocaleRefreshSource): () => void {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
container.remove()
|
|
61
|
-
container = undefined
|
|
62
|
-
}
|
|
63
|
-
const column = conversationColumn()
|
|
64
|
-
if (column === undefined) return
|
|
65
|
-
container = document.createElement('div')
|
|
66
|
-
container.dataset.dshTaskboardView = ''
|
|
67
|
-
container.dataset.dshPlugin = 'task-board'
|
|
68
|
-
container.className = css.boardView
|
|
69
|
-
column.appendChild(container)
|
|
70
|
-
root = createRoot(container)
|
|
71
|
-
root.render(<TaskBoard controller={controller} />)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// The frame mounts after boot settlement; watch for the column's arrival.
|
|
75
|
-
const waitObserver = new MutationObserver(() => { ensure() })
|
|
76
|
-
waitObserver.observe(document.body, { childList: true, subtree: true })
|
|
77
|
-
|
|
78
|
-
const applyActive = (): void => {
|
|
79
|
-
if (controller.getSnapshot().boardOpen) {
|
|
80
|
-
// Single-occupant center column: opening this panel must evict the
|
|
81
|
-
// sibling panel (ssh), both its html attribute and its controller
|
|
82
|
-
// state, otherwise the two panels' visibility rules fight and the
|
|
83
|
-
// second click appears dead.
|
|
84
|
-
document.documentElement.removeAttribute(OTHER_ACTIVE_ATTR)
|
|
85
|
-
document.documentElement.setAttribute(ACTIVE_ATTR, '')
|
|
86
|
-
document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }))
|
|
87
|
-
} else {
|
|
88
|
-
document.documentElement.removeAttribute(ACTIVE_ATTR)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const onOtherActivate = (event: Event): void => {
|
|
92
|
-
if ((event as CustomEvent).detail === 'ssh' && controller.getSnapshot().boardOpen) {
|
|
93
|
-
controller.closeBoard()
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
// Jump out on sidebar context clicks: clicking a session/workspace row
|
|
97
|
-
// (including the already-current one, which produces no session-change
|
|
98
|
-
// event) hands the center column back to the conversation. Capture phase,
|
|
99
|
-
// so the panel closes before the shell processes the click.
|
|
100
|
-
const SIDEBAR_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [class*="newSession"]'
|
|
101
|
-
const onClickSidebarRow = (event: MouseEvent): void => {
|
|
102
|
-
if (!controller.getSnapshot().boardOpen) return
|
|
103
|
-
const target = event.target as HTMLElement | null
|
|
104
|
-
if (target === null) return
|
|
105
|
-
if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.closeBoard()
|
|
106
|
-
}
|
|
107
|
-
document.addEventListener('click', onClickSidebarRow, true)
|
|
108
|
-
document.addEventListener(ACTIVATE_EVENT, onOtherActivate)
|
|
109
|
-
const unsubscribe = controller.subscribe(applyActive)
|
|
110
|
-
applyActive()
|
|
111
|
-
ensure()
|
|
112
|
-
|
|
113
|
-
return () => {
|
|
114
|
-
document.removeEventListener('click', onClickSidebarRow, true)
|
|
115
|
-
document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)
|
|
116
|
-
waitObserver.disconnect()
|
|
117
|
-
unsubscribe()
|
|
118
|
-
unsubscribeLocale?.()
|
|
119
|
-
document.documentElement.removeAttribute(ACTIVE_ATTR)
|
|
120
|
-
root?.unmount()
|
|
121
|
-
root = undefined
|
|
122
|
-
container?.remove()
|
|
123
|
-
container = undefined
|
|
124
|
-
}
|
|
29
|
+
return mountCenterPanel({
|
|
30
|
+
render: root => root.render(<TaskBoard controller={controller} />),
|
|
31
|
+
viewDatasetKey: 'dshTaskboardView',
|
|
32
|
+
pluginName: 'task-board',
|
|
33
|
+
viewClassName: css.boardView,
|
|
34
|
+
activeAttribute: 'data-dsh-taskboard-active',
|
|
35
|
+
siblingActiveAttribute: 'data-dsh-ssh-active',
|
|
36
|
+
panelName: 'taskboard',
|
|
37
|
+
siblingPanelName: 'ssh',
|
|
38
|
+
isOpen: () => controller.getSnapshot().boardOpen,
|
|
39
|
+
close: () => controller.closeBoard(),
|
|
40
|
+
subscribe: listener => controller.subscribe(listener),
|
|
41
|
+
locale,
|
|
42
|
+
})
|
|
125
43
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Generated by scripts/sync-shared.mjs from shared/client/panel-mount-core.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
|
|
2
|
+
/**
|
|
3
|
+
* Center-column panel takeover lifecycle.
|
|
4
|
+
*
|
|
5
|
+
* The `conversation` slot is single-occupant (ui-conversation) and external
|
|
6
|
+
* plugins cannot declare slots, so a family panel takes over the center
|
|
7
|
+
* column at the DOM level: a container is appended inside the center column
|
|
8
|
+
* (`[class*="centerCol"]`, the 0.1.0-rc.6+ AppFrame layout; previously
|
|
9
|
+
* `[data-pane="conversation"]` on older shells — the mount selector keeps
|
|
10
|
+
* both, ssh #243 / task-board #107) as an extra trailing child React never
|
|
11
|
+
* manages, and a stylesheet rule hides the conversation content while the
|
|
12
|
+
* panel is active. Toggling is a data attribute on <html> — no React
|
|
13
|
+
* involvement, so the conversation subtree underneath stays mounted and
|
|
14
|
+
* stateful.
|
|
15
|
+
*
|
|
16
|
+
* Consuming plugins keep a thin wrapper that supplies the panel tree,
|
|
17
|
+
* container attribute names, and stylesheet class; those names are pinned by
|
|
18
|
+
* each package's CSS, skins, and the semantic-attributes contract. The
|
|
19
|
+
* sidebar row toggling the panel shares its core the same way
|
|
20
|
+
* (shared/client/sidebar-entry-core.ts, synced copy).
|
|
21
|
+
*/
|
|
22
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
23
|
+
|
|
24
|
+
/** Options for mountCenterPanel; dsh-ssh mount.tsx and dsh-task-board board-mount.tsx are the canonical consumers. */
|
|
25
|
+
export interface CenterPanelMountOptions {
|
|
26
|
+
/** Render the panel React tree into a root (initial mount, remount, locale refresh). */
|
|
27
|
+
render: (root: Root) => void
|
|
28
|
+
/** dataset key of the injected container's view attribute, e.g. `dshSshView` for `data-dsh-ssh-view`. */
|
|
29
|
+
viewDatasetKey: string
|
|
30
|
+
/** value of the container's L2 `data-dsh-plugin` semantic attribute. */
|
|
31
|
+
pluginName: string
|
|
32
|
+
/** stylesheet class applied to the injected container. */
|
|
33
|
+
viewClassName: string
|
|
34
|
+
/** <html> attribute set while this panel is active. */
|
|
35
|
+
activeAttribute: string
|
|
36
|
+
/** the sibling panel's active attribute, removed from <html> when this panel opens. */
|
|
37
|
+
siblingActiveAttribute: string
|
|
38
|
+
/** detail value this panel broadcasts on the cross-plugin activation event. */
|
|
39
|
+
panelName: string
|
|
40
|
+
/** sibling detail value whose activation closes this panel. */
|
|
41
|
+
siblingPanelName: string
|
|
42
|
+
/** open flag of the owning controller. */
|
|
43
|
+
isOpen: () => boolean
|
|
44
|
+
/** close the panel, handing the center column back to the conversation. */
|
|
45
|
+
close: () => void
|
|
46
|
+
/** subscribe to the owning controller's open-state changes; returns an unsubscriber. */
|
|
47
|
+
subscribe: (listener: () => void) => () => void
|
|
48
|
+
/** locale-change source; when given, re-renders an open panel on a Language switch. */
|
|
49
|
+
locale?: { subscribe(listener: () => void): () => void }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const CONVERSATION_COLUMN_SELECTOR = '[data-pane="conversation"], [class*="centerCol"]'
|
|
53
|
+
/** Cross-plugin activation event; detail is the activating panel name. */
|
|
54
|
+
const ACTIVATE_EVENT = 'dsh-panel-activate'
|
|
55
|
+
// Sidebar rows whose clicks hand the center column back to the conversation
|
|
56
|
+
// (including the already-current row, which produces no session-change
|
|
57
|
+
// event). Capture phase, so the panel closes before the shell processes the
|
|
58
|
+
// click.
|
|
59
|
+
const SIDEBAR_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [class*="newSession"]'
|
|
60
|
+
|
|
61
|
+
/** Find the center column, or undefined while the frame is not mounted. */
|
|
62
|
+
function conversationColumn(): HTMLElement | undefined {
|
|
63
|
+
return document.querySelector<HTMLElement>(CONVERSATION_COLUMN_SELECTOR) ?? undefined
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Mount a family panel into the center column and bind its visibility to the
|
|
68
|
+
* owning controller's open state.
|
|
69
|
+
* @returns disposer unmounting the tree and restoring the column.
|
|
70
|
+
*/
|
|
71
|
+
export function mountCenterPanel(options: CenterPanelMountOptions): () => void {
|
|
72
|
+
let root: Root | undefined
|
|
73
|
+
let container: HTMLDivElement | undefined
|
|
74
|
+
let unsubscribeLocale: (() => void) | undefined
|
|
75
|
+
try {
|
|
76
|
+
unsubscribeLocale = options.locale?.subscribe(() => {
|
|
77
|
+
if (root !== undefined) options.render(root)
|
|
78
|
+
})
|
|
79
|
+
} catch { /* locale service absent: the panel follows its next natural re-render */ }
|
|
80
|
+
|
|
81
|
+
const ensure = (): void => {
|
|
82
|
+
if (container !== undefined) {
|
|
83
|
+
if (container.isConnected) return
|
|
84
|
+
// The conversation pane was replaced; drop the stale tree and remount.
|
|
85
|
+
root?.unmount()
|
|
86
|
+
root = undefined
|
|
87
|
+
container.remove()
|
|
88
|
+
container = undefined
|
|
89
|
+
}
|
|
90
|
+
const column = conversationColumn()
|
|
91
|
+
if (column === undefined) return
|
|
92
|
+
container = document.createElement('div')
|
|
93
|
+
container.dataset[options.viewDatasetKey] = ''
|
|
94
|
+
container.dataset.dshPlugin = options.pluginName
|
|
95
|
+
container.className = options.viewClassName
|
|
96
|
+
column.appendChild(container)
|
|
97
|
+
root = createRoot(container)
|
|
98
|
+
options.render(root)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// The frame mounts after boot settlement; watch for the column's arrival.
|
|
102
|
+
const waitObserver = new MutationObserver(() => { ensure() })
|
|
103
|
+
waitObserver.observe(document.body, { childList: true, subtree: true })
|
|
104
|
+
|
|
105
|
+
const applyActive = (): void => {
|
|
106
|
+
if (options.isOpen()) {
|
|
107
|
+
// Single-occupant center column: opening this panel must evict the
|
|
108
|
+
// sibling panel, both its html attribute and its controller state,
|
|
109
|
+
// otherwise the two panels' visibility rules fight and the second
|
|
110
|
+
// click appears dead.
|
|
111
|
+
document.documentElement.removeAttribute(options.siblingActiveAttribute)
|
|
112
|
+
document.documentElement.setAttribute(options.activeAttribute, '')
|
|
113
|
+
document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: options.panelName }))
|
|
114
|
+
} else {
|
|
115
|
+
document.documentElement.removeAttribute(options.activeAttribute)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const onOtherActivate = (event: Event): void => {
|
|
119
|
+
if ((event as CustomEvent).detail === options.siblingPanelName && options.isOpen()) {
|
|
120
|
+
options.close()
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const onClickSidebarRow = (event: MouseEvent): void => {
|
|
124
|
+
if (!options.isOpen()) return
|
|
125
|
+
const target = event.target as HTMLElement | null
|
|
126
|
+
if (target === null) return
|
|
127
|
+
if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) options.close()
|
|
128
|
+
}
|
|
129
|
+
document.addEventListener('click', onClickSidebarRow, true)
|
|
130
|
+
document.addEventListener(ACTIVATE_EVENT, onOtherActivate)
|
|
131
|
+
const unsubscribe = options.subscribe(applyActive)
|
|
132
|
+
applyActive()
|
|
133
|
+
ensure()
|
|
134
|
+
|
|
135
|
+
return () => {
|
|
136
|
+
document.removeEventListener('click', onClickSidebarRow, true)
|
|
137
|
+
document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)
|
|
138
|
+
waitObserver.disconnect()
|
|
139
|
+
unsubscribe()
|
|
140
|
+
unsubscribeLocale?.()
|
|
141
|
+
document.documentElement.removeAttribute(options.activeAttribute)
|
|
142
|
+
root?.unmount()
|
|
143
|
+
root = undefined
|
|
144
|
+
container?.remove()
|
|
145
|
+
container = undefined
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -72,7 +72,10 @@
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
.description {
|
|
75
|
-
|
|
75
|
+
/* Secondary, not tertiary: skins whose tertiary token is tuned for large
|
|
76
|
+
print leave 12-13px hint text below readable contrast (orca-link #778399
|
|
77
|
+
on cream measures ~3.4:1). */
|
|
78
|
+
color: var(--dsw-alias-label-secondary);
|
|
76
79
|
font-size: 13px;
|
|
77
80
|
line-height: 1.5;
|
|
78
81
|
}
|
|
@@ -106,7 +109,7 @@
|
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
.readOnly {
|
|
109
|
-
color: var(--dsw-alias-label-
|
|
112
|
+
color: var(--dsw-alias-label-secondary);
|
|
110
113
|
margin: 12px 0 0;
|
|
111
114
|
font-size: 12px;
|
|
112
115
|
line-height: 1.5;
|
|
@@ -130,7 +133,10 @@
|
|
|
130
133
|
|
|
131
134
|
.failed {
|
|
132
135
|
min-width: 0;
|
|
133
|
-
|
|
136
|
+
/* The suite's error alias is state-error-primary; a label-error token does
|
|
137
|
+
not exist, so the var() would resolve to nothing and the message would
|
|
138
|
+
inherit the ink color, losing the failure signal. */
|
|
139
|
+
color: var(--dsw-alias-state-error-primary, #b42318);
|
|
134
140
|
flex: 1;
|
|
135
141
|
margin: 0;
|
|
136
142
|
font-size: 12px;
|
|
@@ -278,7 +284,7 @@
|
|
|
278
284
|
}
|
|
279
285
|
|
|
280
286
|
.inputInvalid {
|
|
281
|
-
border: 1px solid var(--dsw-alias-
|
|
287
|
+
border: 1px solid var(--dsw-alias-state-error-primary, #b42318);
|
|
282
288
|
background: var(--dsw-alias-bg-layer-3);
|
|
283
289
|
height: 34px;
|
|
284
290
|
font: inherit;
|
|
@@ -290,9 +296,9 @@
|
|
|
290
296
|
}
|
|
291
297
|
|
|
292
298
|
.inputInvalid:focus-visible {
|
|
293
|
-
outline: 2px solid var(--dsw-alias-
|
|
299
|
+
outline: 2px solid var(--dsw-alias-state-error-primary, #b42318);
|
|
294
300
|
outline-offset: 1px;
|
|
295
|
-
border-color: var(--dsw-alias-
|
|
301
|
+
border-color: var(--dsw-alias-state-error-primary, #b42318);
|
|
296
302
|
}
|
|
297
303
|
|
|
298
304
|
.selectWrap {
|
|
@@ -384,14 +390,14 @@
|
|
|
384
390
|
}
|
|
385
391
|
|
|
386
392
|
.invalid {
|
|
387
|
-
color: var(--dsw-alias-
|
|
393
|
+
color: var(--dsw-alias-state-error-primary, #b42318);
|
|
388
394
|
margin: 0;
|
|
389
395
|
font-size: 12px;
|
|
390
396
|
line-height: 1.5;
|
|
391
397
|
}
|
|
392
398
|
|
|
393
399
|
.hint {
|
|
394
|
-
color: var(--dsw-alias-label-
|
|
400
|
+
color: var(--dsw-alias-label-secondary);
|
|
395
401
|
margin: 0;
|
|
396
402
|
font-size: 12px;
|
|
397
403
|
line-height: 1.5;
|