@morlay/dsh-client-ui-primitives 0.0.4 → 0.0.5
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 +18 -266
- package/dist/client.cjs +1 -611
- package/dist/client.d.cts +11 -658
- package/dist/client.d.mts +11 -658
- package/dist/index.mjs +1 -1
- package/dist/{reference-Dxy9uQYz.mjs → reference-Cjcn21uW.mjs} +0 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
- package/src/client/index.ts +7 -9
- package/src/client/schema-form/Editor.tsx +25 -38
- package/src/client/schema-form/SchemaForm.tsx +6 -12
- package/src/client/schema-form/controller.ts +65 -123
- package/src/client/schema-form/draft.ts +55 -138
- package/src/client/schema-form/fields.tsx +5 -14
- package/src/client/schema-form/hints.ts +31 -131
- package/src/client/schema-form/index.ts +15 -20
- package/src/client/schema-form/labels.ts +7 -9
- package/src/client/schema-form/lines.ts +36 -60
- package/src/client/schema-form/locales.ts +6 -8
- package/src/client/schema-form/rows.ts +22 -41
- package/src/client/schema-form/schema-node.ts +87 -150
- package/src/client/schema-form/slot-contract.ts +55 -76
- package/src/client/schema-form/styles.ts +39 -51
- package/src/client/schema-form/testing/fake-describe.ts +3 -9
- package/src/client/schema-form/testing/fake-scope.ts +8 -10
- package/src/client/schema-form/value.tsx +14 -26
- package/src/client/settings-form/SettingsForm.tsx +13 -17
- package/src/client/settings-form/fields.tsx +25 -35
- package/src/client/settings-form/form-model.ts +63 -145
- package/src/client/styling/styled.tsx +3 -5
- package/src/client/theme.generated.ts +6 -7
- package/src/reference-markdown.tsx +1 -1
- package/src/reference.ts +1 -1
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* 于是字段槽只需声明一次,所有行共用。
|
|
11
|
-
*/
|
|
12
|
-
|
|
1
|
+
// 槽契约:字段级自定义输入与文案的注入点,以及承载它的 Factory 契约。
|
|
2
|
+
//
|
|
3
|
+
// 字段槽是 **chain**:注册项在 `select` 里按 `node.meta.role` 或 `ns + path` 决定认领哪个字段,命中即渲染自己的
|
|
4
|
+
// 组件(收到 `matched`),未命中落到本包的默认控件。链式槽的注册项还可以只声明 `label`/`hint`(不改控件)——
|
|
5
|
+
// 默认控件仍会渲染,文案以它为准。
|
|
6
|
+
//
|
|
7
|
+
// 字段槽声明在 Factory 上(`children`),而不是每个行注册项上:slots 的 children 声明 per key 只能一次,而本包
|
|
8
|
+
// 会给每个可渲染的行各注册一个 `plugins.row.config` 项——那些项经 `renderFactorySlot` 渲染同一个 Factory,
|
|
9
|
+
// 于是字段槽只需声明一次,所有行共用。
|
|
13
10
|
import type {} from "@deepseek-ai/dsh-client-ui-slots";
|
|
14
11
|
import type {
|
|
15
12
|
FactoryComponentPropsOf,
|
|
@@ -23,129 +20,111 @@ import type { ResolveText } from "./labels.ts";
|
|
|
23
20
|
import type { SchemaFormLocaleKey } from "./locales.ts";
|
|
24
21
|
import type { FieldNode } from "./schema-node.ts";
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
// `plugins.row.config` 的 key:`<bundle 包名>#<行 id>`(与页面 `rowConfigKey` 同拼法)。
|
|
27
24
|
export type RowConfigKey = string;
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
// 字段槽的 owner props:路由输入 + 显示状态 + 受控动作(默认控件要的一切都在这里)。
|
|
30
27
|
export interface SchemaFieldOwnerProps {
|
|
31
|
-
|
|
28
|
+
// settings 命名空间(行 id)。
|
|
32
29
|
ns: string;
|
|
33
|
-
|
|
30
|
+
// 字段的具体路径(动态段已按真实键或索引替换)。
|
|
34
31
|
path: readonly string[];
|
|
35
|
-
|
|
32
|
+
// 字段节点(类型、`role`、`extra`、界限、只读原因)。
|
|
36
33
|
node: FieldNode;
|
|
37
|
-
|
|
34
|
+
// 显示标签:默认是字段名拆词,业务方经槽给时会覆盖。
|
|
38
35
|
label: string;
|
|
39
|
-
|
|
36
|
+
// 显示说明(`description` 本地化 + `comment`)。
|
|
40
37
|
hint: string | undefined;
|
|
41
|
-
|
|
38
|
+
// 当前生效值(草稿优先)。
|
|
42
39
|
value: unknown;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* 候选——候选依赖的兄弟字段一变,这里就换一批。
|
|
46
|
-
*/
|
|
40
|
+
// 这个字段的可选项:给了就是选择器(点值不再进文本编辑)。来源是字面量集合的 union,或业务经提示面注册的
|
|
41
|
+
// 候选——候选依赖的兄弟字段一变,这里就换一批。
|
|
47
42
|
options: readonly SelectOption[] | undefined;
|
|
48
|
-
|
|
43
|
+
// 文本草稿:文本控件显示它,而不是重新格式化生效值。
|
|
49
44
|
text: string | undefined;
|
|
50
|
-
|
|
45
|
+
// secret 槽是否已配置(值本身不回传)。
|
|
51
46
|
secretConfigured: boolean;
|
|
52
|
-
|
|
47
|
+
// 嵌套深度:默认控件按它缩进。
|
|
53
48
|
depth: number;
|
|
54
|
-
|
|
49
|
+
// 保存后这一字段是否会留下用户层条目。
|
|
55
50
|
overridden: boolean;
|
|
56
|
-
|
|
51
|
+
// 草稿不合法时的消息。
|
|
57
52
|
invalid: string | undefined;
|
|
58
|
-
|
|
53
|
+
// 只读部署或不可用命名空间时为 true。
|
|
59
54
|
disabled: boolean;
|
|
60
|
-
|
|
55
|
+
// 本包字典(复刻或包装默认控件时用)。
|
|
61
56
|
t: SchemaFormTranslate;
|
|
62
|
-
|
|
57
|
+
// 容器动作(增删数组项、字典键、丢弃、保存)。
|
|
63
58
|
actions: SchemaFormActions;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* 默认折叠与否由 schema 的 `collapse()` 决定,用户点一下就是反转。
|
|
67
|
-
*/
|
|
59
|
+
// 分组节点的折叠交互(object / dict / array / tuple);叶子节点是 `undefined`。
|
|
60
|
+
// 默认折叠与否由 schema 的 `collapse()` 决定,用户点一下就是反转。
|
|
68
61
|
group: { collapsed: boolean; toggle: () => void } | undefined;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
* 标题行(名字 + 移除),叶子成员由渲染层另起一行——所以「哪个字段属于哪个键/哪一项」在页面上一眼可见。
|
|
72
|
-
*/
|
|
62
|
+
// 这一行是动态容器的成员本身(dict 的键 / array 的项)时的身份:容器路径、键、数组项的位置。容器成员把它画进
|
|
63
|
+
// 标题行(名字 + 移除),叶子成员由渲染层另起一行——所以「哪个字段属于哪个键/哪一项」在页面上一眼可见。
|
|
73
64
|
member:
|
|
74
65
|
| { parent: readonly string[]; key: string; index: number | undefined; pending?: boolean }
|
|
75
66
|
| undefined;
|
|
76
|
-
|
|
67
|
+
// 暂存一个合法值。
|
|
77
68
|
onChange: (value: unknown) => void;
|
|
78
|
-
|
|
79
|
-
* 暂存一段文本:按字段类型的默认规则解析;给了 `parse` 就用它(例如 secret 留空=不写)。
|
|
80
|
-
* @param text - 用户输入。
|
|
81
|
-
* @param parse - 覆盖默认解析规则。
|
|
82
|
-
*/
|
|
69
|
+
// 暂存一段文本:按字段类型的默认规则解析;给了 `parse` 就用它(例如 secret 留空=不写)。
|
|
83
70
|
onEditText: (text: string, parse?: TextParse) => void;
|
|
84
|
-
|
|
71
|
+
// 暂存「恢复默认」。
|
|
85
72
|
onReset: () => void;
|
|
86
73
|
}
|
|
87
74
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
* 注册方的组件按这个类型声明参数(**不要**自己重写 owner props 的类型:注册时框架会把它读成 inject 面)。
|
|
92
|
-
*/
|
|
75
|
+
// 字段槽注册项的完整 props:框架注入的 owner props(`SchemaFieldOwnerProps`)+它的 `select` 结果。
|
|
76
|
+
//
|
|
77
|
+
// 注册方的组件按这个类型声明参数(**不要**自己重写 owner props 的类型:注册时框架会把它读成 inject 面)。
|
|
93
78
|
export type SchemaFieldComponentProps<M extends SchemaFieldMatch = SchemaFieldMatch> =
|
|
94
79
|
PropsRuntime<"settings.schema-form.field"> & { matched: M };
|
|
95
80
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
* `undefined` 不会被当成"不认领")。
|
|
99
|
-
*/
|
|
81
|
+
// 字段槽注册项的 `select`:返回认领结果,或 `null` 表示不认领(chain 槽的约定是 `null` 放行给下一项,
|
|
82
|
+
// `undefined` 不会被当成"不认领")。
|
|
100
83
|
export type SchemaFieldSelect = (field: SchemaFieldOwnerProps) => SchemaFieldMatch | null;
|
|
101
84
|
|
|
102
|
-
|
|
85
|
+
// 字段槽 `select` 可以只给文案:控件仍由注册方渲染(拿本包的默认控件包一层即可)。
|
|
103
86
|
export interface SchemaFieldMatch {
|
|
104
|
-
|
|
87
|
+
// 覆盖显示标签。
|
|
105
88
|
label?: string;
|
|
106
|
-
|
|
89
|
+
// 覆盖显示说明。
|
|
107
90
|
hint?: string;
|
|
108
91
|
}
|
|
109
92
|
|
|
110
|
-
|
|
93
|
+
// Factory 的输入 props:一行的配置页。
|
|
111
94
|
export interface SchemaFormProps {
|
|
112
|
-
|
|
95
|
+
// settings 命名空间(行 id)。
|
|
113
96
|
ns: string;
|
|
114
|
-
|
|
97
|
+
// 该行的控制器动作与状态。
|
|
115
98
|
face: SchemaFormFace;
|
|
116
|
-
|
|
99
|
+
// schema 里的本地化文本(`description`)按当前语言解析。
|
|
117
100
|
resolveText: ResolveText;
|
|
118
101
|
}
|
|
119
102
|
|
|
120
|
-
|
|
103
|
+
// Factory 组件收到的完整 props。
|
|
121
104
|
export type SchemaFormComponentProps = FactoryComponentPropsOf<"settings.schema-form.form">;
|
|
122
105
|
|
|
123
|
-
|
|
106
|
+
// 本包字典的翻译函数(默认控件与表单壳使用)。
|
|
124
107
|
export type SchemaFormTranslate = TranslateNS<"settings.schema-form">;
|
|
125
108
|
|
|
126
|
-
|
|
109
|
+
// 渲染层读一行状态的便捷别名。
|
|
127
110
|
export type { SchemaFormState };
|
|
128
111
|
|
|
129
112
|
declare module "@deepseek-ai/dsh-client-ui-slots" {
|
|
130
113
|
interface LocaleNamespaceMap {
|
|
131
|
-
|
|
114
|
+
// 表单壳、字段控件与只读呈现的文案。
|
|
132
115
|
"settings.schema-form": SchemaFormLocaleKey;
|
|
133
116
|
}
|
|
134
117
|
|
|
135
118
|
interface SlotMap {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
* Factory 给出。
|
|
140
|
-
*/
|
|
119
|
+
// 字段级自定义输入与文案:按 `role` 或 `ns + path` 认领一个字段,命中即渲染注册方的组件(未命中落到默认
|
|
120
|
+
// 控件)。注册项由业务插件用 `ctx.slots.inject('settings.schema-form.field', …)` 注册,声明由本包的
|
|
121
|
+
// Factory 给出。
|
|
141
122
|
"settings.schema-form.field": { kind: "chain"; scope: "root"; owner: SchemaFieldOwnerProps };
|
|
142
123
|
}
|
|
143
124
|
|
|
144
125
|
interface SlotFactoryMap {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
* 一次(slots 的 children 声明 per key 唯一)。
|
|
148
|
-
*/
|
|
126
|
+
// 一行配置页的渲染体:每个可渲染的行注册项经 `renderFactorySlot` 渲染同一个 Factory,字段槽因此只需声明
|
|
127
|
+
// 一次(slots 的 children 声明 per key 唯一)。
|
|
149
128
|
"settings.schema-form.form": {
|
|
150
129
|
scope: "root";
|
|
151
130
|
props: SchemaFormProps;
|
|
@@ -1,52 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* 几何只有一套:行高、行内间距、动作按钮尺寸都由下面几个常量给,行与行之间不各自决定。
|
|
5
|
-
*/
|
|
1
|
+
// 行式编辑器的样式:等宽、行号、语法色、悬停行为——全部消费官方 `--dsw-*` token。
|
|
2
|
+
//
|
|
3
|
+
// 几何只有一套:行高、行内间距、动作按钮尺寸都由下面几个常量给,行与行之间不各自决定。
|
|
6
4
|
|
|
7
5
|
import { styled } from "../styling/index.ts";
|
|
8
6
|
import { dsw } from "../theme.ts";
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
// 等宽字体栈:配置值的观感与代码一致。
|
|
11
9
|
const MONO = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace';
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
// 行高:行内容、行内输入、折叠箭头、按钮排都在这一档上对齐。
|
|
14
12
|
const ROW = "22px";
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
// 行内元素之间的间距。
|
|
17
15
|
const GAP = "6px";
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
// 动作按钮:方形图标键,尺寸与行高同一档的紧凑版。
|
|
20
18
|
const ACTION = "20px";
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
// 动作按钮之间的间距(比行内间距紧一档)。
|
|
23
21
|
const ACTION_GAP = "4px";
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
// 图标边长(行内图标与动作键同一档)。
|
|
26
24
|
export const ICON_SIZE = 14;
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
// 折叠列(chevron 与它的占位)宽度。
|
|
29
27
|
const FOLD = "14px";
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
// 行号列宽度。
|
|
32
30
|
const NUMBER = "32px";
|
|
33
31
|
|
|
34
|
-
|
|
32
|
+
// 行尾留白。
|
|
35
33
|
const PAD = "8px";
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
// 细线:theme 给的 0.5px 描边(`0 0 0 0.5px var(--dsw-elevation-stroke-color)`)。
|
|
38
36
|
const HAIRLINE = dsw.elevation.stroke;
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
// 描边色:theme 里给描边用的那一档(与官方 `Menu` 面板同源)。
|
|
41
39
|
const LINE = dsw.elevation.stroke.color;
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
// 行左侧那道「改过」的标记宽度。
|
|
44
42
|
const EDGE = "2px";
|
|
45
43
|
|
|
46
|
-
|
|
44
|
+
// 出错那一行的底色:theme 的红掺进透明里(半透明红),比整行实红轻、又比只有一道条明显。
|
|
47
45
|
const ERROR_WASH = `color-mix(in srgb, ${String(dsw.alias.state.error.secondary)} 12%, transparent)`;
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
// 编辑器的根:纵向排列的行。
|
|
50
48
|
export const EditorRoot = styled("div")({
|
|
51
49
|
display: "flex",
|
|
52
50
|
flexDirection: "column",
|
|
@@ -57,7 +55,7 @@ export const EditorRoot = styled("div")({
|
|
|
57
55
|
color: dsw.alias.label.primary,
|
|
58
56
|
});
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
// 一行:行号 + 折进 + 行内容。悬停给一层浅底。
|
|
61
59
|
export const LineRow = styled("div")({
|
|
62
60
|
display: "flex",
|
|
63
61
|
// 顶部对齐:行里出现输入框或多行值时,内容不跟着上下跳。
|
|
@@ -85,7 +83,7 @@ export const LineRow = styled("div")({
|
|
|
85
83
|
"&:hover [data-role='actions']": { opacity: 1 },
|
|
86
84
|
});
|
|
87
85
|
|
|
88
|
-
|
|
86
|
+
// 一行的内容区:**缩进只作用在这里**——行号列固定在左边,缩进不推它。
|
|
89
87
|
export const LineBody = styled("div")({
|
|
90
88
|
display: "flex",
|
|
91
89
|
alignItems: "center",
|
|
@@ -95,7 +93,7 @@ export const LineBody = styled("div")({
|
|
|
95
93
|
minHeight: ROW,
|
|
96
94
|
});
|
|
97
95
|
|
|
98
|
-
|
|
96
|
+
// 行号列:右对齐、不可选、淡色。
|
|
99
97
|
export const LineNumber = styled("span")({
|
|
100
98
|
flex: "none",
|
|
101
99
|
width: NUMBER,
|
|
@@ -106,7 +104,7 @@ export const LineNumber = styled("span")({
|
|
|
106
104
|
cursor: "pointer",
|
|
107
105
|
});
|
|
108
106
|
|
|
109
|
-
|
|
107
|
+
// 折叠 chevron(官方箭头图标,尺寸与别的行内图标同源)。
|
|
110
108
|
export const LineFold = styled("button")({
|
|
111
109
|
display: "inline-flex",
|
|
112
110
|
alignItems: "center",
|
|
@@ -122,26 +120,26 @@ export const LineFold = styled("button")({
|
|
|
122
120
|
"&:hover": { color: dsw.alias.label.secondary },
|
|
123
121
|
});
|
|
124
122
|
|
|
125
|
-
|
|
123
|
+
// 叶子行的折叠占位(保持列对齐)。
|
|
126
124
|
export const LineFoldSpacer = styled("span")({
|
|
127
125
|
flex: "none",
|
|
128
126
|
width: FOLD,
|
|
129
127
|
});
|
|
130
128
|
|
|
131
|
-
|
|
129
|
+
// 键名(配置键原样;数组下标淡一些)。
|
|
132
130
|
export const LineKey = styled("span")({
|
|
133
131
|
flex: "none",
|
|
134
132
|
color: dsw.alias.label.primary,
|
|
135
133
|
"&[data-index='true']": { color: dsw.alias.label.caption },
|
|
136
134
|
});
|
|
137
135
|
|
|
138
|
-
|
|
136
|
+
// 结构符与标点(`{` `}` `[` `]` `:`)。
|
|
139
137
|
export const LineToken = styled("span")({
|
|
140
138
|
flex: "none",
|
|
141
139
|
color: dsw.alias.label.caption,
|
|
142
140
|
});
|
|
143
141
|
|
|
144
|
-
|
|
142
|
+
// 值(token):字符串 / 数字 / 布尔各有语法色。
|
|
145
143
|
export const LineValue = styled("span")({
|
|
146
144
|
minWidth: 0,
|
|
147
145
|
overflow: "hidden",
|
|
@@ -153,9 +151,7 @@ export const LineValue = styled("span")({
|
|
|
153
151
|
"&[data-tone='empty']": { color: dsw.alias.label.caption },
|
|
154
152
|
});
|
|
155
153
|
|
|
156
|
-
|
|
157
|
-
* 值上的下拉触发:**它自己就是值的呈现**(不再是「值 + 一个下拉」两遍),点开才出菜单。
|
|
158
|
-
*/
|
|
154
|
+
// 值上的下拉触发:**它自己就是值的呈现**(不再是「值 + 一个下拉」两遍),点开才出菜单。
|
|
159
155
|
export const ValueTrigger = styled("button")({
|
|
160
156
|
display: "inline-flex",
|
|
161
157
|
alignItems: "center",
|
|
@@ -172,11 +168,9 @@ export const ValueTrigger = styled("button")({
|
|
|
172
168
|
"&:focus-visible": { outline: "none" },
|
|
173
169
|
});
|
|
174
170
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
* 行内编辑与容器闭合行的添加输入共用它——两处的输入框因此长得一模一样。
|
|
179
|
-
*/
|
|
171
|
+
// 行内输入的外壳:官方 `Input` 压到与值同一行高,并**吃掉剩下的宽度**。
|
|
172
|
+
//
|
|
173
|
+
// 行内编辑与容器闭合行的添加输入共用它——两处的输入框因此长得一模一样。
|
|
180
174
|
export const CompactInput = styled("span")({
|
|
181
175
|
display: "inline-flex",
|
|
182
176
|
flex: "1 1 auto",
|
|
@@ -193,10 +187,8 @@ export const CompactInput = styled("span")({
|
|
|
193
187
|
},
|
|
194
188
|
});
|
|
195
189
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
* 高度跟着内容长、宽度同样撑满剩下的位置。
|
|
199
|
-
*/
|
|
190
|
+
// 多行值的行内输入:官方没有多行原子,所以这里用同一套观感的字段壳(同边框、同圆角、同焦点色),
|
|
191
|
+
// 高度跟着内容长、宽度同样撑满剩下的位置。
|
|
200
192
|
export const CompactTextField = styled("span")({
|
|
201
193
|
display: "inline-flex",
|
|
202
194
|
flex: "1 1 auto",
|
|
@@ -226,7 +218,7 @@ export const CompactTextField = styled("span")({
|
|
|
226
218
|
},
|
|
227
219
|
});
|
|
228
220
|
|
|
229
|
-
|
|
221
|
+
// 行内注释(schema 说明与业务文案):比配置值小一档,占满这一行剩下的宽度,长了就截断(hover 看全文)。
|
|
230
222
|
export const LineComment = styled("span")({
|
|
231
223
|
flex: "1 1 auto",
|
|
232
224
|
minWidth: 0,
|
|
@@ -237,7 +229,7 @@ export const LineComment = styled("span")({
|
|
|
237
229
|
fontSize: "12px",
|
|
238
230
|
});
|
|
239
231
|
|
|
240
|
-
|
|
232
|
+
// 行内的校验消息(与注释放同一档字号)。
|
|
241
233
|
export const LineInvalid = styled("span")({
|
|
242
234
|
flex: "none",
|
|
243
235
|
color: dsw.alias.state.error.primary,
|
|
@@ -245,7 +237,7 @@ export const LineInvalid = styled("span")({
|
|
|
245
237
|
fontSize: "12px",
|
|
246
238
|
});
|
|
247
239
|
|
|
248
|
-
|
|
240
|
+
// 行尾的动作组:方形图标键(默认透明,悬停时显形)。
|
|
249
241
|
export const LineActions = styled("span")({
|
|
250
242
|
display: "inline-flex",
|
|
251
243
|
alignItems: "center",
|
|
@@ -269,14 +261,10 @@ export const LineActions = styled("span")({
|
|
|
269
261
|
},
|
|
270
262
|
});
|
|
271
263
|
|
|
272
|
-
|
|
273
|
-
* 悬停行时才显示这一行的动作按钮:显形规则写在行上(`[data-role='actions']`),组件只需要带这个属性。
|
|
274
|
-
*/
|
|
264
|
+
// 悬停行时才显示这一行的动作按钮:显形规则写在行上(`[data-role='actions']`),组件只需要带这个属性。
|
|
275
265
|
export const HoverActions = styled(LineActions)({
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
* 所以这里给实体按钮(theme 的描边 + 一层底),确认用成功档、取消用中性档。
|
|
279
|
-
*/
|
|
266
|
+
// 编辑态的两个按钮常显,而且**要有底色与描边**:行底色上只有淡色图标是看不见的,
|
|
267
|
+
// 所以这里给实体按钮(theme 的描边 + 一层底),确认用成功档、取消用中性档。
|
|
280
268
|
"&[data-editing='true']": {
|
|
281
269
|
opacity: 1,
|
|
282
270
|
gap: GAP,
|
|
@@ -289,7 +277,7 @@ export const HoverActions = styled(LineActions)({
|
|
|
289
277
|
},
|
|
290
278
|
});
|
|
291
279
|
|
|
292
|
-
|
|
280
|
+
// 添加输入:**跟在容器的闭合括号同一行**(菜单锚在它上面),宽度吃掉剩下的位置。
|
|
293
281
|
export const AddWrap = styled("span")({
|
|
294
282
|
position: "relative",
|
|
295
283
|
display: "inline-flex",
|
|
@@ -301,7 +289,7 @@ export const AddWrap = styled("span")({
|
|
|
301
289
|
"&[data-invalid='true'] input": { borderColor: dsw.alias.state.error.primary },
|
|
302
290
|
});
|
|
303
291
|
|
|
304
|
-
|
|
292
|
+
// 一行说明(不可用 / 没有可生成项时用)。
|
|
305
293
|
export const Hint = styled("p")({
|
|
306
294
|
margin: 0,
|
|
307
295
|
fontSize: "12px",
|
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
* 测试用的 describe 读面替身:可推动命名空间清单变化,形状与上游 mirror 的读面一致。
|
|
3
|
-
*/
|
|
1
|
+
// 测试用的 describe 读面替身:可推动命名空间清单变化,形状与上游 mirror 的读面一致。
|
|
4
2
|
|
|
5
3
|
import type { SettingsNamespaceView } from "@deepseek-ai/dsh-api-remotes/client";
|
|
6
4
|
import type { SettingsDescribeFace } from "@deepseek-ai/dsh-client-ui-settings/client";
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
// 一个可以推动变更的 describe 读面。
|
|
9
7
|
export interface FakeDescribe extends SettingsDescribeFace {
|
|
10
|
-
|
|
8
|
+
// 换掉命名空间清单并通知订阅者。
|
|
11
9
|
publish(namespaces: readonly SettingsNamespaceView[]): void;
|
|
12
10
|
}
|
|
13
11
|
|
|
14
|
-
/**
|
|
15
|
-
* @param initial - 初始命名空间清单。
|
|
16
|
-
* @returns 可读可推的 describe 替身。
|
|
17
|
-
*/
|
|
18
12
|
export function fakeDescribe(initial: readonly SettingsNamespaceView[] = []): FakeDescribe {
|
|
19
13
|
const listeners = new Set<() => void>();
|
|
20
14
|
let namespaces = initial;
|
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* 它让控制器与草稿模型的用例不碰远程面、不碰装配,只盯「草稿怎么变成写」与「状态怎么投影」。
|
|
5
|
-
*/
|
|
1
|
+
// 测试用的共享配置表单:内存里模拟 host 的读写两层(快照 / 订阅 / 一次带 revision 栅栏的写)。
|
|
2
|
+
//
|
|
3
|
+
// 它让控制器与草稿模型的用例不碰远程面、不碰装配,只盯「草稿怎么变成写」与「状态怎么投影」。
|
|
6
4
|
|
|
7
5
|
import type { SettingsPathOpView } from "@deepseek-ai/dsh-api-remotes/client";
|
|
8
6
|
import type { ConfigForm, ConfigFormSnapshot } from "@deepseek-ai/dsh-client-ui-settings/client";
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
// 命名空间段的值。
|
|
11
9
|
export type Section = Record<string, unknown>;
|
|
12
10
|
|
|
13
11
|
export class FakeScope implements ConfigForm<Section> {
|
|
14
|
-
|
|
12
|
+
// 每次保存发出的 path op(按保存顺序)。
|
|
15
13
|
readonly writes: (readonly SettingsPathOpView[])[] = [];
|
|
16
|
-
|
|
14
|
+
// 每次保存带的 revision 栅栏。
|
|
17
15
|
readonly fences: (number | undefined)[] = [];
|
|
18
|
-
|
|
16
|
+
// 让 host 拒绝下一次写。
|
|
19
17
|
accepted = true;
|
|
20
18
|
|
|
21
19
|
#snapshot: ConfigFormSnapshot<Section>;
|
|
@@ -85,7 +83,7 @@ export class FakeScope implements ConfigForm<Section> {
|
|
|
85
83
|
return Promise.resolve(true);
|
|
86
84
|
}
|
|
87
85
|
|
|
88
|
-
|
|
86
|
+
// 从外部改一次读数(模拟另一个界面或 host 的改动)。
|
|
89
87
|
publish(overrides: Partial<ConfigFormSnapshot<Section>> = {}): void {
|
|
90
88
|
this.#snapshot = { ...this.#snapshot, ...overrides };
|
|
91
89
|
this.#publish();
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* 形状——字符串把换行写成 `\n`,与一行的输入框相配)。
|
|
6
|
-
*/
|
|
1
|
+
// 行内值的呈现与编辑文本规则,以及字段槽注册方复用的默认值组件。
|
|
2
|
+
//
|
|
3
|
+
// 值本身仍走草稿:这个文件只决定"怎么显示"(语法色 token、下拉触发的呈现)与"怎么编辑"(一行输入框里的文本
|
|
4
|
+
// 形状——字符串把换行写成 `\n`,与一行的输入框相配)。
|
|
7
5
|
|
|
8
6
|
import { useState, type ReactNode } from "react";
|
|
9
7
|
import { IconChevronDownOutlineRegular, Menu } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
@@ -11,7 +9,7 @@ import type { SelectOption } from "./hints.ts";
|
|
|
11
9
|
import type { SchemaFieldOwnerProps } from "./slot-contract.ts";
|
|
12
10
|
import { ICON_SIZE, LineValue, ValueTrigger } from "./styles.ts";
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
// 编辑态文本:字符串就是原文(换行照旧),其余按 JSON 写。
|
|
15
13
|
export function valueText(value: unknown): string {
|
|
16
14
|
if (value === undefined || value === null) return "";
|
|
17
15
|
if (typeof value === "string") return value;
|
|
@@ -19,12 +17,12 @@ export function valueText(value: unknown): string {
|
|
|
19
17
|
return JSON.stringify(value);
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
// 这个值要在多行输入里编辑吗(字符串带换行)。
|
|
23
21
|
export function isMultiline(value: unknown): boolean {
|
|
24
22
|
return typeof value === "string" && /[\r\n]/.test(value);
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
// 显示态文本:字符串带引号;多行与特殊字符都按 JSON 的写法收在一行里。
|
|
28
26
|
export function tokenText(value: unknown): string {
|
|
29
27
|
if (value === undefined || value === null) return "null";
|
|
30
28
|
if (typeof value === "string") return JSON.stringify(value);
|
|
@@ -32,7 +30,7 @@ export function tokenText(value: unknown): string {
|
|
|
32
30
|
return JSON.stringify(value);
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
// 值的语法色分组。
|
|
36
34
|
export function tokenTone(value: unknown): "string" | "number" | "boolean" | "empty" {
|
|
37
35
|
if (value === undefined || value === null) return "empty";
|
|
38
36
|
if (typeof value === "number") return "number";
|
|
@@ -40,11 +38,7 @@ export function tokenTone(value: unknown): "string" | "number" | "boolean" | "em
|
|
|
40
38
|
return "string";
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
* 行内值的默认呈现(显示态 token)。
|
|
45
|
-
* @param props.owner - 字段上下文(值、是否 secret、只读原因)。
|
|
46
|
-
* @returns 一行里的值节点。
|
|
47
|
-
*/
|
|
41
|
+
// 行内值的默认呈现(显示态 token)。
|
|
48
42
|
export function InlineValue({ owner }: { owner: SchemaFieldOwnerProps }): ReactNode {
|
|
49
43
|
if (owner.options !== undefined) return <OptionSelect owner={owner} options={owner.options} />;
|
|
50
44
|
if (owner.node.meta.secret) {
|
|
@@ -66,11 +60,8 @@ export function InlineValue({ owner }: { owner: SchemaFieldOwnerProps }): ReactN
|
|
|
66
60
|
);
|
|
67
61
|
}
|
|
68
62
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
*
|
|
72
|
-
* 当前值不在候选里时(例如目录里已经没有那个服务商)照原样显示,选中才换成新值。
|
|
73
|
-
*/
|
|
63
|
+
// 选择器:**值本身**就是下拉的触发(不再另画一个 `select` 与它并列),点开是官方菜单。
|
|
64
|
+
// 当前值不在候选里时(例如目录里已经没有那个服务商)照原样显示,选中才换成新值。
|
|
74
65
|
function OptionSelect({
|
|
75
66
|
owner,
|
|
76
67
|
options,
|
|
@@ -128,7 +119,7 @@ function OptionSelect({
|
|
|
128
119
|
);
|
|
129
120
|
}
|
|
130
121
|
|
|
131
|
-
|
|
122
|
+
// 值是否就是这一项(菜单的高亮判据)。
|
|
132
123
|
function sameValue(left: unknown, right: unknown): boolean {
|
|
133
124
|
if (Object.is(left, right)) return true;
|
|
134
125
|
if (typeof left !== "object" || typeof right !== "object" || left === null || right === null)
|
|
@@ -136,11 +127,8 @@ function sameValue(left: unknown, right: unknown): boolean {
|
|
|
136
127
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
137
128
|
}
|
|
138
129
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
*
|
|
142
|
-
* 注意行式视图里键名来自配置键本身、注释来自 `hint`,所以 `label` 只在业务自写控件时才需要。
|
|
143
|
-
*/
|
|
130
|
+
// 字段槽注册方复用的默认值:只改文案时拿它包一层即可(`owner` 上覆盖 `label`/`hint`)。
|
|
131
|
+
// 行式视图里键名来自配置键本身、注释来自 `hint`,所以 `label` 只在业务自写控件时才需要。
|
|
144
132
|
export function SchemaFieldDefault({ owner }: { owner: SchemaFieldOwnerProps }): ReactNode {
|
|
145
133
|
return <InlineValue owner={owner} />;
|
|
146
134
|
}
|
|
@@ -12,39 +12,35 @@ const Footer = styled("div")(styles.footer);
|
|
|
12
12
|
const Failed = styled("p")(styles.failed);
|
|
13
13
|
const Save = styled("button")(styles.save);
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// The copy the form frame renders, from the owning plugin's dictionary.
|
|
16
16
|
export interface SettingsFormLabels {
|
|
17
|
-
|
|
17
|
+
// Shown in place of the controls while the namespace is not served.
|
|
18
18
|
unavailable: string;
|
|
19
|
-
|
|
19
|
+
// Shown over the controls while the document is read-only.
|
|
20
20
|
readOnly: string;
|
|
21
|
-
|
|
21
|
+
// Shown beside the save after a save the Host did not accept.
|
|
22
22
|
saveFailed: string;
|
|
23
|
-
|
|
23
|
+
// The save control.
|
|
24
24
|
save: string;
|
|
25
|
-
|
|
25
|
+
// The save control while a save is crossing the wire.
|
|
26
26
|
saving: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
// Form chrome shared by every settings page.
|
|
30
30
|
export interface SettingsFormProps {
|
|
31
|
-
|
|
31
|
+
// The frame's copy.
|
|
32
32
|
labels: SettingsFormLabels;
|
|
33
|
-
|
|
33
|
+
// The form state: availability, writability, and what a save would do.
|
|
34
34
|
state: SettingsFormShell;
|
|
35
|
-
|
|
35
|
+
// Write every staged edit.
|
|
36
36
|
onSave: () => void;
|
|
37
|
-
|
|
37
|
+
// Drop every staged edit; the form calls it when it leaves the page.
|
|
38
38
|
onDiscard: () => void;
|
|
39
|
-
|
|
39
|
+
// The plugin's controls.
|
|
40
40
|
children: ReactNode;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
* Render one plugin's settings form.
|
|
45
|
-
* @param props - the form's copy and state, its controls, and the save and discard actions.
|
|
46
|
-
* @returns the form, or the unavailable line while the namespace is not served.
|
|
47
|
-
*/
|
|
43
|
+
// Render one plugin's settings form.
|
|
48
44
|
export function SettingsForm(props: SettingsFormProps) {
|
|
49
45
|
const { state, labels } = props;
|
|
50
46
|
const discard = useRef(props.onDiscard);
|