@linxin666/dsh-client-ui-task-board 0.1.12 → 0.1.14
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.i18n.yaml +6 -0
- package/README.md +92 -174
- package/README.zh.md +132 -0
- package/lib/client.js +177 -108
- package/lib/client.js.map +1 -1
- package/lib/types/client/apply-guard.d.ts +25 -0
- package/lib/types/core/store.d.ts +29 -1
- package/package.json +1 -6
- package/src/client/PluginSettingsCard.tsx +30 -5
- package/src/client/apply-guard.ts +35 -0
- package/src/client/board-mount.tsx +6 -3
- package/src/client/board.module.css +2 -1
- package/src/client/index.ts +11 -0
- package/src/client/settings-card.module.css +146 -116
- package/src/client/sidebar-entry.ts +7 -0
- package/src/core/controller.ts +19 -4
- package/src/core/store.ts +41 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-module-instance apply guard for the task-board client bundle.
|
|
3
|
+
*
|
|
4
|
+
* The client factory can run more than once in a single page lifetime (for
|
|
5
|
+
* example when a stale bundle is mixed with a rebuilt one while `dsh web` is
|
|
6
|
+
* restarted). Without a guard, every factory run mounts its own sidebar entry
|
|
7
|
+
* and board view, so the shell ends up showing two "task board" rows.
|
|
8
|
+
*
|
|
9
|
+
* The flag lives on globalThis so separate module instances (independent
|
|
10
|
+
* factory runs) still share one guard. First claim wins; later claims become
|
|
11
|
+
* no-ops until the claim is released (fiber unload / hot-reload) or the page
|
|
12
|
+
* reloads.
|
|
13
|
+
*/
|
|
14
|
+
declare global {
|
|
15
|
+
var __dshTaskboardApplied: boolean | undefined;
|
|
16
|
+
}
|
|
17
|
+
/** Claims the plugin apply slot. Returns true when this call won the slot. */
|
|
18
|
+
export declare function claimTaskboardApply(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Releases the claim. Called from the client fiber cleanup so that a
|
|
21
|
+
* hot-reloaded bundle (the loader unloads the old plugin fiber and invokes
|
|
22
|
+
* the rebuilt one in the same page) can claim again instead of being
|
|
23
|
+
* silently dropped.
|
|
24
|
+
*/
|
|
25
|
+
export declare function releaseTaskboardApply(): void;
|
|
@@ -7,9 +7,26 @@ export interface TaskStore {
|
|
|
7
7
|
save(tasks: readonly TaskRecord[]): void;
|
|
8
8
|
/** Drop the persisted ledger (leaves the in-memory state alone). */
|
|
9
9
|
clear(): void;
|
|
10
|
+
/**
|
|
11
|
+
* Subscribe to ledger changes written by ANOTHER tab of the same origin
|
|
12
|
+
* (browser storage events). The board controller reloads the ledger on
|
|
13
|
+
* such a change, so a task deleted in one tab cannot keep firing (or be
|
|
14
|
+
* written back) from the stale in-memory copy of another tab. No-op when
|
|
15
|
+
* the backend has no cross-instance channel (in-memory store).
|
|
16
|
+
*/
|
|
17
|
+
subscribeExternal?(listener: () => void): () => void;
|
|
10
18
|
}
|
|
11
19
|
/** Storage key for the task ledger document. */
|
|
12
20
|
export declare const DEFAULT_STORAGE_KEY = "dsh.taskBoard.v1";
|
|
21
|
+
/** Structural shape of the storage event fired in sibling tabs (DOM-free). */
|
|
22
|
+
export interface StorageChangeEvent {
|
|
23
|
+
key: string | null;
|
|
24
|
+
}
|
|
25
|
+
/** The event-target face the store needs for cross-tab notifications. */
|
|
26
|
+
export interface StorageEvents {
|
|
27
|
+
addEventListener(type: 'storage', listener: (event: StorageChangeEvent) => void): void;
|
|
28
|
+
removeEventListener(type: 'storage', listener: (event: StorageChangeEvent) => void): void;
|
|
29
|
+
}
|
|
13
30
|
/** A task record is structurally valid if it round-trips through the UI. */
|
|
14
31
|
export declare function isTaskRecord(value: unknown): value is TaskRecord;
|
|
15
32
|
/** Parse + validate a persisted ledger document; invalid rows are dropped. */
|
|
@@ -18,14 +35,25 @@ export declare function parseLedger(raw: string | null): TaskRecord[];
|
|
|
18
35
|
export declare class LocalStorageTaskStore implements TaskStore {
|
|
19
36
|
private readonly key;
|
|
20
37
|
private readonly storage;
|
|
38
|
+
private readonly events;
|
|
21
39
|
/**
|
|
22
40
|
* @param key - storage key for the ledger document.
|
|
23
41
|
* @param storage - storage backend (defaults to the global localStorage; tests inject fakes).
|
|
42
|
+
* @param events - storage-event target for cross-tab notifications (defaults
|
|
43
|
+
* to the browser global; undefined in non-browser runtimes, where the
|
|
44
|
+
* subscription becomes a no-op).
|
|
24
45
|
*/
|
|
25
|
-
constructor(key?: string, storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | undefined);
|
|
46
|
+
constructor(key?: string, storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | undefined, events?: StorageEvents | undefined);
|
|
26
47
|
load(): TaskRecord[];
|
|
27
48
|
save(tasks: readonly TaskRecord[]): void;
|
|
28
49
|
clear(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Cross-tab change subscription (see {@link TaskStore.subscribeExternal}).
|
|
52
|
+
* The browser fires the storage event in every OTHER tab of the same origin
|
|
53
|
+
* when one tab writes; a null key means the whole storage was cleared. Both
|
|
54
|
+
* cases reload the ledger here; unrelated keys are ignored.
|
|
55
|
+
*/
|
|
56
|
+
subscribeExternal(listener: () => void): () => void;
|
|
29
57
|
}
|
|
30
58
|
/** In-memory backend (tests, and a fallback when storage is unavailable). */
|
|
31
59
|
export declare class InMemoryTaskStore implements TaskStore {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-client-ui-task-board",
|
|
3
3
|
"description": "Task board for the dsh web GUI: a sidebar entry plus a multi-column kanban view with local persistence, task details, and real execution through dsh sessions (session.prompt). Hot-pluggable — mounted via ~/.dsh/cordis.patch.yml + a profile node_modules symlink, no dsh source changes.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.14",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -35,10 +35,6 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
|
|
39
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
40
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
|
|
41
|
-
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
42
38
|
"react": "^18.2.0"
|
|
43
39
|
},
|
|
44
40
|
"dependencies": {
|
|
@@ -59,7 +55,6 @@
|
|
|
59
55
|
"lightningcss": "1.32.0",
|
|
60
56
|
"react": "^18.3.1",
|
|
61
57
|
"react-dom": "^18.3.1",
|
|
62
|
-
"schemastery": "^3.18.0",
|
|
63
58
|
"tsdown": "0.22.2",
|
|
64
59
|
"typescript": "~5.7.2",
|
|
65
60
|
"vitest": "^3.0.0"
|
|
@@ -41,13 +41,14 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
41
41
|
if (!state.available) return null
|
|
42
42
|
const title = props.t(props.titleKey)
|
|
43
43
|
const blocked = !state.dirty || state.invalid || state.saving
|
|
44
|
+
const cardClass = open ? `${css.cardOpen} ${css.card}` : css.card
|
|
44
45
|
// The namespace exists but the Host does not serve it to this client (the
|
|
45
46
|
// official settings allowlist omits third-party namespaces): show a card
|
|
46
47
|
// that explains the gap instead of vanishing, so a missing card never
|
|
47
48
|
// reads as a missing plugin.
|
|
48
49
|
if (!state.exposed) {
|
|
49
50
|
return (
|
|
50
|
-
<li className={
|
|
51
|
+
<li className={cardClass}>
|
|
51
52
|
<button
|
|
52
53
|
type="button"
|
|
53
54
|
className={css.header}
|
|
@@ -59,7 +60,19 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
59
60
|
<span className={css.name}>{title}</span>
|
|
60
61
|
<span className={css.description}>{props.t(props.descriptionKey)}</span>
|
|
61
62
|
</span>
|
|
62
|
-
<
|
|
63
|
+
<svg
|
|
64
|
+
width="14"
|
|
65
|
+
height="14"
|
|
66
|
+
viewBox="0 0 14 14"
|
|
67
|
+
fill="none"
|
|
68
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
69
|
+
className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}
|
|
70
|
+
>
|
|
71
|
+
<path
|
|
72
|
+
d="M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z"
|
|
73
|
+
fill="currentColor"
|
|
74
|
+
/>
|
|
75
|
+
</svg>
|
|
63
76
|
</button>
|
|
64
77
|
{open
|
|
65
78
|
? (
|
|
@@ -72,7 +85,7 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
72
85
|
)
|
|
73
86
|
}
|
|
74
87
|
return (
|
|
75
|
-
<li className={
|
|
88
|
+
<li className={cardClass}>
|
|
76
89
|
<button
|
|
77
90
|
type="button"
|
|
78
91
|
className={css.header}
|
|
@@ -85,7 +98,19 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
85
98
|
<span className={css.description}>{props.t(props.descriptionKey)}</span>
|
|
86
99
|
</span>
|
|
87
100
|
{state.dirty ? <span className={css.pending}>{props.t('settings.unsaved')}</span> : null}
|
|
88
|
-
<
|
|
101
|
+
<svg
|
|
102
|
+
width="14"
|
|
103
|
+
height="14"
|
|
104
|
+
viewBox="0 0 14 14"
|
|
105
|
+
fill="none"
|
|
106
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
107
|
+
className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}
|
|
108
|
+
>
|
|
109
|
+
<path
|
|
110
|
+
d="M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z"
|
|
111
|
+
fill="currentColor"
|
|
112
|
+
/>
|
|
113
|
+
</svg>
|
|
89
114
|
</button>
|
|
90
115
|
{open
|
|
91
116
|
? (
|
|
@@ -234,4 +259,4 @@ export function BooleanField(props: FieldProps & {
|
|
|
234
259
|
<p className={css.hint}>{props.hint}</p>
|
|
235
260
|
</div>
|
|
236
261
|
)
|
|
237
|
-
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-module-instance apply guard for the task-board client bundle.
|
|
3
|
+
*
|
|
4
|
+
* The client factory can run more than once in a single page lifetime (for
|
|
5
|
+
* example when a stale bundle is mixed with a rebuilt one while `dsh web` is
|
|
6
|
+
* restarted). Without a guard, every factory run mounts its own sidebar entry
|
|
7
|
+
* and board view, so the shell ends up showing two "task board" rows.
|
|
8
|
+
*
|
|
9
|
+
* The flag lives on globalThis so separate module instances (independent
|
|
10
|
+
* factory runs) still share one guard. First claim wins; later claims become
|
|
11
|
+
* no-ops until the claim is released (fiber unload / hot-reload) or the page
|
|
12
|
+
* reloads.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
declare global {
|
|
16
|
+
// eslint-disable-next-line no-var
|
|
17
|
+
var __dshTaskboardApplied: boolean | undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Claims the plugin apply slot. Returns true when this call won the slot. */
|
|
21
|
+
export function claimTaskboardApply(): boolean {
|
|
22
|
+
if (globalThis.__dshTaskboardApplied === true) return false
|
|
23
|
+
globalThis.__dshTaskboardApplied = true
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Releases the claim. Called from the client fiber cleanup so that a
|
|
29
|
+
* hot-reloaded bundle (the loader unloads the old plugin fiber and invokes
|
|
30
|
+
* the rebuilt one in the same page) can claim again instead of being
|
|
31
|
+
* silently dropped.
|
|
32
|
+
*/
|
|
33
|
+
export function releaseTaskboardApply(): void {
|
|
34
|
+
globalThis.__dshTaskboardApplied = undefined
|
|
35
|
+
}
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The `conversation` slot is single-occupant (ui-conversation) and external
|
|
5
5
|
* plugins cannot declare slots, so the board takes over the center column at
|
|
6
|
-
* the DOM level: a container is appended inside the
|
|
7
|
-
*
|
|
6
|
+
* the DOM level: a container is appended inside the center column
|
|
7
|
+
* (`[class*="centerCol"]`, the dsh 0.1.0-rc.6 AppFrame layout; previously
|
|
8
|
+
* `[data-pane="conversation"]` on older shells — the mount selector keeps both)
|
|
9
|
+
* as an extra trailing child
|
|
10
|
+
* React never manages, and a stylesheet
|
|
8
11
|
* rule hides the conversation content while the board is active. Toggling is
|
|
9
12
|
* a data attribute on <html> — no React involvement, so the conversation
|
|
10
13
|
* subtree underneath stays mounted and stateful.
|
|
@@ -17,7 +20,7 @@ import css from './board.module.css'
|
|
|
17
20
|
/** The injected board container (kept in the DOM, hidden when inactive). */
|
|
18
21
|
export const BOARD_VIEW_SELECTOR = '[data-dsh-taskboard-view]'
|
|
19
22
|
|
|
20
|
-
const CONVERSATION_COLUMN_SELECTOR = '[data-pane="conversation"]'
|
|
23
|
+
const CONVERSATION_COLUMN_SELECTOR = '[data-pane="conversation"], [class*="centerCol"]'
|
|
21
24
|
const ACTIVE_ATTR = 'data-dsh-taskboard-active'
|
|
22
25
|
/** The sibling panel's activation attribute (ssh), removed when this panel opens. */
|
|
23
26
|
const OTHER_ACTIVE_ATTR = 'data-dsh-ssh-active'
|
package/src/client/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { BoardController } from '../core/controller.ts'
|
|
|
20
20
|
import { ExecutionService } from '../core/execution.ts'
|
|
21
21
|
import { SchedulerService } from '../core/scheduler.ts'
|
|
22
22
|
import { LocalStorageTaskStore } from '../core/store.ts'
|
|
23
|
+
import { claimTaskboardApply, releaseTaskboardApply } from './apply-guard.ts'
|
|
23
24
|
import { mountBoard } from './board-mount.tsx'
|
|
24
25
|
import { mountSidebarEntry } from './sidebar-entry.ts'
|
|
25
26
|
import { TaskBoardSettingsCard, TaskBoardSettingsCardController, type TaskBoardSettings } from './TaskBoardSettingsCard.tsx'
|
|
@@ -74,6 +75,16 @@ export const inject = ['slots', 'sessions', 'workspaces', 'connection', 'setting
|
|
|
74
75
|
* @param ctx - client root context (services: sessions, workspaces).
|
|
75
76
|
*/
|
|
76
77
|
export function apply(ctx: ClientContext): void {
|
|
78
|
+
// A duplicated client injection (module factory executed twice in one page
|
|
79
|
+
// lifetime) would otherwise mount a second sidebar entry and board view.
|
|
80
|
+
// First application wins; later calls become no-ops (see apply-guard.ts).
|
|
81
|
+
if (!claimTaskboardApply()) return
|
|
82
|
+
|
|
83
|
+
// Release the claim when this fiber unloads (the loader supports plugin
|
|
84
|
+
// unloads / hot-reloads), so a rebuilt bundle can claim again in the same
|
|
85
|
+
// page instead of being silently dropped.
|
|
86
|
+
ctx.effect(() => releaseTaskboardApply, 'task-board: apply claim')
|
|
87
|
+
|
|
77
88
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'task-board: dictionaries')
|
|
78
89
|
|
|
79
90
|
// Plugin configuration card: one staged form over the `task-board` settings
|
|
@@ -1,60 +1,83 @@
|
|
|
1
|
+
/* Plugin settings card chrome + staged form fields.
|
|
2
|
+
* Aligned with the official ui-settings-plugins PluginCard / fields CSS:
|
|
3
|
+
* same semantic tokens, radius, typography and states so family cards read
|
|
4
|
+
* as siblings of the built-in Shell / Agent loop / Web search cards. */
|
|
5
|
+
|
|
1
6
|
.card {
|
|
2
|
-
list-style: none;
|
|
3
7
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
4
|
-
border-radius: 8px;
|
|
5
8
|
background: var(--dsw-alias-bg-layer-3);
|
|
6
|
-
|
|
9
|
+
border-radius: 12px;
|
|
10
|
+
list-style: none;
|
|
7
11
|
transition: border-color 0.16s, background 0.16s;
|
|
8
12
|
}
|
|
9
13
|
|
|
14
|
+
.card:hover {
|
|
15
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
16
|
+
}
|
|
17
|
+
|
|
10
18
|
.cardOpen {
|
|
11
19
|
background: var(--dsw-alias-bg-layer-2);
|
|
12
20
|
border-color: var(--dsw-alias-label-dimmed);
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
.header {
|
|
16
|
-
|
|
17
|
-
align-items: center;
|
|
18
|
-
gap: 8px;
|
|
24
|
+
appearance: none;
|
|
19
25
|
width: 100%;
|
|
20
|
-
padding: 10px 14px;
|
|
21
|
-
border: 0;
|
|
22
|
-
background: transparent;
|
|
23
|
-
cursor: pointer;
|
|
24
|
-
text-align: left;
|
|
25
26
|
font: inherit;
|
|
27
|
+
color: inherit;
|
|
28
|
+
text-align: left;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
background: transparent;
|
|
31
|
+
border: 0;
|
|
32
|
+
border-radius: 12px;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: 12px;
|
|
35
|
+
padding: 14px 16px;
|
|
36
|
+
display: flex;
|
|
26
37
|
}
|
|
27
38
|
|
|
28
|
-
.header:
|
|
29
|
-
|
|
39
|
+
.header:focus-visible {
|
|
40
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
41
|
+
outline-offset: -2px;
|
|
30
42
|
}
|
|
31
43
|
|
|
32
44
|
.headText {
|
|
33
|
-
display: flex;
|
|
34
45
|
flex-direction: column;
|
|
35
|
-
gap: 2px;
|
|
36
46
|
flex: 1;
|
|
47
|
+
gap: 4px;
|
|
37
48
|
min-width: 0;
|
|
49
|
+
display: flex;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
.name {
|
|
41
|
-
font-weight: 600;
|
|
42
53
|
color: var(--dsw-alias-label-primary);
|
|
54
|
+
font-size: 15px;
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
line-height: 1.4;
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
.description {
|
|
46
|
-
font-size: 12px;
|
|
47
60
|
color: var(--dsw-alias-label-tertiary);
|
|
61
|
+
font-size: 13px;
|
|
62
|
+
line-height: 1.5;
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
.pending {
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
white-space: nowrap;
|
|
67
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
68
|
+
color: var(--dsw-alias-label-secondary);
|
|
69
|
+
border-radius: 999px;
|
|
70
|
+
flex: none;
|
|
71
|
+
padding: 1px 8px;
|
|
72
|
+
font-size: 11px;
|
|
73
|
+
font-weight: 500;
|
|
74
|
+
line-height: 17px;
|
|
53
75
|
}
|
|
54
76
|
|
|
55
77
|
.chevron {
|
|
56
|
-
transition: transform 120ms ease;
|
|
57
78
|
color: var(--dsw-alias-label-tertiary);
|
|
79
|
+
flex: none;
|
|
80
|
+
transition: transform 0.16s;
|
|
58
81
|
}
|
|
59
82
|
|
|
60
83
|
.chevronOpen {
|
|
@@ -62,51 +85,59 @@
|
|
|
62
85
|
}
|
|
63
86
|
|
|
64
87
|
.body {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
gap: 14px;
|
|
88
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
89
|
+
margin: 0 16px;
|
|
90
|
+
padding-bottom: 8px;
|
|
69
91
|
}
|
|
70
92
|
|
|
71
93
|
.readOnly {
|
|
72
|
-
margin: 0;
|
|
73
|
-
font-size: 12px;
|
|
74
94
|
color: var(--dsw-alias-label-tertiary);
|
|
95
|
+
margin: 12px 0 0;
|
|
96
|
+
font-size: 12px;
|
|
97
|
+
line-height: 1.5;
|
|
75
98
|
}
|
|
76
99
|
|
|
77
100
|
.notExposed {
|
|
78
|
-
|
|
101
|
+
color: var(--dsw-alias-state-warn-primary);
|
|
102
|
+
margin: 12px 0 0;
|
|
79
103
|
font-size: 12px;
|
|
80
104
|
line-height: 1.5;
|
|
81
|
-
color: var(--dsw-alias-state-warn-primary);
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
.footer {
|
|
85
|
-
|
|
86
|
-
align-items: center;
|
|
108
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
87
109
|
justify-content: flex-end;
|
|
110
|
+
align-items: center;
|
|
88
111
|
gap: 8px;
|
|
112
|
+
padding: 12px 0 4px;
|
|
113
|
+
display: flex;
|
|
89
114
|
}
|
|
90
115
|
|
|
91
116
|
.failed {
|
|
92
|
-
|
|
117
|
+
min-width: 0;
|
|
118
|
+
color: var(--dsw-alias-label-error);
|
|
119
|
+
flex: 1;
|
|
120
|
+
margin: 0;
|
|
93
121
|
font-size: 12px;
|
|
94
|
-
|
|
122
|
+
line-height: 1.5;
|
|
95
123
|
}
|
|
96
124
|
|
|
97
125
|
.discard,
|
|
98
126
|
.save {
|
|
99
|
-
|
|
100
|
-
border-radius: 6px;
|
|
127
|
+
appearance: none;
|
|
101
128
|
font: inherit;
|
|
102
|
-
font-size: 13px;
|
|
103
129
|
cursor: pointer;
|
|
130
|
+
border: 1px solid transparent;
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
padding: 5px 14px;
|
|
133
|
+
font-size: 13px;
|
|
134
|
+
line-height: 1.5;
|
|
104
135
|
}
|
|
105
136
|
|
|
106
137
|
.discard {
|
|
107
|
-
border:
|
|
108
|
-
background: transparent;
|
|
138
|
+
border-color: var(--dsw-alias-border-l2);
|
|
109
139
|
color: var(--dsw-alias-label-secondary);
|
|
140
|
+
background: transparent;
|
|
110
141
|
}
|
|
111
142
|
|
|
112
143
|
.discard:hover:not(:disabled) {
|
|
@@ -115,154 +146,153 @@
|
|
|
115
146
|
}
|
|
116
147
|
|
|
117
148
|
.save {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
color: var(--dsw-alias-label-primary-foreground);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.save:hover:not(:disabled) {
|
|
124
|
-
border-color: var(--dsw-alias-button-info-hover);
|
|
125
|
-
background: var(--dsw-alias-button-info-hover);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.discard:active:not(:disabled),
|
|
129
|
-
.save:active:not(:disabled) {
|
|
130
|
-
transform: translateY(1px);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.discard:focus-visible:not(:disabled),
|
|
134
|
-
.save:focus-visible:not(:disabled) {
|
|
135
|
-
outline: 2px solid var(--dsw-alias-state-business-primary);
|
|
136
|
-
outline-offset: -2px;
|
|
149
|
+
background: var(--dsw-alias-label-primary);
|
|
150
|
+
color: var(--dsw-alias-bg-layer-3);
|
|
137
151
|
}
|
|
138
152
|
|
|
139
153
|
.discard:disabled,
|
|
140
154
|
.save:disabled {
|
|
141
|
-
opacity: 0.
|
|
155
|
+
opacity: 0.4;
|
|
142
156
|
cursor: default;
|
|
143
157
|
}
|
|
144
158
|
|
|
159
|
+
.discard:focus-visible,
|
|
160
|
+
.save:focus-visible {
|
|
161
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
162
|
+
outline-offset: 1px;
|
|
163
|
+
}
|
|
164
|
+
|
|
145
165
|
.field {
|
|
146
|
-
display: flex;
|
|
147
166
|
flex-direction: column;
|
|
148
|
-
gap:
|
|
167
|
+
gap: 6px;
|
|
168
|
+
padding: 12px 0;
|
|
169
|
+
display: flex;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.field + .field {
|
|
173
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
149
174
|
}
|
|
150
175
|
|
|
151
176
|
.head {
|
|
152
|
-
display: flex;
|
|
153
177
|
align-items: center;
|
|
154
178
|
gap: 8px;
|
|
179
|
+
display: flex;
|
|
155
180
|
}
|
|
156
181
|
|
|
157
182
|
.label {
|
|
183
|
+
min-width: 0;
|
|
184
|
+
color: var(--dsw-alias-label-primary);
|
|
185
|
+
flex: 1;
|
|
158
186
|
font-size: 13px;
|
|
159
187
|
font-weight: 500;
|
|
160
|
-
|
|
188
|
+
line-height: 1.5;
|
|
161
189
|
}
|
|
162
190
|
|
|
163
191
|
.badges {
|
|
164
|
-
display: flex;
|
|
165
192
|
align-items: center;
|
|
166
|
-
gap:
|
|
193
|
+
gap: 8px;
|
|
194
|
+
display: inline-flex;
|
|
167
195
|
}
|
|
168
196
|
|
|
169
197
|
.badge {
|
|
170
|
-
|
|
171
|
-
|
|
198
|
+
white-space: nowrap;
|
|
199
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
200
|
+
color: var(--dsw-alias-label-secondary);
|
|
172
201
|
border-radius: 999px;
|
|
173
|
-
|
|
174
|
-
|
|
202
|
+
padding: 1px 8px;
|
|
203
|
+
font-size: 11px;
|
|
204
|
+
font-weight: 500;
|
|
205
|
+
line-height: 17px;
|
|
175
206
|
}
|
|
176
207
|
|
|
177
208
|
.reset {
|
|
178
|
-
font
|
|
179
|
-
|
|
180
|
-
background: transparent;
|
|
181
|
-
color: var(--dsw-alias-state-business-primary);
|
|
209
|
+
font: inherit;
|
|
210
|
+
color: var(--dsw-alias-label-secondary);
|
|
182
211
|
cursor: pointer;
|
|
212
|
+
background: transparent;
|
|
213
|
+
border: none;
|
|
183
214
|
padding: 0;
|
|
215
|
+
font-size: 12px;
|
|
216
|
+
line-height: 1.5;
|
|
184
217
|
}
|
|
185
218
|
|
|
186
219
|
.reset:hover:not(:disabled) {
|
|
187
|
-
|
|
220
|
+
color: var(--dsw-alias-label-primary);
|
|
188
221
|
}
|
|
189
222
|
|
|
190
|
-
.reset:
|
|
191
|
-
|
|
192
|
-
opacity: 0.8;
|
|
223
|
+
.reset:disabled {
|
|
224
|
+
cursor: default;
|
|
193
225
|
}
|
|
194
226
|
|
|
195
227
|
.reset:focus-visible {
|
|
196
|
-
outline: 2px solid var(--dsw-alias-
|
|
197
|
-
outline-offset:
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.reset:disabled {
|
|
201
|
-
opacity: 0.5;
|
|
202
|
-
cursor: default;
|
|
228
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
229
|
+
outline-offset: 2px;
|
|
203
230
|
}
|
|
204
231
|
|
|
205
232
|
.input,
|
|
206
233
|
.select {
|
|
207
|
-
padding: 6px 8px;
|
|
208
234
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
209
|
-
|
|
235
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
236
|
+
height: 34px;
|
|
210
237
|
font: inherit;
|
|
211
|
-
font-size: 13px;
|
|
212
238
|
color: var(--dsw-alias-label-primary);
|
|
213
|
-
|
|
239
|
+
border-radius: 8px;
|
|
240
|
+
padding: 0 12px;
|
|
241
|
+
font-size: 13px;
|
|
242
|
+
line-height: 1.5;
|
|
214
243
|
}
|
|
215
244
|
|
|
216
|
-
.
|
|
217
|
-
|
|
218
|
-
border:
|
|
219
|
-
|
|
220
|
-
font: inherit;
|
|
221
|
-
font-size: 13px;
|
|
222
|
-
color: var(--dsw-alias-label-primary);
|
|
245
|
+
.input:focus-visible,
|
|
246
|
+
.select:focus-visible {
|
|
247
|
+
border-color: var(--dsw-alias-brand-primary);
|
|
248
|
+
outline: none;
|
|
223
249
|
}
|
|
224
250
|
|
|
225
251
|
.input:disabled,
|
|
226
252
|
.select:disabled {
|
|
227
|
-
|
|
253
|
+
color: var(--dsw-alias-label-tertiary);
|
|
254
|
+
cursor: default;
|
|
228
255
|
}
|
|
229
256
|
|
|
230
|
-
.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
257
|
+
.inputInvalid {
|
|
258
|
+
border: 1px solid var(--dsw-alias-label-error);
|
|
259
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
260
|
+
height: 34px;
|
|
261
|
+
font: inherit;
|
|
262
|
+
color: var(--dsw-alias-label-primary);
|
|
263
|
+
border-radius: 8px;
|
|
264
|
+
padding: 0 12px;
|
|
265
|
+
font-size: 13px;
|
|
266
|
+
line-height: 1.5;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.inputInvalid:focus-visible {
|
|
270
|
+
outline: 2px solid var(--dsw-alias-label-error);
|
|
271
|
+
outline-offset: 1px;
|
|
272
|
+
border-color: var(--dsw-alias-label-error);
|
|
234
273
|
}
|
|
235
274
|
|
|
236
275
|
.invalid {
|
|
276
|
+
color: var(--dsw-alias-label-error);
|
|
237
277
|
margin: 0;
|
|
238
278
|
font-size: 12px;
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/* --- polish: header keyboard focus / press, unified motion, reduced-motion --- */
|
|
243
|
-
.header:focus-visible {
|
|
244
|
-
outline: 2px solid var(--dsw-alias-state-business-primary);
|
|
245
|
-
outline-offset: -2px;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.header:active {
|
|
249
|
-
background: var(--dsw-alias-interactive-bg-hover);
|
|
279
|
+
line-height: 1.5;
|
|
250
280
|
}
|
|
251
281
|
|
|
252
|
-
.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
282
|
+
.hint {
|
|
283
|
+
color: var(--dsw-alias-label-tertiary);
|
|
284
|
+
margin: 0;
|
|
285
|
+
font-size: 12px;
|
|
286
|
+
line-height: 1.5;
|
|
257
287
|
}
|
|
258
288
|
|
|
259
289
|
@media (prefers-reduced-motion: reduce) {
|
|
260
290
|
.card,
|
|
261
291
|
.header,
|
|
262
292
|
.chevron,
|
|
293
|
+
.chevronOpen,
|
|
263
294
|
.discard,
|
|
264
|
-
.save
|
|
265
|
-
.reset {
|
|
295
|
+
.save {
|
|
266
296
|
transition: none;
|
|
267
297
|
}
|
|
268
298
|
}
|